@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,1050 +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/conversion/mapper
7
- */
8
- import { ModelPosition } from '../model/position.js';
9
- import { ModelRange } from '../model/range.js';
10
- import { ViewPosition } from '../view/position.js';
11
- import { ViewRange } from '../view/range.js';
12
- import { CKEditorError, EmitterMixin } from '@ckeditor/ckeditor5-utils';
13
- /**
14
- * Maps elements, positions and markers between the {@link module:engine/view/document~ViewDocument view} and
15
- * the {@link module:engine/model/model model}.
16
- *
17
- * The instance of the Mapper used for the editing pipeline is available in
18
- * {@link module:engine/controller/editingcontroller~EditingController#mapper `editor.editing.mapper`}.
19
- *
20
- * Mapper uses bound elements to find corresponding elements and positions, so, to get proper results,
21
- * all model elements should be {@link module:engine/conversion/mapper~Mapper#bindElements bound}.
22
- *
23
- * To map the complex model to/from view relations, you may provide custom callbacks for the
24
- * {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition modelToViewPosition event} and
25
- * {@link module:engine/conversion/mapper~Mapper#event:viewToModelPosition viewToModelPosition event} that are fired whenever
26
- * a position mapping request occurs.
27
- * Those events are fired by the {@link module:engine/conversion/mapper~Mapper#toViewPosition toViewPosition}
28
- * and {@link module:engine/conversion/mapper~Mapper#toModelPosition toModelPosition} methods. `Mapper` adds its own default callbacks
29
- * with `'lowest'` priority. To override default `Mapper` mapping, add custom callback with higher priority and
30
- * stop the event.
31
- */
32
- export class Mapper extends /* #__PURE__ */ EmitterMixin() {
33
- /**
34
- * Model element to view element mapping.
35
- */
36
- _modelToViewMapping = new WeakMap();
37
- /**
38
- * View element to model element mapping.
39
- */
40
- _viewToModelMapping = new WeakMap();
41
- /**
42
- * A map containing callbacks between view element names and functions evaluating length of view elements
43
- * in model.
44
- */
45
- _viewToModelLengthCallbacks = new Map();
46
- /**
47
- * Model marker name to view elements mapping.
48
- *
49
- * Keys are `String`s while values are `Set`s with {@link module:engine/view/element~ViewElement view elements}.
50
- * One marker (name) can be mapped to multiple elements.
51
- */
52
- _markerNameToElements = new Map();
53
- /**
54
- * View element to model marker names mapping.
55
- *
56
- * This is reverse to {@link ~Mapper#_markerNameToElements} map.
57
- */
58
- _elementToMarkerNames = new Map();
59
- /**
60
- * The map of removed view elements with their current root (used for deferred unbinding).
61
- */
62
- _deferredBindingRemovals = new Map();
63
- /**
64
- * Stores marker names of markers which have changed due to unbinding a view element (so it is assumed that the view element
65
- * has been removed, moved or renamed).
66
- */
67
- _unboundMarkerNames = new Set();
68
- /**
69
- * Manages dynamic cache for the `Mapper` to improve the performance.
70
- */
71
- _cache = new MapperCache();
72
- /**
73
- * Creates an instance of the mapper.
74
- */
75
- constructor() {
76
- super();
77
- // Default mapper algorithm for mapping model position to view position.
78
- this.on('modelToViewPosition', (evt, data) => {
79
- if (data.viewPosition) {
80
- return;
81
- }
82
- const viewContainer = this._modelToViewMapping.get(data.modelPosition.parent);
83
- if (!viewContainer) {
84
- /**
85
- * A model position could not be mapped to the view because the parent of the model position
86
- * does not have a mapped view element (might have not been converted yet or it has no converter).
87
- *
88
- * Make sure that the model element is correctly converted to the view.
89
- *
90
- * @error mapping-model-position-view-parent-not-found
91
- */
92
- throw new CKEditorError('mapping-model-position-view-parent-not-found', this, { modelPosition: data.modelPosition });
93
- }
94
- data.viewPosition = this.findPositionIn(viewContainer, data.modelPosition.offset);
95
- }, { priority: 'low' });
96
- // Default mapper algorithm for mapping view position to model position.
97
- this.on('viewToModelPosition', (evt, data) => {
98
- if (data.modelPosition) {
99
- return;
100
- }
101
- const viewBlock = this.findMappedViewAncestor(data.viewPosition);
102
- const modelParent = this._viewToModelMapping.get(viewBlock);
103
- const modelOffset = this._toModelOffset(data.viewPosition.parent, data.viewPosition.offset, viewBlock);
104
- data.modelPosition = ModelPosition._createAt(modelParent, modelOffset);
105
- }, { priority: 'low' });
106
- }
107
- /**
108
- * Marks model and view elements as corresponding. Corresponding elements can be retrieved by using
109
- * the {@link module:engine/conversion/mapper~Mapper#toModelElement toModelElement} and
110
- * {@link module:engine/conversion/mapper~Mapper#toViewElement toViewElement} methods.
111
- * The information that elements are bound is also used to translate positions.
112
- *
113
- * @param modelElement Model element.
114
- * @param viewElement View element.
115
- */
116
- bindElements(modelElement, viewElement) {
117
- this._modelToViewMapping.set(modelElement, viewElement);
118
- this._viewToModelMapping.set(viewElement, modelElement);
119
- }
120
- /**
121
- * Unbinds the given {@link module:engine/view/element~ViewElement view element} from the map.
122
- *
123
- * **Note:** view-to-model binding will be removed, if it existed. However, corresponding model-to-view binding
124
- * will be removed only if model element is still bound to the passed `viewElement`.
125
- *
126
- * This behavior allows for re-binding model element to another view element without fear of losing the new binding
127
- * when the previously bound view element is unbound.
128
- *
129
- * @param viewElement View element to unbind.
130
- * @param options The options object.
131
- * @param options.defer Controls whether the binding should be removed immediately or deferred until a
132
- * {@link #flushDeferredBindings `flushDeferredBindings()`} call.
133
- */
134
- unbindViewElement(viewElement, options = {}) {
135
- const modelElement = this.toModelElement(viewElement);
136
- if (this._elementToMarkerNames.has(viewElement)) {
137
- for (const markerName of this._elementToMarkerNames.get(viewElement)) {
138
- this._unboundMarkerNames.add(markerName);
139
- }
140
- }
141
- if (options.defer) {
142
- this._deferredBindingRemovals.set(viewElement, viewElement.root);
143
- }
144
- else {
145
- const wasFound = this._viewToModelMapping.delete(viewElement);
146
- if (wasFound) {
147
- // Stop tracking after the element is no longer mapped. We want to track all mapped elements and only mapped elements.
148
- this._cache.stopTracking(viewElement);
149
- }
150
- if (this._modelToViewMapping.get(modelElement) == viewElement) {
151
- this._modelToViewMapping.delete(modelElement);
152
- }
153
- }
154
- }
155
- /**
156
- * Unbinds the given {@link module:engine/model/element~ModelElement model element} from the map.
157
- *
158
- * **Note:** the model-to-view binding will be removed, if it existed. However, the corresponding view-to-model binding
159
- * will be removed only if the view element is still bound to the passed `modelElement`.
160
- *
161
- * This behavior lets for re-binding view element to another model element without fear of losing the new binding
162
- * when the previously bound model element is unbound.
163
- *
164
- * @param modelElement Model element to unbind.
165
- */
166
- unbindModelElement(modelElement) {
167
- const viewElement = this.toViewElement(modelElement);
168
- this._modelToViewMapping.delete(modelElement);
169
- if (this._viewToModelMapping.get(viewElement) == modelElement) {
170
- const wasFound = this._viewToModelMapping.delete(viewElement);
171
- if (wasFound) {
172
- // Stop tracking after the element is no longer mapped. We want to track all mapped elements and only mapped elements.
173
- this._cache.stopTracking(viewElement);
174
- }
175
- }
176
- }
177
- /**
178
- * Binds the given marker name with the given {@link module:engine/view/element~ViewElement view element}. The element
179
- * will be added to the current set of elements bound with the given marker name.
180
- *
181
- * @param element Element to bind.
182
- * @param name Marker name.
183
- */
184
- bindElementToMarker(element, name) {
185
- const elements = this._markerNameToElements.get(name) || new Set();
186
- elements.add(element);
187
- const names = this._elementToMarkerNames.get(element) || new Set();
188
- names.add(name);
189
- this._markerNameToElements.set(name, elements);
190
- this._elementToMarkerNames.set(element, names);
191
- }
192
- /**
193
- * Unbinds an element from given marker name.
194
- *
195
- * @param element Element to unbind.
196
- * @param name Marker name.
197
- */
198
- unbindElementFromMarkerName(element, name) {
199
- const nameToElements = this._markerNameToElements.get(name);
200
- if (nameToElements) {
201
- nameToElements.delete(element);
202
- if (nameToElements.size == 0) {
203
- this._markerNameToElements.delete(name);
204
- }
205
- }
206
- const elementToNames = this._elementToMarkerNames.get(element);
207
- if (elementToNames) {
208
- elementToNames.delete(name);
209
- if (elementToNames.size == 0) {
210
- this._elementToMarkerNames.delete(element);
211
- }
212
- }
213
- }
214
- /**
215
- * Returns all marker names of markers which have changed due to unbinding a view element (so it is assumed that the view element
216
- * has been removed, moved or renamed) since the last flush. After returning, the marker names list is cleared.
217
- */
218
- flushUnboundMarkerNames() {
219
- const markerNames = Array.from(this._unboundMarkerNames);
220
- this._unboundMarkerNames.clear();
221
- return markerNames;
222
- }
223
- /**
224
- * Unbinds all deferred binding removals of view elements that in the meantime were not re-attached to some root or document fragment.
225
- *
226
- * See: {@link #unbindViewElement `unbindViewElement()`}.
227
- */
228
- flushDeferredBindings() {
229
- for (const [viewElement, root] of this._deferredBindingRemovals) {
230
- // Unbind it only if it wasn't re-attached to some root or document fragment.
231
- if (viewElement.root == root) {
232
- this.unbindViewElement(viewElement);
233
- }
234
- }
235
- this._deferredBindingRemovals = new Map();
236
- }
237
- /**
238
- * Removes all model to view and view to model bindings.
239
- */
240
- clearBindings() {
241
- this._modelToViewMapping = new WeakMap();
242
- this._viewToModelMapping = new WeakMap();
243
- this._markerNameToElements = new Map();
244
- this._elementToMarkerNames = new Map();
245
- this._unboundMarkerNames = new Set();
246
- this._deferredBindingRemovals = new Map();
247
- }
248
- toModelElement(viewElement) {
249
- return this._viewToModelMapping.get(viewElement);
250
- }
251
- toViewElement(modelElement) {
252
- return this._modelToViewMapping.get(modelElement);
253
- }
254
- /**
255
- * Gets the corresponding model range.
256
- *
257
- * @param viewRange View range.
258
- * @returns Corresponding model range.
259
- */
260
- toModelRange(viewRange) {
261
- return new ModelRange(this.toModelPosition(viewRange.start), this.toModelPosition(viewRange.end));
262
- }
263
- /**
264
- * Gets the corresponding view range.
265
- *
266
- * @param modelRange Model range.
267
- * @returns Corresponding view range.
268
- */
269
- toViewRange(modelRange) {
270
- return new ViewRange(this.toViewPosition(modelRange.start), this.toViewPosition(modelRange.end));
271
- }
272
- /**
273
- * Gets the corresponding model position.
274
- *
275
- * @fires viewToModelPosition
276
- * @param viewPosition View position.
277
- * @returns Corresponding model position.
278
- */
279
- toModelPosition(viewPosition) {
280
- const data = {
281
- viewPosition,
282
- mapper: this
283
- };
284
- this.fire('viewToModelPosition', data);
285
- return data.modelPosition;
286
- }
287
- /**
288
- * Gets the corresponding view position.
289
- *
290
- * @fires modelToViewPosition
291
- * @param modelPosition Model position.
292
- * @param options Additional options for position mapping process.
293
- * @param options.isPhantom Should be set to `true` if the model position to map is pointing to a place
294
- * in model tree which no longer exists. For example, it could be an end of a removed model range.
295
- * @returns Corresponding view position.
296
- */
297
- toViewPosition(modelPosition, options = {}) {
298
- const data = {
299
- modelPosition,
300
- mapper: this,
301
- isPhantom: options.isPhantom
302
- };
303
- this.fire('modelToViewPosition', data);
304
- return data.viewPosition;
305
- }
306
- /**
307
- * Gets all view elements bound to the given marker name.
308
- *
309
- * @param name Marker name.
310
- * @returns View elements bound with the given marker name or `null`
311
- * if no elements are bound to the given marker name.
312
- */
313
- markerNameToElements(name) {
314
- const boundElements = this._markerNameToElements.get(name);
315
- if (!boundElements) {
316
- return null;
317
- }
318
- const elements = new Set();
319
- for (const element of boundElements) {
320
- if (element.is('attributeElement')) {
321
- for (const clone of element.getElementsWithSameId()) {
322
- elements.add(clone);
323
- }
324
- }
325
- else {
326
- elements.add(element);
327
- }
328
- }
329
- return elements;
330
- }
331
- /**
332
- * **This method is deprecated and will be removed in one of the future CKEditor 5 releases.**
333
- *
334
- * **Using this method will turn off `Mapper` caching system and may degrade performance when operating on bigger documents.**
335
- *
336
- * Registers a callback that evaluates the length in the model of a view element with the given name.
337
- *
338
- * The callback is fired with one argument, which is a view element instance. The callback is expected to return
339
- * a number representing the length of the view element in the model.
340
- *
341
- * ```ts
342
- * // List item in view may contain nested list, which have other list items. In model though,
343
- * // the lists are represented by flat structure. Because of those differences, length of list view element
344
- * // may be greater than one. In the callback it's checked how many nested list items are in evaluated list item.
345
- *
346
- * function getViewListItemLength( element ) {
347
- * let length = 1;
348
- *
349
- * for ( let child of element.getChildren() ) {
350
- * if ( child.name == 'ul' || child.name == 'ol' ) {
351
- * for ( let item of child.getChildren() ) {
352
- * length += getViewListItemLength( item );
353
- * }
354
- * }
355
- * }
356
- *
357
- * return length;
358
- * }
359
- *
360
- * mapper.registerViewToModelLength( 'li', getViewListItemLength );
361
- * ```
362
- *
363
- * @param viewElementName Name of view element for which callback is registered.
364
- * @param lengthCallback Function return a length of view element instance in model.
365
- * @deprecated
366
- */
367
- registerViewToModelLength(viewElementName, lengthCallback) {
368
- this._viewToModelLengthCallbacks.set(viewElementName, lengthCallback);
369
- }
370
- /**
371
- * For the given `viewPosition`, finds and returns the closest ancestor of this position that has a mapping to
372
- * the model.
373
- *
374
- * @param viewPosition Position for which a mapped ancestor should be found.
375
- */
376
- findMappedViewAncestor(viewPosition) {
377
- let parent = viewPosition.parent;
378
- while (!this._viewToModelMapping.has(parent)) {
379
- parent = parent.parent;
380
- }
381
- return parent;
382
- }
383
- /**
384
- * Calculates model offset based on the view position and the block element.
385
- *
386
- * Example:
387
- *
388
- * ```html
389
- * <p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, p ) -> 5
390
- * ```
391
- *
392
- * Is a sum of:
393
- *
394
- * ```html
395
- * <p>foo|<b>bar</b></p> // _toModelOffset( p, 3, p ) -> 3
396
- * <p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, b ) -> 2
397
- * ```
398
- *
399
- * @param viewParent Position parent.
400
- * @param viewOffset Position offset.
401
- * @param viewBlock Block used as a base to calculate offset.
402
- * @returns Offset in the model.
403
- */
404
- _toModelOffset(viewParent, viewOffset, viewBlock) {
405
- if (viewBlock != viewParent) {
406
- // See example.
407
- const offsetToParentStart = this._toModelOffset(viewParent.parent, viewParent.index, viewBlock);
408
- const offsetInParent = this._toModelOffset(viewParent, viewOffset, viewParent);
409
- return offsetToParentStart + offsetInParent;
410
- }
411
- // viewBlock == viewParent, so we need to calculate the offset in the parent element.
412
- // If the position is a text it is simple ("ba|r" -> 2).
413
- if (viewParent.is('$text')) {
414
- return viewOffset;
415
- }
416
- // If the position is in an element we need to sum lengths of siblings ( <b> bar </b> foo | -> 3 + 3 = 6 ).
417
- let modelOffset = 0;
418
- for (let i = 0; i < viewOffset; i++) {
419
- modelOffset += this.getModelLength(viewParent.getChild(i));
420
- }
421
- return modelOffset;
422
- }
423
- /**
424
- * Gets the length of the view element in the model.
425
- *
426
- * The length is calculated as follows:
427
- * * if a {@link #registerViewToModelLength length mapping callback} is provided for the given `viewNode`, it is used to
428
- * evaluate the model length (`viewNode` is used as first and only parameter passed to the callback),
429
- * * length of a {@link module:engine/view/text~ViewText text node} is equal to the length of its
430
- * {@link module:engine/view/text~ViewText#data data},
431
- * * length of a {@link module:engine/view/uielement~ViewUIElement ui element} is equal to 0,
432
- * * length of a mapped {@link module:engine/view/element~ViewElement element} is equal to 1,
433
- * * length of a non-mapped {@link module:engine/view/element~ViewElement element} is equal to the length of its children.
434
- *
435
- * Examples:
436
- *
437
- * ```
438
- * foo -> 3 // Text length is equal to its data length.
439
- * <p>foo</p> -> 1 // Length of an element which is mapped is by default equal to 1.
440
- * <b>foo</b> -> 3 // Length of an element which is not mapped is a length of its children.
441
- * <div><p>x</p><p>y</p></div> -> 2 // Assuming that <div> is not mapped and <p> are mapped.
442
- * ```
443
- *
444
- * @param viewNode View node.
445
- * @returns Length of the node in the tree model.
446
- */
447
- getModelLength(viewNode) {
448
- const stack = [viewNode];
449
- let len = 0;
450
- while (stack.length > 0) {
451
- const node = stack.pop();
452
- const callback = node.name &&
453
- this._viewToModelLengthCallbacks.size > 0 &&
454
- this._viewToModelLengthCallbacks.get(node.name);
455
- if (callback) {
456
- len += callback(node);
457
- }
458
- else if (this._viewToModelMapping.has(node)) {
459
- len += 1;
460
- }
461
- else if (node.is('$text')) {
462
- len += node.data.length;
463
- }
464
- else if (node.is('uiElement')) {
465
- continue;
466
- }
467
- else {
468
- for (const child of node.getChildren()) {
469
- stack.push(child);
470
- }
471
- }
472
- }
473
- return len;
474
- }
475
- /**
476
- * Finds the position in a view element or view document fragment node (or in its children) with the expected model offset.
477
- *
478
- * If the passed `viewContainer` is bound to model, `Mapper` will use caching mechanism to improve performance.
479
- *
480
- * @param viewContainer Tree view element in which we are looking for the position.
481
- * @param modelOffset Expected offset.
482
- * @returns Found position.
483
- */
484
- findPositionIn(viewContainer, modelOffset) {
485
- if (modelOffset === 0) {
486
- // Quickly return if asked for a position at the beginning of the container. No need to fire complex mechanisms to find it.
487
- return this._moveViewPositionToTextNode(new ViewPosition(viewContainer, 0));
488
- }
489
- // Use cache only if there are no custom view-to-model length callbacks and only for bound elements.
490
- // View-to-model length callbacks are deprecated and should be removed in one of the following releases.
491
- // Then it will be possible to simplify some logic inside `Mapper`.
492
- // Note: we could consider requiring `viewContainer` to be a mapped item.
493
- const useCache = this._viewToModelLengthCallbacks.size == 0 && this._viewToModelMapping.has(viewContainer);
494
- if (useCache) {
495
- const cacheItem = this._cache.getClosest(viewContainer, modelOffset);
496
- return this._findPositionStartingFrom(cacheItem.viewPosition, cacheItem.modelOffset, modelOffset, viewContainer, true);
497
- }
498
- else {
499
- return this._findPositionStartingFrom(new ViewPosition(viewContainer, 0), 0, modelOffset, viewContainer, false);
500
- }
501
- }
502
- /**
503
- * Performs most of the logic for `Mapper#findPositionIn()`.
504
- *
505
- * It allows to start looking for the requested model offset from a given starting position, to enable caching. Using the cache,
506
- * you can set the starting point and skip all the calculations that were already previously done.
507
- *
508
- * This method uses recursion to find positions inside deep structures. Example:
509
- *
510
- * ```
511
- * <p>fo<b>bar</b>bom</p> -> target offset: 4
512
- * <p>|fo<b>bar</b>bom</p> -> target offset: 4, traversed offset: 0
513
- * <p>fo|<b>bar</b>bom</p> -> target offset: 4, traversed offset: 2
514
- * <p>fo<b>bar</b>|bom</p> -> target offset: 4, traversed offset: 5 -> we are too far, look recursively in <b>.
515
- *
516
- * <p>fo<b>|bar</b>bom</p> -> target offset: 4, traversed offset: 2
517
- * <p>fo<b>bar|</b>bom</p> -> target offset: 4, traversed offset: 5 -> we are too far, look inside "bar".
518
- *
519
- * <p>fo<b>ba|r</b>bom</p> -> target offset: 4, traversed offset: 2 -> position is inside text node at offset 4-2 = 2.
520
- * ```
521
- *
522
- * @param startViewPosition View position to start looking from.
523
- * @param startModelOffset Model offset related to `startViewPosition`.
524
- * @param targetModelOffset Target model offset to find.
525
- * @param viewContainer Mapped ancestor of `startViewPosition`. `startModelOffset` is the offset inside a model element or model
526
- * document fragment mapped to `viewContainer`.
527
- * @param useCache Whether `Mapper` should cache positions while traversing the view tree looking for `expectedModelOffset`.
528
- * @returns View position mapped to `targetModelOffset`.
529
- */
530
- _findPositionStartingFrom(startViewPosition, startModelOffset, targetModelOffset, viewContainer, useCache) {
531
- let viewParent = startViewPosition.parent;
532
- let viewOffset = startViewPosition.offset;
533
- // In the text node it is simple: the offset in the model equals the offset in the text.
534
- if (viewParent.is('$text')) {
535
- return new ViewPosition(viewParent, targetModelOffset - startModelOffset);
536
- }
537
- // Last scanned view node.
538
- let viewNode;
539
- // Total model offset of the view nodes that were visited so far.
540
- let traversedModelOffset = startModelOffset;
541
- // Model length of the last traversed view node.
542
- let lastLength = 0;
543
- while (traversedModelOffset < targetModelOffset) {
544
- viewNode = viewParent.getChild(viewOffset);
545
- if (!viewNode) {
546
- // If we still haven't reached the model offset, but we reached end of this `viewParent`, then we need to "leave" this
547
- // element and "go up", looking further for the target model offset. This can happen when cached model offset is "deeper"
548
- // but target model offset is "higher" in the view tree.
549
- //
550
- // Example: `<p>Foo<strong><em>Bar</em>^Baz</strong>Xyz</p>`
551
- //
552
- // Consider `^` is last cached position, when the `targetModelOffset` is `12`. In such case, we need to "go up" from
553
- // `<strong>` and continue traversing in `<p>`.
554
- //
555
- if (viewParent == viewContainer) {
556
- /**
557
- * A model position could not be mapped to the view because specified model offset was too big and could not be
558
- * found inside the mapped view element or view document fragment.
559
- *
560
- * @error mapping-model-offset-not-found
561
- */
562
- throw new CKEditorError('mapping-model-offset-not-found', this, { modelOffset: targetModelOffset, viewContainer });
563
- }
564
- else {
565
- viewOffset = viewParent.parent.getChildIndex(viewParent) + 1;
566
- viewParent = viewParent.parent;
567
- // Cache view position after stepping out of the view element to make sure that all visited view positions are cached.
568
- // Otherwise, cache invalidation may work incorrectly.
569
- if (useCache) {
570
- this._cache.save(viewParent, viewOffset, viewContainer, traversedModelOffset);
571
- }
572
- continue;
573
- }
574
- }
575
- if (useCache) {
576
- lastLength = this._getModelLengthAndCache(viewNode, viewContainer, traversedModelOffset);
577
- }
578
- else {
579
- lastLength = this.getModelLength(viewNode);
580
- }
581
- traversedModelOffset += lastLength;
582
- viewOffset++;
583
- }
584
- let viewPosition = new ViewPosition(viewParent, viewOffset);
585
- if (useCache) {
586
- // Make sure to hoist view position and save cache for all view positions along the way that have the same `modelOffset`.
587
- //
588
- // Consider example view:
589
- //
590
- // <p>Foo<strong><i>bar<em>xyz</em></i></strong>abc</p>
591
- //
592
- // Lets assume that we looked for model offset `9`, starting from `6` (as it was previously cached value).
593
- // In this case we will only traverse over `<em>xyz</em>` and cache view positions after "xyz" and after `</em>`.
594
- // After stepping over `<em>xyz</em>`, we will stop processing this view, as we will reach target model offset `9`.
595
- //
596
- // However, position before `</strong>` and before `abc` are also valid positions for model offset `9`.
597
- // Additionally, `Mapper` is supposed to return "hoisted" view positions, that is, we prefer positions that are closer to
598
- // the mapped `viewContainer`. If a position is nested inside attribute elements, it should be "moved up" if possible.
599
- //
600
- // As we hoist the view position, we need to make sure that all view positions valid for model offset `9` are cached.
601
- // This is necessary for cache invalidation to work correctly.
602
- //
603
- // To hoist a view position, we "go up" as long as the position is at the end of a non-mapped view element. We also cache
604
- // all necessary values. See example:
605
- //
606
- // <p>Foo<strong><i>bar<em>xyz</em>^</i></strong>abc</p>
607
- // <p>Foo<strong><i>bar<em>xyz</em></i>^</strong>abc</p>
608
- // <p>Foo<strong><i>bar<em>xyz</em></i></strong>^abc</p>
609
- //
610
- while (viewPosition.isAtEnd && viewPosition.parent !== viewContainer && viewPosition.parent.parent) {
611
- const cacheViewParent = viewPosition.parent.parent;
612
- const cacheViewOffset = cacheViewParent.getChildIndex(viewPosition.parent) + 1;
613
- this._cache.save(cacheViewParent, cacheViewOffset, viewContainer, traversedModelOffset);
614
- viewPosition = new ViewPosition(cacheViewParent, cacheViewOffset);
615
- }
616
- }
617
- if (traversedModelOffset == targetModelOffset) {
618
- // If it equals we found the position.
619
- //
620
- // Try moving view position into a text node if possible, as the editor engine prefers positions inside view text nodes.
621
- //
622
- // <p>Foo<strong><i>bar<em>xyz</em></i></strong>[]abc</p> --> <p>Foo<strong><i>bar<em>xyz</em></i></strong>{}abc</p>
623
- //
624
- return this._moveViewPositionToTextNode(viewPosition);
625
- }
626
- else {
627
- // If it is higher we overstepped with the last traversed view node.
628
- // We need to "enter" it, and look for the view position / model offset inside the last visited view node.
629
- return this._findPositionStartingFrom(new ViewPosition(viewNode, 0), traversedModelOffset - lastLength, targetModelOffset, viewContainer, useCache);
630
- }
631
- }
632
- /**
633
- * Gets the length of the view element in the model and updates cache values after each view item it visits.
634
- *
635
- * See also {@link #getModelLength}.
636
- *
637
- * @param viewNode View node.
638
- * @param viewContainer Ancestor of `viewNode` that is a mapped view element.
639
- * @param modelOffset Model offset at which the `viewNode` starts.
640
- * @returns Length of the node in the tree model.
641
- */
642
- _getModelLengthAndCache(viewNode, viewContainer, modelOffset) {
643
- let len = 0;
644
- if (this._viewToModelMapping.has(viewNode)) {
645
- len = 1;
646
- }
647
- else if (viewNode.is('$text')) {
648
- len = viewNode.data.length;
649
- }
650
- else if (!viewNode.is('uiElement')) {
651
- for (const child of viewNode.getChildren()) {
652
- len += this._getModelLengthAndCache(child, viewContainer, modelOffset + len);
653
- }
654
- }
655
- this._cache.save(viewNode.parent, viewNode.index + 1, viewContainer, modelOffset + len);
656
- return len;
657
- }
658
- /**
659
- * Because we prefer positions in the text nodes over positions next to text nodes, if the view position was next to a text node,
660
- * it moves it into the text node instead.
661
- *
662
- * ```
663
- * <p>[]<b>foo</b></p> -> <p>[]<b>foo</b></p> // do not touch if position is not directly next to text
664
- * <p>foo[]<b>foo</b></p> -> <p>foo{}<b>foo</b></p> // move to text node
665
- * <p><b>[]foo</b></p> -> <p><b>{}foo</b></p> // move to text node
666
- * ```
667
- *
668
- * @param viewPosition Position potentially next to the text node.
669
- * @returns Position in the text node if possible.
670
- */
671
- _moveViewPositionToTextNode(viewPosition) {
672
- // If the position is just after a text node, put it at the end of that text node.
673
- // If the position is just before a text node, put it at the beginning of that text node.
674
- const nodeBefore = viewPosition.nodeBefore;
675
- const nodeAfter = viewPosition.nodeAfter;
676
- if (nodeBefore && nodeBefore.is('view:$text')) {
677
- return new ViewPosition(nodeBefore, nodeBefore.data.length);
678
- }
679
- else if (nodeAfter && nodeAfter.is('view:$text')) {
680
- return new ViewPosition(nodeAfter, 0);
681
- }
682
- // Otherwise, just return the given position.
683
- return viewPosition;
684
- }
685
- }
686
- /**
687
- * Cache mechanism for {@link module:engine/conversion/mapper~Mapper Mapper}.
688
- *
689
- * `MapperCache` improves performance for model-to-view position mapping, which is the main `Mapper` task. Asking for a mapping is much
690
- * more frequent than actually performing changes, and even if the change happens, we can still partially keep the cache. This makes
691
- * caching a useful strategy for `Mapper`.
692
- *
693
- * `MapperCache` will store some data for view elements or view document fragments that are mapped by the `Mapper`. These view items
694
- * are "tracked" by the `MapperCache`. For such view items, we will keep entries of model offsets inside their mapped counterpart. For
695
- * the cached model offsets, we will keep a view position that is inside the tracked item. This allows us to either get the mapping
696
- * instantly, or at least in less steps than when calculating it from the beginning.
697
- *
698
- * Important problem related to caching is invalidating the cache. The cache must be invalidated each time the tracked view item changes.
699
- * Additionally, we should invalidate as small part of the cache as possible. Since all the logic is encapsulated inside `MapperCache`,
700
- * the `MapperCache` listens to view items {@link module:engine/view/node~ViewNodeChangeEvent `change` event} and reacts to it.
701
- * Then, it invalidates just the part of the cache that is "after" the changed part of the view.
702
- *
703
- * As mentioned, `MapperCache` currently is used only for model-to-view position mapping as it was much bigger problem than view-to-model
704
- * mapping. However, it should be possible to use it also for view-to-model.
705
- *
706
- * The main assumptions regarding `MapperCache` are:
707
- *
708
- * * it is an internal tool, used by `Mapper`, transparent to the outside (no additional effort when developing a plugin or a converter),
709
- * * it stores all the necessary data internally, which makes it easier to disable or debug,
710
- * * it is optimized for initial downcast process (long insertions), which is crucial for editor init and data save,
711
- * * it does not save all possible positions mapping for memory considerations, although it is a possible improvement, which may increase
712
- * performance, as well as simplify some parts of the `MapperCache` logic.
713
- *
714
- * @internal
715
- */
716
- export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
717
- /**
718
- * For every view element or document fragment tracked by `MapperCache`, it holds currently cached data, or more precisely,
719
- * model offset to view position mappings. See also `MappingCache` and `CacheItem`.
720
- *
721
- * If an item is tracked by `MapperCache` it has an entry in this structure, so this structure can be used to check which items
722
- * are tracked by `MapperCache`. When an item is no longer tracked, it is removed from this structure.
723
- *
724
- * Although `MappingCache` and `CacheItem` structures allows for caching any model offsets and view positions, we only cache
725
- * values for model offsets that are after a view node. So, in essence, positions inside text nodes are not cached. However, it takes
726
- * from one to at most a few steps, to get from a cached position to a position that is inside a view text node.
727
- *
728
- * Additionally, only one item per `modelOffset` is cached. There can be several view positions that map to the same `modelOffset`.
729
- * Only the first save for `modelOffset` is stored.
730
- */
731
- _cachedMapping = new WeakMap();
732
- /**
733
- * When `MapperCache` {@link ~MapperCache#save saves} view position -> model offset mapping, a `CacheItem` is inserted into certain
734
- * `MappingCache#cacheList` at some index. Additionally, we store that index with the view node that is before the cached view position.
735
- *
736
- * This allows to quickly get a cache list item related to certain view node, and hence, for fast cache invalidation.
737
- *
738
- * For example, consider view: `<p>Some <strong>bold</strong> text.</p>`, where `<p>` is a view element tracked by `MapperCache`.
739
- * If all `<p>` children were visited by `MapperCache`, then `<p>` cache list would have four items, related to following model offsets:
740
- * `0`, `5`, `9`, `15`. Then, view node `"Some "` would have index `1`, `<strong>` index `2`, and `" text." index `3`.
741
- *
742
- * Note that the index related with a node is always greater than `0`. The first item in cache list is always for model offset `0`
743
- * (and view offset `0`), and it is not related to any node.
744
- */
745
- _nodeToCacheListIndex = new WeakMap();
746
- /**
747
- * Callback fired whenever there is a direct or indirect children change in tracked view element or tracked view document fragment.
748
- *
749
- * This is specified as a property to make it easier to set as an event callback and to later turn off that event.
750
- */
751
- _invalidateOnChildrenChangeCallback = (evt, viewNode, data) => {
752
- // View element or document fragment changed its children at `data.index`. Clear all cache starting from before that index.
753
- this._clearCacheInsideParent(viewNode, data.index);
754
- };
755
- /**
756
- * Callback fired whenever a view text node directly or indirectly inside a tracked view element or tracked view document fragment
757
- * changes its text data.
758
- *
759
- * This is specified as a property to make it easier to set as an event callback and to later turn off that event.
760
- */
761
- _invalidateOnTextChangeCallback = (evt, viewNode) => {
762
- // It is enough to validate starting from "after the text node", because the first cache entry that we might want to invalidate
763
- // is the position after this text node.
764
- //
765
- // For example - assume following view and following view positions cached (marked by `^`): `<p>Foo^<strong>bar^</strong>^abc^</p>`.
766
- //
767
- // If we change text "bar", we only need to invalidate cached positions after it. Cached positions before the text are not changed.
768
- //
769
- this._clearCacheAfter(viewNode);
770
- };
771
- /**
772
- * Saves cache for given view position mapping <-> model offset mapping. The view position should be after a node (i.e. it cannot
773
- * be the first position inside its parent, or in other words, `viewOffset` must be greater than `0`).
774
- *
775
- * Note, that if `modelOffset` for given `viewContainer` was already saved, the stored view position (i.e. parent+offset) will not
776
- * be overwritten. However, it is important to still save it, as we still store additional data related to cached view positions.
777
- *
778
- * @param viewParent View position parent.
779
- * @param viewOffset View position offset. Must be greater than `0`.
780
- * @param viewContainer Tracked view position ascendant (it may be the direct parent of the view position).
781
- * @param modelOffset Model offset in the model element or document fragment which is mapped to `viewContainer`.
782
- */
783
- save(viewParent, viewOffset, viewContainer, modelOffset) {
784
- // Get current cache for the tracked ancestor.
785
- const cache = this._cachedMapping.get(viewContainer);
786
- // See if there is already a cache defined for `modelOffset`.
787
- const cacheItem = cache.cacheMap.get(modelOffset);
788
- if (cacheItem) {
789
- // We already cached this offset. Don't overwrite the cache.
790
- // However, we still need to set a proper entry in `_nodeToCacheListIndex`. We can figure it out based on existing `cacheItem`.
791
- //
792
- // We have a `cacheItem` for the `modelOffset`, so we can get a `viewPosition` from there. Before that view position, there
793
- // must be a node. That node must have an index set. This will be the index we will want to use.
794
- // Since we expect `viewOffset` to be greater than 0, then in almost all cases `modelOffset` will be greater than 0 as well.
795
- // As a result, we can expect `cacheItem.viewPosition.nodeBefore` to be set.
796
- //
797
- // However, in an edge case, were the tracked element contains a 0-model-length view element as the first child (UI element or
798
- // an empty attribute element), then `modelOffset` will be 0, and `cacheItem.viewPosition` will be before any view node.
799
- // In such edge case, `cacheItem.viewPosition.nodeBefore` is `undefined`, so we set index to `0`.
800
- //
801
- const viewChild = viewParent.getChild(viewOffset - 1);
802
- const index = cacheItem.viewPosition.nodeBefore ? this._nodeToCacheListIndex.get(cacheItem.viewPosition.nodeBefore) : 0;
803
- this._nodeToCacheListIndex.set(viewChild, index);
804
- return;
805
- }
806
- const viewPosition = new ViewPosition(viewParent, viewOffset);
807
- const newCacheItem = { viewPosition, modelOffset };
808
- // Extend the valid cache range.
809
- cache.maxModelOffset = modelOffset > cache.maxModelOffset ? modelOffset : cache.maxModelOffset;
810
- // Save the new cache item to the `cacheMap`.
811
- cache.cacheMap.set(modelOffset, newCacheItem);
812
- // Save the new cache item to the `cacheList`.
813
- let i = cache.cacheList.length - 1;
814
- // Mostly, we cache elements at the end of `cacheList` and the loop does not execute even once. But when we recursively visit nodes
815
- // in `Mapper#_findPositionIn()`, then we will first cache the parent, and then it's children, and they will not be added at the
816
- // end of `cacheList`. This is why we need to find correct index to insert them.
817
- while (i >= 0 && cache.cacheList[i].modelOffset > modelOffset) {
818
- i--;
819
- }
820
- cache.cacheList.splice(i + 1, 0, newCacheItem);
821
- if (viewOffset > 0) {
822
- const viewChild = viewParent.getChild(viewOffset - 1);
823
- // There was an idea to also cache `viewContainer` here but, it could lead to wrong results. If we wanted to cache
824
- // `viewContainer`, we probably would need to clear `this._nodeToCacheListIndex` when cache is cleared.
825
- // Also, there was no gain from caching this value, the results were almost the same (statistical error).
826
- this._nodeToCacheListIndex.set(viewChild, i + 1);
827
- }
828
- }
829
- /**
830
- * For given `modelOffset` inside a model element mapped to given `viewContainer`, it returns the closest saved cache item
831
- * (view position and related model offset) to the requested one.
832
- *
833
- * It can be exactly the requested mapping, or it can be mapping that is the closest starting point to look for the requested mapping.
834
- *
835
- * `viewContainer` must be a view element or document fragment that is mapped by the {@link ~Mapper Mapper}.
836
- *
837
- * If `viewContainer` is not yet tracked by the `MapperCache`, it will be automatically tracked after calling this method.
838
- *
839
- * Note: this method will automatically "hoist" cached positions, i.e. it will return a position that is closest to the tracked element.
840
- *
841
- * For example, if `<p>` is tracked element, and `^` is cached position:
842
- *
843
- * ```
844
- * <p>This is <strong>some <em>heavily <u>formatted</u>^</em></strong> text.</p>
845
- * ```
846
- *
847
- * If this position would be returned, instead, a position directly in `<p>` would be returned:
848
- *
849
- * ```
850
- * <p>This is <strong>some <em>heavily <u>formatted</u></em></strong>^ text.</p>
851
- * ```
852
- *
853
- * Note, that `modelOffset` for both positions is the same.
854
- *
855
- * @param viewContainer Tracked view element or document fragment, which cache will be used.
856
- * @param modelOffset Model offset in a model element or document fragment, which is mapped to `viewContainer`.
857
- */
858
- getClosest(viewContainer, modelOffset) {
859
- const cache = this._cachedMapping.get(viewContainer);
860
- let result;
861
- if (cache) {
862
- if (modelOffset > cache.maxModelOffset) {
863
- result = cache.cacheList[cache.cacheList.length - 1];
864
- }
865
- else {
866
- const cacheItem = cache.cacheMap.get(modelOffset);
867
- if (cacheItem) {
868
- result = cacheItem;
869
- }
870
- else {
871
- result = this._findInCacheList(cache.cacheList, modelOffset);
872
- }
873
- }
874
- }
875
- else {
876
- result = this.startTracking(viewContainer);
877
- }
878
- return {
879
- modelOffset: result.modelOffset,
880
- viewPosition: result.viewPosition.clone()
881
- };
882
- }
883
- /**
884
- * Starts tracking given `viewContainer`, which must be mapped to a model element or model document fragment.
885
- *
886
- * Note, that this method is automatically called by
887
- * {@link module:engine/conversion/mapper~MapperCache#getClosest `MapperCache#getClosest()`} and there is no need to call it manually.
888
- *
889
- * This method initializes the cache for `viewContainer` and adds callbacks for
890
- * {@link module:engine/view/node~ViewNodeChangeEvent `change` event} fired by `viewContainer`. `MapperCache` listens to `change` event
891
- * on the tracked elements to invalidate the stored cache.
892
- */
893
- startTracking(viewContainer) {
894
- const viewPosition = new ViewPosition(viewContainer, 0);
895
- const initialCacheItem = { viewPosition, modelOffset: 0 };
896
- const initialCache = {
897
- maxModelOffset: 0,
898
- cacheList: [initialCacheItem],
899
- cacheMap: new Map([[0, initialCacheItem]])
900
- };
901
- this._cachedMapping.set(viewContainer, initialCache);
902
- // Listen to changes in tracked view containers in order to invalidate the cache.
903
- //
904
- // Possible performance improvement. This event bubbles, so if there are multiple tracked (mapped) elements that are ancestors
905
- // then this will be unnecessarily fired for each ancestor. This could be rewritten to listen only to roots and document fragments.
906
- viewContainer.on('change:children', this._invalidateOnChildrenChangeCallback);
907
- viewContainer.on('change:text', this._invalidateOnTextChangeCallback);
908
- return initialCacheItem;
909
- }
910
- /**
911
- * Stops tracking given `viewContainer`.
912
- *
913
- * It removes the cached data and stops listening to {@link module:engine/view/node~ViewNodeChangeEvent `change` event} on the
914
- * `viewContainer`.
915
- */
916
- stopTracking(viewContainer) {
917
- viewContainer.off('change:children', this._invalidateOnChildrenChangeCallback);
918
- viewContainer.off('change:text', this._invalidateOnTextChangeCallback);
919
- this._cachedMapping.delete(viewContainer);
920
- }
921
- /**
922
- * Invalidates cache inside `viewParent`, starting from given `index` in that parent.
923
- *
924
- * This method may clear a bit more cache than just what was saved after given `index`, but it is guaranteed that at least it
925
- * will invalidate everything after `index`.
926
- */
927
- _clearCacheInsideParent(viewParent, index) {
928
- if (index == 0) {
929
- // Change at the beginning of the parent.
930
- if (this._cachedMapping.has(viewParent)) {
931
- // If this is a tracked element, clear all cache.
932
- this._clearCacheAll(viewParent);
933
- }
934
- else {
935
- // If this is not a tracked element, remove cache starting from before this element.
936
- // Since it is not a tracked element, it has to have a parent.
937
- this._clearCacheInsideParent(viewParent.parent, viewParent.index);
938
- }
939
- }
940
- else {
941
- // Change in the middle of the parent. Get a view node that's before the change.
942
- const lastValidNode = viewParent.getChild(index - 1);
943
- // Then, clear all cache after this view node.
944
- //
945
- this._clearCacheAfter(lastValidNode);
946
- }
947
- }
948
- /**
949
- * Clears all the cache for given tracked `viewContainer`.
950
- */
951
- _clearCacheAll(viewContainer) {
952
- const cache = this._cachedMapping.get(viewContainer);
953
- // TODO: Should clear `_nodeToCacheListIndex` too?
954
- if (cache.maxModelOffset > 0) {
955
- cache.maxModelOffset = 0;
956
- cache.cacheList.length = 1;
957
- cache.cacheMap.clear();
958
- cache.cacheMap.set(0, cache.cacheList[0]);
959
- }
960
- }
961
- /**
962
- * Clears all the stored cache that is after given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
963
- * or view document fragment.
964
- *
965
- * In reality, this function may clear a bit more cache than just "starting after" `viewNode`, but it is guaranteed that at least
966
- * all cache after `viewNode` is invalidated.
967
- */
968
- _clearCacheAfter(viewNode) {
969
- // To quickly invalidate the cache, we base on the cache list index stored with the node. See docs for `this._nodeToCacheListIndex`.
970
- const cacheListIndex = this._nodeToCacheListIndex.get(viewNode);
971
- // If there is no index stored, it means that this `viewNode` has not been cached yet.
972
- if (cacheListIndex === undefined) {
973
- // If the node is not cached, maybe it's parent is. We will try to invalidate the cache using the parent.
974
- const viewParent = viewNode.parent;
975
- // If the parent is a non-tracked element, try clearing the cache starting from the position before it.
976
- //
977
- // For example: `<p>Abc<strong>def<em>ghi</em></strong></p>`.
978
- //
979
- // If `viewNode` is `<em>` in this case, and it was not cached yet, we will try to clear cache starting from before `<strong>`.
980
- //
981
- // If the parent is a tracked element, then it means there's no cache to clear (nothing after the element is cached).
982
- // In this case, there's nothing to do. We assume that there are no "holes" in caching in direct children of tracked element
983
- // (that is if some children is cached, then its previous sibling is cached too, and we would not end up inside this `if`).
984
- //
985
- // TODO: Most probably this `if` could be removed altogether, after recent changes in Mapper.
986
- // TODO: Now we cache all items one after another, so there aren't any "holes" anywhere, not only on top-level.
987
- //
988
- if (!this._cachedMapping.has(viewParent)) {
989
- this._clearCacheInsideParent(viewParent.parent, viewParent.index);
990
- }
991
- return;
992
- }
993
- let viewContainer = viewNode.parent;
994
- while (!this._cachedMapping.has(viewContainer)) {
995
- viewContainer = viewContainer.parent;
996
- }
997
- this._clearCacheFromCacheIndex(viewContainer, cacheListIndex);
998
- }
999
- /**
1000
- * Clears all the cache in the cache list related to given `viewContainer`, starting from `index` (inclusive).
1001
- */
1002
- _clearCacheFromCacheIndex(viewContainer, index) {
1003
- if (index === 0) {
1004
- // Don't remove the first entry in the cache (this entry is always a mapping between view offset 0 <-> model offset 0,
1005
- // and it is a default value that is always expected to be in the cache list).
1006
- //
1007
- // The cache mechanism may ask to clear from index `0` in a case where a 0-model-length view element (UI element or empty
1008
- // attribute element) was at the beginning of tracked element. In such scenario, the view element is mapped through
1009
- // `nodeToCacheListIndex` to index `0`.
1010
- index = 1;
1011
- }
1012
- // Cache is always available here because we initialize it just before adding a listener that fires `_clearCacheFromIndex()`.
1013
- const cache = this._cachedMapping.get(viewContainer);
1014
- const cacheItem = cache.cacheList[index - 1];
1015
- if (!cacheItem) {
1016
- return;
1017
- }
1018
- cache.maxModelOffset = cacheItem.modelOffset;
1019
- // Remove from cache all `CacheItem`s that are "after" the index to clear from.
1020
- const clearedItems = cache.cacheList.splice(index);
1021
- // For each removed item, make sure to also remove it from `cacheMap` and clear related entry in `_nodeToCacheListIndex`.
1022
- for (const item of clearedItems) {
1023
- cache.cacheMap.delete(item.modelOffset);
1024
- const viewNode = item.viewPosition.nodeBefore;
1025
- this._nodeToCacheListIndex.delete(viewNode);
1026
- }
1027
- }
1028
- /**
1029
- * Finds a cache item in the given cache list, which `modelOffset` is closest (but smaller or equal) to given `offset`.
1030
- *
1031
- * Since `cacheList` is a sorted array, this uses binary search to retrieve the item quickly.
1032
- */
1033
- _findInCacheList(cacheList, offset) {
1034
- let start = 0;
1035
- let end = cacheList.length - 1;
1036
- let index = (end - start) >> 1;
1037
- let item = cacheList[index];
1038
- while (start < end) {
1039
- if (item.modelOffset < offset) {
1040
- start = index + 1;
1041
- }
1042
- else {
1043
- end = index - 1;
1044
- }
1045
- index = start + ((end - start) >> 1);
1046
- item = cacheList[index];
1047
- }
1048
- return item.modelOffset <= offset ? item : cacheList[index - 1];
1049
- }
1050
- }