@ckeditor/ckeditor5-engine 44.1.0-alpha.5 → 44.2.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.
- package/LICENSE.md +1 -1
- package/dist/index-content.css +2 -2
- package/dist/index-editor.css +2 -2
- package/dist/index.css +4 -4
- package/dist/index.css.map +1 -1
- package/dist/index.js +646 -77
- package/dist/index.js.map +1 -1
- package/package.json +23 -3
- package/src/controller/datacontroller.d.ts +1 -1
- package/src/controller/datacontroller.js +1 -1
- package/src/controller/editingcontroller.d.ts +1 -1
- package/src/controller/editingcontroller.js +1 -1
- package/src/conversion/conversion.d.ts +1 -1
- package/src/conversion/conversion.js +1 -1
- package/src/conversion/conversionhelpers.d.ts +1 -1
- package/src/conversion/conversionhelpers.js +1 -1
- package/src/conversion/downcastdispatcher.d.ts +1 -1
- package/src/conversion/downcastdispatcher.js +1 -1
- package/src/conversion/downcasthelpers.d.ts +1 -1
- package/src/conversion/downcasthelpers.js +1 -1
- package/src/conversion/mapper.d.ts +249 -20
- package/src/conversion/mapper.js +524 -38
- package/src/conversion/modelconsumable.d.ts +1 -1
- package/src/conversion/modelconsumable.js +1 -1
- package/src/conversion/upcastdispatcher.d.ts +1 -1
- package/src/conversion/upcastdispatcher.js +1 -1
- package/src/conversion/upcasthelpers.d.ts +1 -1
- package/src/conversion/upcasthelpers.js +1 -1
- package/src/conversion/viewconsumable.d.ts +1 -1
- package/src/conversion/viewconsumable.js +1 -1
- package/src/dataprocessor/basichtmlwriter.d.ts +1 -1
- package/src/dataprocessor/basichtmlwriter.js +1 -1
- package/src/dataprocessor/dataprocessor.d.ts +1 -1
- package/src/dataprocessor/dataprocessor.js +1 -1
- package/src/dataprocessor/htmldataprocessor.d.ts +1 -1
- package/src/dataprocessor/htmldataprocessor.js +1 -1
- package/src/dataprocessor/htmlwriter.d.ts +1 -1
- package/src/dataprocessor/htmlwriter.js +1 -1
- package/src/dataprocessor/xmldataprocessor.d.ts +1 -1
- package/src/dataprocessor/xmldataprocessor.js +1 -1
- package/src/dev-utils/model.d.ts +1 -1
- package/src/dev-utils/model.js +1 -1
- package/src/dev-utils/operationreplayer.d.ts +1 -1
- package/src/dev-utils/operationreplayer.js +1 -1
- package/src/dev-utils/utils.d.ts +1 -1
- package/src/dev-utils/utils.js +1 -1
- package/src/dev-utils/view.d.ts +1 -1
- package/src/dev-utils/view.js +1 -1
- package/src/index.d.ts +4 -2
- package/src/index.js +2 -1
- package/src/model/batch.d.ts +1 -1
- package/src/model/batch.js +7 -3
- package/src/model/differ.d.ts +1 -1
- package/src/model/differ.js +1 -1
- package/src/model/document.d.ts +1 -1
- package/src/model/document.js +1 -1
- package/src/model/documentfragment.d.ts +1 -1
- package/src/model/documentfragment.js +1 -1
- package/src/model/documentselection.d.ts +1 -1
- package/src/model/documentselection.js +1 -1
- package/src/model/element.d.ts +1 -1
- package/src/model/element.js +1 -1
- package/src/model/history.d.ts +1 -1
- package/src/model/history.js +1 -1
- package/src/model/item.d.ts +1 -1
- package/src/model/item.js +1 -1
- package/src/model/liveposition.d.ts +1 -1
- package/src/model/liveposition.js +1 -1
- package/src/model/liverange.d.ts +1 -1
- package/src/model/liverange.js +1 -1
- package/src/model/markercollection.d.ts +1 -1
- package/src/model/markercollection.js +1 -1
- package/src/model/model.d.ts +1 -1
- package/src/model/model.js +1 -1
- package/src/model/node.d.ts +1 -1
- package/src/model/node.js +1 -1
- package/src/model/nodelist.d.ts +1 -1
- package/src/model/nodelist.js +1 -1
- package/src/model/operation/attributeoperation.d.ts +1 -1
- package/src/model/operation/attributeoperation.js +1 -1
- package/src/model/operation/detachoperation.d.ts +1 -1
- package/src/model/operation/detachoperation.js +1 -1
- package/src/model/operation/insertoperation.d.ts +1 -1
- package/src/model/operation/insertoperation.js +1 -1
- package/src/model/operation/markeroperation.d.ts +1 -1
- package/src/model/operation/markeroperation.js +1 -1
- package/src/model/operation/mergeoperation.d.ts +1 -1
- package/src/model/operation/mergeoperation.js +1 -1
- package/src/model/operation/moveoperation.d.ts +1 -1
- package/src/model/operation/moveoperation.js +1 -1
- package/src/model/operation/nooperation.d.ts +1 -1
- package/src/model/operation/nooperation.js +1 -1
- package/src/model/operation/operation.d.ts +4 -1
- package/src/model/operation/operation.js +2 -2
- package/src/model/operation/operationfactory.d.ts +1 -1
- package/src/model/operation/operationfactory.js +1 -1
- package/src/model/operation/renameoperation.d.ts +1 -1
- package/src/model/operation/renameoperation.js +1 -1
- package/src/model/operation/rootattributeoperation.d.ts +1 -1
- package/src/model/operation/rootattributeoperation.js +1 -1
- package/src/model/operation/rootoperation.d.ts +1 -1
- package/src/model/operation/rootoperation.js +1 -1
- package/src/model/operation/splitoperation.d.ts +1 -1
- package/src/model/operation/splitoperation.js +1 -1
- package/src/model/operation/transform.d.ts +1 -1
- package/src/model/operation/transform.js +1 -1
- package/src/model/operation/utils.d.ts +1 -1
- package/src/model/operation/utils.js +1 -1
- package/src/model/position.d.ts +1 -1
- package/src/model/position.js +1 -1
- package/src/model/range.d.ts +1 -1
- package/src/model/range.js +1 -1
- package/src/model/rootelement.d.ts +1 -1
- package/src/model/rootelement.js +1 -1
- package/src/model/schema.d.ts +1 -1
- package/src/model/schema.js +1 -1
- package/src/model/selection.d.ts +1 -1
- package/src/model/selection.js +9 -2
- package/src/model/text.d.ts +1 -1
- package/src/model/text.js +1 -1
- package/src/model/textproxy.d.ts +1 -1
- package/src/model/textproxy.js +1 -1
- package/src/model/treewalker.d.ts +17 -1
- package/src/model/treewalker.js +26 -1
- package/src/model/typecheckable.d.ts +1 -1
- package/src/model/typecheckable.js +1 -1
- package/src/model/utils/autoparagraphing.d.ts +1 -1
- package/src/model/utils/autoparagraphing.js +1 -1
- package/src/model/utils/deletecontent.d.ts +1 -1
- package/src/model/utils/deletecontent.js +1 -1
- package/src/model/utils/getselectedcontent.d.ts +1 -1
- package/src/model/utils/getselectedcontent.js +1 -1
- package/src/model/utils/insertcontent.d.ts +1 -1
- package/src/model/utils/insertcontent.js +1 -1
- package/src/model/utils/insertobject.d.ts +1 -1
- package/src/model/utils/insertobject.js +1 -1
- package/src/model/utils/modifyselection.d.ts +1 -1
- package/src/model/utils/modifyselection.js +1 -1
- package/src/model/utils/selection-post-fixer.d.ts +1 -1
- package/src/model/utils/selection-post-fixer.js +1 -1
- package/src/model/writer.d.ts +1 -1
- package/src/model/writer.js +1 -1
- package/src/view/attributeelement.d.ts +1 -1
- package/src/view/attributeelement.js +1 -1
- package/src/view/containerelement.d.ts +1 -1
- package/src/view/containerelement.js +1 -1
- package/src/view/datatransfer.d.ts +1 -1
- package/src/view/datatransfer.js +1 -1
- package/src/view/document.d.ts +1 -1
- package/src/view/document.js +1 -1
- package/src/view/documentfragment.d.ts +6 -4
- package/src/view/documentfragment.js +7 -7
- package/src/view/documentselection.d.ts +1 -1
- package/src/view/documentselection.js +1 -1
- package/src/view/domconverter.d.ts +1 -1
- package/src/view/domconverter.js +1 -1
- package/src/view/downcastwriter.d.ts +1 -1
- package/src/view/downcastwriter.js +1 -1
- package/src/view/editableelement.d.ts +1 -1
- package/src/view/editableelement.js +1 -1
- package/src/view/element.d.ts +1 -1
- package/src/view/element.js +3 -3
- package/src/view/elementdefinition.d.ts +1 -1
- package/src/view/elementdefinition.js +1 -1
- package/src/view/emptyelement.d.ts +1 -1
- package/src/view/emptyelement.js +1 -1
- package/src/view/filler.d.ts +1 -1
- package/src/view/filler.js +1 -1
- package/src/view/item.d.ts +1 -1
- package/src/view/item.js +1 -1
- package/src/view/matcher.d.ts +1 -1
- package/src/view/matcher.js +1 -1
- package/src/view/node.d.ts +13 -4
- package/src/view/node.js +5 -4
- package/src/view/observer/arrowkeysobserver.d.ts +1 -1
- package/src/view/observer/arrowkeysobserver.js +1 -1
- package/src/view/observer/bubblingemittermixin.d.ts +1 -1
- package/src/view/observer/bubblingemittermixin.js +1 -1
- package/src/view/observer/bubblingeventinfo.d.ts +1 -1
- package/src/view/observer/bubblingeventinfo.js +1 -1
- package/src/view/observer/clickobserver.d.ts +1 -1
- package/src/view/observer/clickobserver.js +1 -1
- package/src/view/observer/compositionobserver.d.ts +1 -1
- package/src/view/observer/compositionobserver.js +1 -1
- package/src/view/observer/domeventdata.d.ts +1 -1
- package/src/view/observer/domeventdata.js +1 -1
- package/src/view/observer/domeventobserver.d.ts +1 -1
- package/src/view/observer/domeventobserver.js +1 -1
- package/src/view/observer/fakeselectionobserver.d.ts +1 -1
- package/src/view/observer/fakeselectionobserver.js +1 -1
- package/src/view/observer/focusobserver.d.ts +1 -1
- package/src/view/observer/focusobserver.js +1 -1
- package/src/view/observer/inputobserver.d.ts +1 -1
- package/src/view/observer/inputobserver.js +1 -1
- package/src/view/observer/keyobserver.d.ts +1 -1
- package/src/view/observer/keyobserver.js +1 -1
- package/src/view/observer/mouseobserver.d.ts +1 -1
- package/src/view/observer/mouseobserver.js +1 -1
- package/src/view/observer/mutationobserver.d.ts +1 -1
- package/src/view/observer/mutationobserver.js +1 -1
- package/src/view/observer/observer.d.ts +1 -1
- package/src/view/observer/observer.js +1 -1
- package/src/view/observer/selectionobserver.d.ts +1 -1
- package/src/view/observer/selectionobserver.js +1 -1
- package/src/view/observer/tabobserver.d.ts +1 -1
- package/src/view/observer/tabobserver.js +1 -1
- package/src/view/observer/touchobserver.d.ts +73 -0
- package/src/view/observer/touchobserver.js +29 -0
- package/src/view/placeholder.d.ts +1 -1
- package/src/view/placeholder.js +23 -17
- package/src/view/position.d.ts +1 -1
- package/src/view/position.js +1 -1
- package/src/view/range.d.ts +1 -1
- package/src/view/range.js +1 -1
- package/src/view/rawelement.d.ts +1 -1
- package/src/view/rawelement.js +1 -1
- package/src/view/renderer.d.ts +1 -1
- package/src/view/renderer.js +1 -1
- package/src/view/rooteditableelement.d.ts +1 -1
- package/src/view/rooteditableelement.js +1 -1
- package/src/view/selection.d.ts +1 -1
- package/src/view/selection.js +1 -1
- package/src/view/styles/background.d.ts +1 -1
- package/src/view/styles/background.js +1 -1
- package/src/view/styles/border.d.ts +1 -1
- package/src/view/styles/border.js +1 -1
- package/src/view/styles/margin.d.ts +1 -1
- package/src/view/styles/margin.js +1 -1
- package/src/view/styles/padding.d.ts +1 -1
- package/src/view/styles/padding.js +1 -1
- package/src/view/styles/utils.d.ts +1 -1
- package/src/view/styles/utils.js +1 -1
- package/src/view/stylesmap.d.ts +1 -1
- package/src/view/stylesmap.js +1 -1
- package/src/view/text.d.ts +1 -1
- package/src/view/text.js +1 -1
- package/src/view/textproxy.d.ts +1 -1
- package/src/view/textproxy.js +1 -1
- package/src/view/treewalker.d.ts +17 -1
- package/src/view/treewalker.js +25 -1
- package/src/view/typecheckable.d.ts +1 -1
- package/src/view/typecheckable.js +1 -1
- package/src/view/uielement.d.ts +1 -1
- package/src/view/uielement.js +1 -1
- package/src/view/upcastwriter.d.ts +1 -1
- package/src/view/upcastwriter.js +1 -1
- package/src/view/view.d.ts +1 -1
- package/src/view/view.js +1 -1
- package/theme/placeholder.css +1 -1
- package/theme/renderer.css +1 -1
- package/dist/controller/datacontroller.d.ts +0 -339
- package/dist/controller/editingcontroller.d.ts +0 -102
- package/dist/conversion/conversion.d.ts +0 -482
- package/dist/conversion/conversionhelpers.d.ts +0 -30
- package/dist/conversion/downcastdispatcher.d.ts +0 -566
- package/dist/conversion/downcasthelpers.d.ts +0 -1194
- package/dist/conversion/mapper.d.ts +0 -507
- package/dist/conversion/modelconsumable.d.ts +0 -205
- package/dist/conversion/upcastdispatcher.d.ts +0 -496
- package/dist/conversion/upcasthelpers.d.ts +0 -503
- package/dist/conversion/viewconsumable.d.ts +0 -373
- package/dist/dataprocessor/basichtmlwriter.d.ts +0 -22
- package/dist/dataprocessor/dataprocessor.d.ts +0 -65
- package/dist/dataprocessor/htmldataprocessor.d.ts +0 -80
- package/dist/dataprocessor/htmlwriter.d.ts +0 -20
- package/dist/dataprocessor/xmldataprocessor.d.ts +0 -94
- package/dist/dev-utils/model.d.ts +0 -130
- package/dist/dev-utils/operationreplayer.d.ts +0 -55
- package/dist/dev-utils/utils.d.ts +0 -41
- package/dist/dev-utils/view.d.ts +0 -324
- package/dist/index.d.ts +0 -122
- package/dist/model/batch.d.ts +0 -110
- package/dist/model/differ.d.ts +0 -511
- package/dist/model/document.d.ts +0 -278
- package/dist/model/documentfragment.d.ts +0 -223
- package/dist/model/documentselection.d.ts +0 -424
- package/dist/model/element.d.ts +0 -191
- package/dist/model/history.d.ts +0 -118
- package/dist/model/item.d.ts +0 -18
- package/dist/model/liveposition.d.ts +0 -81
- package/dist/model/liverange.d.ts +0 -106
- package/dist/model/markercollection.d.ts +0 -339
- package/dist/model/model.d.ts +0 -923
- package/dist/model/node.d.ts +0 -262
- package/dist/model/nodelist.d.ts +0 -119
- package/dist/model/operation/attributeoperation.d.ts +0 -107
- package/dist/model/operation/detachoperation.d.ts +0 -64
- package/dist/model/operation/insertoperation.d.ts +0 -94
- package/dist/model/operation/markeroperation.d.ts +0 -95
- package/dist/model/operation/mergeoperation.d.ts +0 -104
- package/dist/model/operation/moveoperation.d.ts +0 -100
- package/dist/model/operation/nooperation.d.ts +0 -42
- package/dist/model/operation/operation.d.ts +0 -100
- package/dist/model/operation/operationfactory.d.ts +0 -22
- package/dist/model/operation/renameoperation.d.ts +0 -87
- package/dist/model/operation/rootattributeoperation.d.ts +0 -102
- package/dist/model/operation/rootoperation.d.ts +0 -80
- package/dist/model/operation/splitoperation.d.ts +0 -113
- package/dist/model/operation/transform.d.ts +0 -104
- package/dist/model/operation/utils.d.ts +0 -75
- package/dist/model/position.d.ts +0 -549
- package/dist/model/range.d.ts +0 -462
- package/dist/model/rootelement.d.ts +0 -64
- package/dist/model/schema.d.ts +0 -1334
- package/dist/model/selection.d.ts +0 -486
- package/dist/model/text.d.ts +0 -70
- package/dist/model/textproxy.d.ts +0 -148
- package/dist/model/treewalker.d.ts +0 -190
- package/dist/model/typecheckable.d.ts +0 -289
- package/dist/model/utils/autoparagraphing.d.ts +0 -41
- package/dist/model/utils/deletecontent.d.ts +0 -62
- package/dist/model/utils/getselectedcontent.d.ts +0 -34
- package/dist/model/utils/insertcontent.d.ts +0 -50
- package/dist/model/utils/insertobject.d.ts +0 -51
- package/dist/model/utils/modifyselection.d.ts +0 -52
- package/dist/model/utils/selection-post-fixer.d.ts +0 -78
- package/dist/model/writer.d.ts +0 -855
- package/dist/view/attributeelement.d.ts +0 -112
- package/dist/view/containerelement.d.ts +0 -53
- package/dist/view/datatransfer.d.ts +0 -83
- package/dist/view/document.d.ts +0 -188
- package/dist/view/documentfragment.d.ts +0 -157
- package/dist/view/documentselection.d.ts +0 -310
- package/dist/view/domconverter.d.ts +0 -665
- package/dist/view/downcastwriter.d.ts +0 -1000
- package/dist/view/editableelement.d.ts +0 -66
- package/dist/view/element.d.ts +0 -472
- package/dist/view/elementdefinition.d.ts +0 -91
- package/dist/view/emptyelement.d.ts +0 -45
- package/dist/view/filler.d.ts +0 -115
- package/dist/view/item.d.ts +0 -18
- package/dist/view/matcher.d.ts +0 -490
- package/dist/view/node.d.ts +0 -166
- package/dist/view/observer/arrowkeysobserver.d.ts +0 -49
- package/dist/view/observer/bubblingemittermixin.d.ts +0 -170
- package/dist/view/observer/bubblingeventinfo.d.ts +0 -51
- package/dist/view/observer/clickobserver.d.ts +0 -47
- package/dist/view/observer/compositionobserver.d.ts +0 -86
- package/dist/view/observer/domeventdata.d.ts +0 -54
- package/dist/view/observer/domeventobserver.d.ts +0 -82
- package/dist/view/observer/fakeselectionobserver.d.ts +0 -51
- package/dist/view/observer/focusobserver.d.ts +0 -98
- package/dist/view/observer/inputobserver.d.ts +0 -90
- package/dist/view/observer/keyobserver.d.ts +0 -70
- package/dist/view/observer/mouseobserver.d.ts +0 -93
- package/dist/view/observer/mutationobserver.d.ts +0 -119
- package/dist/view/observer/observer.d.ts +0 -93
- package/dist/view/observer/selectionobserver.d.ts +0 -151
- package/dist/view/observer/tabobserver.d.ts +0 -50
- package/dist/view/placeholder.d.ts +0 -100
- package/dist/view/position.d.ts +0 -192
- package/dist/view/range.d.ts +0 -283
- package/dist/view/rawelement.d.ts +0 -77
- package/dist/view/renderer.d.ts +0 -281
- package/dist/view/rooteditableelement.d.ts +0 -45
- package/dist/view/selection.d.ts +0 -379
- package/dist/view/styles/background.d.ts +0 -37
- package/dist/view/styles/border.d.ts +0 -47
- package/dist/view/styles/margin.d.ts +0 -33
- package/dist/view/styles/padding.d.ts +0 -33
- package/dist/view/styles/utils.d.ts +0 -97
- package/dist/view/stylesmap.d.ts +0 -685
- package/dist/view/text.d.ts +0 -78
- package/dist/view/textproxy.d.ts +0 -101
- package/dist/view/treewalker.d.ts +0 -199
- package/dist/view/typecheckable.d.ts +0 -452
- package/dist/view/uielement.d.ts +0 -100
- package/dist/view/upcastwriter.d.ts +0 -421
- package/dist/view/view.d.ts +0 -488
package/src/conversion/mapper.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
@@ -70,6 +70,10 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
70
70
|
* has been removed, moved or renamed).
|
|
71
71
|
*/
|
|
72
72
|
this._unboundMarkerNames = new Set();
|
|
73
|
+
/**
|
|
74
|
+
* Manages dynamic cache for the `Mapper` to improve the performance.
|
|
75
|
+
*/
|
|
76
|
+
this._cache = new MapperCache();
|
|
73
77
|
// Default mapper algorithm for mapping model position to view position.
|
|
74
78
|
this.on('modelToViewPosition', (evt, data) => {
|
|
75
79
|
if (data.viewPosition) {
|
|
@@ -138,7 +142,11 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
138
142
|
this._deferredBindingRemovals.set(viewElement, viewElement.root);
|
|
139
143
|
}
|
|
140
144
|
else {
|
|
141
|
-
this._viewToModelMapping.delete(viewElement);
|
|
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
|
+
}
|
|
142
150
|
if (this._modelToViewMapping.get(modelElement) == viewElement) {
|
|
143
151
|
this._modelToViewMapping.delete(modelElement);
|
|
144
152
|
}
|
|
@@ -159,7 +167,11 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
159
167
|
const viewElement = this.toViewElement(modelElement);
|
|
160
168
|
this._modelToViewMapping.delete(modelElement);
|
|
161
169
|
if (this._viewToModelMapping.get(viewElement) == modelElement) {
|
|
162
|
-
this._viewToModelMapping.delete(viewElement);
|
|
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
|
+
}
|
|
163
175
|
}
|
|
164
176
|
}
|
|
165
177
|
/**
|
|
@@ -317,6 +329,10 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
317
329
|
return elements;
|
|
318
330
|
}
|
|
319
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
|
+
*
|
|
320
336
|
* Registers a callback that evaluates the length in the model of a view element with the given name.
|
|
321
337
|
*
|
|
322
338
|
* The callback is fired with one argument, which is a view element instance. The callback is expected to return
|
|
@@ -346,6 +362,7 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
346
362
|
*
|
|
347
363
|
* @param viewElementName Name of view element for which callback is registered.
|
|
348
364
|
* @param lengthCallback Function return a length of view element instance in model.
|
|
365
|
+
* @deprecated
|
|
349
366
|
*/
|
|
350
367
|
registerViewToModelLength(viewElementName, lengthCallback) {
|
|
351
368
|
this._viewToModelLengthCallbacks.set(viewElementName, lengthCallback);
|
|
@@ -456,59 +473,122 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
456
473
|
return len;
|
|
457
474
|
}
|
|
458
475
|
/**
|
|
459
|
-
* Finds the position in
|
|
476
|
+
* Finds the position in a view element or view document fragment node (or in its children) with the expected model offset.
|
|
460
477
|
*
|
|
461
|
-
*
|
|
478
|
+
* If the passed `viewContainer` is bound to model, `Mapper` will use caching mechanism to improve performance.
|
|
462
479
|
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
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()`.
|
|
465
504
|
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
* <p>fo|<b>bar</b>bom</p> -> expected offset: 4, actual offset: 2
|
|
469
|
-
* <p>fo<b>bar</b>|bom</p> -> expected offset: 4, actual offset: 5 -> we are too far
|
|
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.
|
|
470
507
|
*
|
|
471
|
-
*
|
|
472
|
-
* <p>fo<b>|bar</b>bom</p> -> expected offset: 2, actual offset: 0
|
|
473
|
-
* <p>fo<b>bar|</b>bom</p> -> expected offset: 2, actual offset: 3 -> we are too far
|
|
508
|
+
* This method uses recursion to find positions inside deep structures. Example:
|
|
474
509
|
*
|
|
475
|
-
* findPositionIn( bar, 2 - ( 3 - 3 ) ):
|
|
476
|
-
* We are in the text node so we can simple find the offset.
|
|
477
|
-
* <p>fo<b>ba|r</b>bom</p> -> expected offset: 2, actual offset: 2 -> position found
|
|
478
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>.
|
|
479
515
|
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
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`.
|
|
483
529
|
*/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
let
|
|
487
|
-
// Length of the last scanned view node.
|
|
488
|
-
let lastLength = 0;
|
|
489
|
-
let modelOffset = 0;
|
|
490
|
-
let viewOffset = 0;
|
|
530
|
+
_findPositionStartingFrom(startViewPosition, startModelOffset, targetModelOffset, viewContainer, useCache) {
|
|
531
|
+
let viewParent = startViewPosition.parent;
|
|
532
|
+
let viewOffset = startViewPosition.offset;
|
|
491
533
|
// In the text node it is simple: the offset in the model equals the offset in the text.
|
|
492
534
|
if (viewParent.is('$text')) {
|
|
493
|
-
return new ViewPosition(viewParent,
|
|
535
|
+
return new ViewPosition(viewParent, targetModelOffset - startModelOffset);
|
|
494
536
|
}
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
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) {
|
|
498
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
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
499
570
|
lastLength = this.getModelLength(viewNode);
|
|
500
|
-
|
|
571
|
+
traversedModelOffset += lastLength;
|
|
501
572
|
viewOffset++;
|
|
573
|
+
if (useCache) {
|
|
574
|
+
// Note, that we cache the view position before and after a visited element here, so before we (possibly) "enter" it
|
|
575
|
+
// (see `else` below).
|
|
576
|
+
//
|
|
577
|
+
// Since `MapperCache#save` does not overwrite already cached model offsets, this way the cached position is set to
|
|
578
|
+
// a correct location, that is the closest to the mapped `viewContainer`.
|
|
579
|
+
//
|
|
580
|
+
// However, in some cases, we still need to "hoist" the cached position (see `MapperCache#_hoistViewPosition()`).
|
|
581
|
+
this._cache.save(viewParent, viewOffset, viewContainer, traversedModelOffset);
|
|
582
|
+
}
|
|
502
583
|
}
|
|
503
|
-
|
|
504
|
-
|
|
584
|
+
if (traversedModelOffset == targetModelOffset) {
|
|
585
|
+
// If it equals we found the position.
|
|
505
586
|
return this._moveViewPositionToTextNode(new ViewPosition(viewParent, viewOffset));
|
|
506
587
|
}
|
|
507
|
-
// If it is higher we need to enter last child.
|
|
508
588
|
else {
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
return this.
|
|
589
|
+
// If it is higher we overstepped with the last traversed view node.
|
|
590
|
+
// We need to "enter" it, and look for the view position / model offset inside the last visited view node.
|
|
591
|
+
return this._findPositionStartingFrom(new ViewPosition(viewNode, 0), traversedModelOffset - lastLength, targetModelOffset, viewContainer, useCache);
|
|
512
592
|
}
|
|
513
593
|
}
|
|
514
594
|
/**
|
|
@@ -539,3 +619,409 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
539
619
|
return viewPosition;
|
|
540
620
|
}
|
|
541
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* Cache mechanism for {@link module:engine/conversion/mapper~Mapper Mapper}.
|
|
624
|
+
*
|
|
625
|
+
* `MapperCache` improves performance for model-to-view position mapping, which is the main `Mapper` task. Asking for a mapping is much
|
|
626
|
+
* more frequent than actually performing changes, and even if the change happens, we can still partially keep the cache. This makes
|
|
627
|
+
* caching a useful strategy for `Mapper`.
|
|
628
|
+
*
|
|
629
|
+
* `MapperCache` will store some data for view elements or view document fragments that are mapped by the `Mapper`. These view items
|
|
630
|
+
* are "tracked" by the `MapperCache`. For such view items, we will keep entries of model offsets inside their mapped counterpart. For
|
|
631
|
+
* the cached model offsets, we will keep a view position that is inside the tracked item. This allows us to either get the mapping
|
|
632
|
+
* instantly, or at least in less steps than when calculating it from the beginning.
|
|
633
|
+
*
|
|
634
|
+
* Important problem related to caching is invalidating the cache. The cache must be invalidated each time the tracked view item changes.
|
|
635
|
+
* Additionally, we should invalidate as small part of the cache as possible. Since all the logic is encapsulated inside `MapperCache`,
|
|
636
|
+
* the `MapperCache` listens to view items {@link module:engine/view/node~ViewNodeChangeEvent `change` event} and reacts to it.
|
|
637
|
+
* Then, it invalidates just the part of the cache that is "after" the changed part of the view.
|
|
638
|
+
*
|
|
639
|
+
* As mentioned, `MapperCache` currently is used only for model-to-view position mapping as it was much bigger problem than view-to-model
|
|
640
|
+
* mapping. However, it should be possible to use it also for view-to-model.
|
|
641
|
+
*
|
|
642
|
+
* The main assumptions regarding `MapperCache` are:
|
|
643
|
+
*
|
|
644
|
+
* * it is an internal tool, used by `Mapper`, transparent to the outside (no additional effort when developing a plugin or a converter),
|
|
645
|
+
* * it stores all the necessary data internally, which makes it easier to disable or debug,
|
|
646
|
+
* * it is optimized for initial downcast process (long insertions), which is crucial for editor init and data save,
|
|
647
|
+
* * it does not save all possible positions for memory considerations, although it is a possible improvement, which may have increase
|
|
648
|
+
* performance, as well as simplify some parts of the `MapperCache` logic.
|
|
649
|
+
*
|
|
650
|
+
* @internal
|
|
651
|
+
*/
|
|
652
|
+
export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
|
|
653
|
+
constructor() {
|
|
654
|
+
super(...arguments);
|
|
655
|
+
/**
|
|
656
|
+
* For every view element or document fragment tracked by `MapperCache`, it holds currently cached data, or more precisely,
|
|
657
|
+
* model offset to view position mappings. See also `MappingCache` and `CacheItem`.
|
|
658
|
+
*
|
|
659
|
+
* If an item is tracked by `MapperCache` it has an entry in this structure, so this structure can be used to check which items
|
|
660
|
+
* are tracked by `MapperCache`. When an item is no longer tracked, it is removed from this structure.
|
|
661
|
+
*
|
|
662
|
+
* Although `MappingCache` and `CacheItem` structures allows for caching any model offsets and view positions, we only cache
|
|
663
|
+
* values for model offsets that are after a view node. So, in essence, positions inside text nodes are not cached. However, it takes
|
|
664
|
+
* from one to at most a few steps, to get from a cached position to a position that is inside a view text node.
|
|
665
|
+
*
|
|
666
|
+
* Additionally, only one item per `modelOffset` is cached. There can be several view nodes that "end" at the same `modelOffset`.
|
|
667
|
+
* In this case, we favour positions that are closer to the mapped item. For example
|
|
668
|
+
*
|
|
669
|
+
* * for model: `<paragraph>Some <$text bold=true italic=true>formatted</$text> text.</paragraph>`,
|
|
670
|
+
* * and view: `<p>Some <em><strong>formatted</strong></em> text.</p>`,
|
|
671
|
+
*
|
|
672
|
+
* for model offset `14` (after "d"), we store view position after `<em>` element (i.e. view position: at `<p>`, offset `2`).
|
|
673
|
+
*/
|
|
674
|
+
this._cachedMapping = new WeakMap();
|
|
675
|
+
/**
|
|
676
|
+
* When `MapperCache` {@link ~MapperCache#save saves} view position -> model offset mapping, a `CacheItem` is inserted into certain
|
|
677
|
+
* `MappingCache#cacheList` at some index. Additionally, we store that index with the view node that is before the cached view position.
|
|
678
|
+
*
|
|
679
|
+
* This allows to quickly get a cache list item related to certain view node, and hence, for fast cache invalidation.
|
|
680
|
+
*
|
|
681
|
+
* For example, consider view: `<p>Some <strong>bold</strong> text.</p>`, where `<p>` is a view element tracked by `MapperCache`.
|
|
682
|
+
* If all `<p>` children were visited by `MapperCache`, then `<p>` cache list would have four items, related to following model offsets:
|
|
683
|
+
* `0`, `5`, `9`, `15`. Then, view node `"Some "` would have index `1`, `<strong>` index `2`, and `" text." index `3`.
|
|
684
|
+
*
|
|
685
|
+
* 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`
|
|
686
|
+
* (and view offset `0`), and it is not related to any node.
|
|
687
|
+
*/
|
|
688
|
+
this._nodeToCacheListIndex = new WeakMap();
|
|
689
|
+
/**
|
|
690
|
+
* Callback fired whenever there is a direct or indirect children change in tracked view element or tracked view document fragment.
|
|
691
|
+
*
|
|
692
|
+
* This is specified as a property to make it easier to set as an event callback and to later turn off that event.
|
|
693
|
+
*/
|
|
694
|
+
this._invalidateOnChildrenChangeCallback = (evt, viewNode, data) => {
|
|
695
|
+
// View element or document fragment changed its children at `data.index`. Clear all cache starting from before that index.
|
|
696
|
+
this._clearCacheInsideParent(viewNode, data.index);
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* Callback fired whenever a view text node directly or indirectly inside a tracked view element or tracked view document fragment
|
|
700
|
+
* changes its text data.
|
|
701
|
+
*
|
|
702
|
+
* This is specified as a property to make it easier to set as an event callback and to later turn off that event.
|
|
703
|
+
*/
|
|
704
|
+
this._invalidateOnTextChangeCallback = (evt, viewNode) => {
|
|
705
|
+
// Text node has changed. Clear all the cache starting from before this text node.
|
|
706
|
+
this._clearCacheStartingBefore(viewNode);
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Saves cache for given view position mapping <-> model offset mapping. The view position should be after a node (i.e. it cannot
|
|
711
|
+
* be the first position inside its parent, or in other words, `viewOffset` must be greater than `0`).
|
|
712
|
+
*
|
|
713
|
+
* @param viewParent View position parent.
|
|
714
|
+
* @param viewOffset View position offset. Must be greater than `0`.
|
|
715
|
+
* @param viewContainer Tracked view position ascendant (it may be the direct parent of the view position).
|
|
716
|
+
* @param modelOffset Model offset in the model element or document fragment which is mapped to `viewContainer`.
|
|
717
|
+
*/
|
|
718
|
+
save(viewParent, viewOffset, viewContainer, modelOffset) {
|
|
719
|
+
// Get current cache for the tracked ancestor.
|
|
720
|
+
const cache = this._cachedMapping.get(viewContainer);
|
|
721
|
+
// See if there is already a cache defined for `modelOffset`.
|
|
722
|
+
const cacheItem = cache.cacheMap.get(modelOffset);
|
|
723
|
+
if (cacheItem) {
|
|
724
|
+
// We already cached this offset. Don't overwrite the cache.
|
|
725
|
+
//
|
|
726
|
+
// This assumes that `Mapper` works in a way that we first cache the parent and only then cache children, as we prefer position
|
|
727
|
+
// after the parent ("closer" to the tracked ancestor). It might be safer to check which position is preferred (newly saved or
|
|
728
|
+
// the one currently in cache) but it would require additional processing. For now, `Mapper#_findPositionIn()` and
|
|
729
|
+
// `Mapper#getModelLength()` are implemented so that parents are cached before their children.
|
|
730
|
+
//
|
|
731
|
+
// So, don't create new cache if one already exists. Instead, only save `_nodeToCacheListIndex` value for the related view node.
|
|
732
|
+
const viewChild = viewParent.getChild(viewOffset - 1);
|
|
733
|
+
// Figure out what index to save with `viewChild`.
|
|
734
|
+
// We have a `cacheItem` for the `modelOffset`, so we can get a `viewPosition` from there. Before that view position, there
|
|
735
|
+
// must be a node. That node must have an index set. This will be the index we will want to use.
|
|
736
|
+
// Since we expect `viewOffset` to be greater than 0, then in almost all cases `modelOffset` will be greater than 0 as well.
|
|
737
|
+
// As a result, we can expect `cacheItem.viewPosition.nodeBefore` to be set.
|
|
738
|
+
//
|
|
739
|
+
// However, in an edge case, were the tracked element contains a 0-model-length view element as the first child (UI element or
|
|
740
|
+
// an empty attribute element), then `modelOffset` will be 0, and `cacheItem` will be the first cache item, which is before any
|
|
741
|
+
// view node. In such edge case, `cacheItem.viewPosition.nodeBefore` is undefined, and we manually set to `0`.
|
|
742
|
+
const index = cacheItem.viewPosition.nodeBefore ? this._nodeToCacheListIndex.get(cacheItem.viewPosition.nodeBefore) : 0;
|
|
743
|
+
this._nodeToCacheListIndex.set(viewChild, index);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const viewPosition = new ViewPosition(viewParent, viewOffset);
|
|
747
|
+
const newCacheItem = { viewPosition, modelOffset };
|
|
748
|
+
// Extend the valid cache range.
|
|
749
|
+
cache.maxModelOffset = modelOffset > cache.maxModelOffset ? modelOffset : cache.maxModelOffset;
|
|
750
|
+
// Save the new cache item to the `cacheMap`.
|
|
751
|
+
cache.cacheMap.set(modelOffset, newCacheItem);
|
|
752
|
+
// Save the new cache item to the `cacheList`.
|
|
753
|
+
let i = cache.cacheList.length - 1;
|
|
754
|
+
// Mostly, we cache elements at the end of `cacheList` and the loop does not execute even once. But when we recursively visit nodes
|
|
755
|
+
// in `Mapper#_findPositionIn()`, then we will first cache the parent, and then it's children, and they will not be added at the
|
|
756
|
+
// end of `cacheList`. This is why we need to find correct index to insert them.
|
|
757
|
+
while (i >= 0 && cache.cacheList[i].modelOffset > modelOffset) {
|
|
758
|
+
i--;
|
|
759
|
+
}
|
|
760
|
+
cache.cacheList.splice(i + 1, 0, newCacheItem);
|
|
761
|
+
if (viewOffset > 0) {
|
|
762
|
+
const viewChild = viewParent.getChild(viewOffset - 1);
|
|
763
|
+
// There was an idea to also cache `viewContainer` here but, it could lead to wrong results. If we wanted to cache
|
|
764
|
+
// `viewContainer`, we probably would need to clear `this._nodeToCacheListIndex` when cache is cleared.
|
|
765
|
+
// Also, there was no gain from caching this value, the results were almost the same (statistical error).
|
|
766
|
+
this._nodeToCacheListIndex.set(viewChild, i + 1);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* For given `modelOffset` inside a model element mapped to given `viewContainer`, it returns the closest saved cache item
|
|
771
|
+
* (view position and related model offset) to the requested one.
|
|
772
|
+
*
|
|
773
|
+
* It can be exactly the requested mapping, or it can be mapping that is the closest starting point to look for the requested mapping.
|
|
774
|
+
*
|
|
775
|
+
* `viewContainer` must be a view element or document fragment that is mapped by the {@link ~Mapper Mapper}.
|
|
776
|
+
*
|
|
777
|
+
* If `viewContainer` is not yet tracked by the `MapperCache`, it will be automatically tracked after calling this method.
|
|
778
|
+
*
|
|
779
|
+
* Note: this method will automatically "hoist" cached positions, i.e. it will return a position that is closest to the tracked element.
|
|
780
|
+
*
|
|
781
|
+
* For example, if `<p>` is tracked element, and `^` is cached position:
|
|
782
|
+
*
|
|
783
|
+
* ```
|
|
784
|
+
* <p>This is <strong>some <em>heavily <u>formatted</u>^</em></strong> text.</p>
|
|
785
|
+
* ```
|
|
786
|
+
*
|
|
787
|
+
* If this position would be returned, instead, a position directly in `<p>` would be returned:
|
|
788
|
+
*
|
|
789
|
+
* ```
|
|
790
|
+
* <p>This is <strong>some <em>heavily <u>formatted</u></em></strong>^ text.</p>
|
|
791
|
+
* ```
|
|
792
|
+
*
|
|
793
|
+
* Note, that `modelOffset` for both positions is the same.
|
|
794
|
+
*
|
|
795
|
+
* @param viewContainer Tracked view element or document fragment, which cache will be used.
|
|
796
|
+
* @param modelOffset Model offset in a model element or document fragment, which is mapped to `viewContainer`.
|
|
797
|
+
*/
|
|
798
|
+
getClosest(viewContainer, modelOffset) {
|
|
799
|
+
const cache = this._cachedMapping.get(viewContainer);
|
|
800
|
+
let result;
|
|
801
|
+
if (cache) {
|
|
802
|
+
if (modelOffset > cache.maxModelOffset) {
|
|
803
|
+
result = cache.cacheList[cache.cacheList.length - 1];
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
const cacheItem = cache.cacheMap.get(modelOffset);
|
|
807
|
+
if (cacheItem) {
|
|
808
|
+
result = cacheItem;
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
result = this._findInCacheList(cache.cacheList, modelOffset);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
result = this.startTracking(viewContainer);
|
|
817
|
+
}
|
|
818
|
+
const viewPosition = this._hoistViewPosition(result.viewPosition);
|
|
819
|
+
return {
|
|
820
|
+
modelOffset: result.modelOffset,
|
|
821
|
+
viewPosition
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Moves a view position to a preferred location.
|
|
826
|
+
*
|
|
827
|
+
* The view position is moved up from a non-tracked view element as long as it remains at the end of its current parent.
|
|
828
|
+
*
|
|
829
|
+
* See example below to understand when it is important:
|
|
830
|
+
*
|
|
831
|
+
* Starting state:
|
|
832
|
+
*
|
|
833
|
+
* ```
|
|
834
|
+
* <p>This is <strong>some <em>heavily <u>formatted</u>^ piece of</em></strong> text.</p>
|
|
835
|
+
* ```
|
|
836
|
+
*
|
|
837
|
+
* Then we remove " piece of " and invalidate some cache:
|
|
838
|
+
*
|
|
839
|
+
* ```
|
|
840
|
+
* <p>This is <strong>some <em>heavily <u>formatted</u>^</em></strong> text.</p>
|
|
841
|
+
* ```
|
|
842
|
+
*
|
|
843
|
+
* Now, if we ask for model offset after letter "d" in "formatted", we should get a position in " text", but we will get in `<em>`.
|
|
844
|
+
* For this scenario, we need to hoist the position.
|
|
845
|
+
*
|
|
846
|
+
* ```
|
|
847
|
+
* <p>This is <strong>some <em>heavily <u>formatted</u></em></strong>^ text.</p>
|
|
848
|
+
* ```
|
|
849
|
+
*/
|
|
850
|
+
_hoistViewPosition(viewPosition) {
|
|
851
|
+
while (viewPosition.parent.parent && !this._cachedMapping.has(viewPosition.parent) && viewPosition.isAtEnd) {
|
|
852
|
+
const parent = viewPosition.parent.parent;
|
|
853
|
+
const offset = parent.getChildIndex(viewPosition.parent) + 1;
|
|
854
|
+
viewPosition = new ViewPosition(parent, offset);
|
|
855
|
+
}
|
|
856
|
+
return viewPosition;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Starts tracking given `viewContainer`, which must be mapped to a model element or model document fragment.
|
|
860
|
+
*
|
|
861
|
+
* Note, that this method is automatically called by
|
|
862
|
+
* {@link module:engine/conversion/mapper~MapperCache#getClosest `MapperCache#getClosest()`} and there is no need to call it manually.
|
|
863
|
+
*
|
|
864
|
+
* This method initializes the cache for `viewContainer` and adds callbacks for
|
|
865
|
+
* {@link module:engine/view/node~ViewNodeChangeEvent `change` event} fired by `viewContainer`. `MapperCache` listens to `change` event
|
|
866
|
+
* on the tracked elements to invalidate the stored cache.
|
|
867
|
+
*/
|
|
868
|
+
startTracking(viewContainer) {
|
|
869
|
+
const viewPosition = new ViewPosition(viewContainer, 0);
|
|
870
|
+
const initialCacheItem = { viewPosition, modelOffset: 0 };
|
|
871
|
+
const initialCache = {
|
|
872
|
+
maxModelOffset: 0,
|
|
873
|
+
cacheList: [initialCacheItem],
|
|
874
|
+
cacheMap: new Map([[0, initialCacheItem]])
|
|
875
|
+
};
|
|
876
|
+
this._cachedMapping.set(viewContainer, initialCache);
|
|
877
|
+
// Listen to changes in tracked view containers in order to invalidate the cache.
|
|
878
|
+
//
|
|
879
|
+
// Possible performance improvement. This event bubbles, so if there are multiple tracked (mapped) elements that are ancestors
|
|
880
|
+
// then this will be unnecessarily fired for each ancestor. This could be rewritten to listen only to roots and document fragments.
|
|
881
|
+
viewContainer.on('change:children', this._invalidateOnChildrenChangeCallback);
|
|
882
|
+
viewContainer.on('change:text', this._invalidateOnTextChangeCallback);
|
|
883
|
+
return initialCacheItem;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Stops tracking given `viewContainer`.
|
|
887
|
+
*
|
|
888
|
+
* It removes the cached data and stops listening to {@link module:engine/view/node~ViewNodeChangeEvent `change` event} on the
|
|
889
|
+
* `viewContainer`.
|
|
890
|
+
*/
|
|
891
|
+
stopTracking(viewContainer) {
|
|
892
|
+
viewContainer.off('change:children', this._invalidateOnChildrenChangeCallback);
|
|
893
|
+
viewContainer.off('change:text', this._invalidateOnTextChangeCallback);
|
|
894
|
+
this._cachedMapping.delete(viewContainer);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Invalidates cache inside `viewParent`, starting from given `index` in that parent.
|
|
898
|
+
*/
|
|
899
|
+
_clearCacheInsideParent(viewParent, index) {
|
|
900
|
+
if (index == 0) {
|
|
901
|
+
// Change at the beginning of the parent.
|
|
902
|
+
if (this._cachedMapping.has(viewParent)) {
|
|
903
|
+
// If this is a tracked element, clear all cache.
|
|
904
|
+
this._clearCacheAll(viewParent);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
// If this is not a tracked element, remove cache starting from before this element.
|
|
908
|
+
this._clearCacheStartingBefore(viewParent);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
// Change in the middle of the parent. Get a view node that's before the change.
|
|
913
|
+
const lastValidNode = viewParent.getChild(index - 1);
|
|
914
|
+
// Then, clear all cache starting from before this view node.
|
|
915
|
+
//
|
|
916
|
+
// Possible performance improvement. We could have had `_clearCacheAfter( lastValidNode )` instead.
|
|
917
|
+
// If the `lastValidNode` is the last unchanged node, then we could clear everything AFTER it, not before.
|
|
918
|
+
// However, with the current setup, it didn't work properly and the actual gain wasn't that big on the tested data.
|
|
919
|
+
// The problem was with following example: <p>Foo<em><strong>Xyz</strong></em>Bar</p>.
|
|
920
|
+
// In this example we cache position after <em>, i.e. view position `<p>` 2 is saved with model offset 6.
|
|
921
|
+
// Now, if we add some text in `<em>`, we won't validate this cached item even though it gets outdated.
|
|
922
|
+
// So, if there's a need to have `_clearCacheAfter()`, we need to solve the above case first.
|
|
923
|
+
//
|
|
924
|
+
this._clearCacheStartingBefore(lastValidNode);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Clears all the cache for given tracked `viewContainer`.
|
|
929
|
+
*/
|
|
930
|
+
_clearCacheAll(viewContainer) {
|
|
931
|
+
const cache = this._cachedMapping.get(viewContainer);
|
|
932
|
+
if (cache.maxModelOffset > 0) {
|
|
933
|
+
cache.maxModelOffset = 0;
|
|
934
|
+
cache.cacheList.length = 1;
|
|
935
|
+
cache.cacheMap.clear();
|
|
936
|
+
cache.cacheMap.set(0, cache.cacheList[0]);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Clears all the stored cache starting before given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
|
|
941
|
+
* or view document fragment.
|
|
942
|
+
*/
|
|
943
|
+
_clearCacheStartingBefore(viewNode) {
|
|
944
|
+
// To quickly invalidate the cache, we base on the cache list index stored with the node. See docs for `this._nodeToCacheListIndex`.
|
|
945
|
+
const cacheListIndex = this._nodeToCacheListIndex.get(viewNode);
|
|
946
|
+
// If there is no index stored, it means that this `viewNode` has not been cached yet.
|
|
947
|
+
if (cacheListIndex === undefined) {
|
|
948
|
+
// If the node is not cached, maybe it's parent is. We will try to invalidate the cache starting from before the parent.
|
|
949
|
+
// Note, that there always must be a parent if we got here.
|
|
950
|
+
const viewParent = viewNode.parent;
|
|
951
|
+
// If the parent is a non-tracked element, try clearing the cache starting before it.
|
|
952
|
+
//
|
|
953
|
+
// This situation may happen e.g. if structure like `<p><strong><em>Foo</em></strong>...` was stepped over in
|
|
954
|
+
// `Mapper#_findPositionIn()` and the children are not cached yet, but the `<strong>` element is. If something changes
|
|
955
|
+
// inside this structure, make sure to invalidate all the cache after `<strong>`.
|
|
956
|
+
//
|
|
957
|
+
// If the parent is a tracked element, then it means there's no cache to clear (nothing after the element is cached).
|
|
958
|
+
// In this case, there's nothing to do.
|
|
959
|
+
//
|
|
960
|
+
if (!this._cachedMapping.has(viewParent)) {
|
|
961
|
+
this._clearCacheStartingBefore(viewParent);
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
// Note: there was a consideration to save the `viewContainer` value together with `cacheListIndex` value.
|
|
966
|
+
// However, it is like it is on purpose. We want to find *current* mapped ancestor for the `viewNode`.
|
|
967
|
+
// This is an essential step to verify if the cache is still up-to-date.
|
|
968
|
+
// Actually, we could save `viewContainer` and compare it to current tracked ancestor to quickly invalidate.
|
|
969
|
+
// But this kinda happens with our flow and other assumptions around caching list index anyway.
|
|
970
|
+
let viewContainer = viewNode.parent;
|
|
971
|
+
while (!this._cachedMapping.has(viewContainer)) {
|
|
972
|
+
viewContainer = viewContainer.parent;
|
|
973
|
+
}
|
|
974
|
+
this._clearCacheFromIndex(viewContainer, cacheListIndex);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Clears all the cache in the cache list related to given `viewContainer`, starting from `index` (inclusive).
|
|
978
|
+
*/
|
|
979
|
+
_clearCacheFromIndex(viewContainer, index) {
|
|
980
|
+
if (index === 0) {
|
|
981
|
+
// Don't remove the first entry in the cache (this entry is always a mapping between view offset 0 <-> model offset 0,
|
|
982
|
+
// and it is a default value that is always expected to be in the cache list).
|
|
983
|
+
//
|
|
984
|
+
// The cache mechanism may ask to clear from index `0` in a case where a 0-model-length view element (UI element or empty
|
|
985
|
+
// attribute element) was at the beginning of tracked element. In such scenario, the view element is mapped through
|
|
986
|
+
// `nodeToCacheListIndex` to index `0`.
|
|
987
|
+
index = 1;
|
|
988
|
+
}
|
|
989
|
+
// Cache is always available here because we initialize it just before adding a listener that fires `_clearCacheFromIndex()`.
|
|
990
|
+
const cache = this._cachedMapping.get(viewContainer);
|
|
991
|
+
const cacheItem = cache.cacheList[index - 1];
|
|
992
|
+
if (!cacheItem) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
cache.maxModelOffset = cacheItem.modelOffset;
|
|
996
|
+
// Remove from cache all `CacheItem`s that are "after" the index to clear from.
|
|
997
|
+
const clearedItems = cache.cacheList.splice(index);
|
|
998
|
+
// For each removed item, make sure to also remove it from `cacheMap` and clear related entry in `_nodeToCacheListIndex`.
|
|
999
|
+
for (const item of clearedItems) {
|
|
1000
|
+
cache.cacheMap.delete(item.modelOffset);
|
|
1001
|
+
const viewNode = item.viewPosition.nodeBefore;
|
|
1002
|
+
this._nodeToCacheListIndex.delete(viewNode);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Finds a cache item in the given cache list, which `modelOffset` is closest (but smaller or equal) to given `offset`.
|
|
1007
|
+
*
|
|
1008
|
+
* Since `cacheList` is a sorted array, this uses binary search to retrieve the item quickly.
|
|
1009
|
+
*/
|
|
1010
|
+
_findInCacheList(cacheList, offset) {
|
|
1011
|
+
let start = 0;
|
|
1012
|
+
let end = cacheList.length - 1;
|
|
1013
|
+
let index = (end - start) >> 1;
|
|
1014
|
+
let item = cacheList[index];
|
|
1015
|
+
while (start < end) {
|
|
1016
|
+
if (item.modelOffset < offset) {
|
|
1017
|
+
start = index + 1;
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
end = index - 1;
|
|
1021
|
+
}
|
|
1022
|
+
index = start + ((end - start) >> 1);
|
|
1023
|
+
item = cacheList[index];
|
|
1024
|
+
}
|
|
1025
|
+
return item.modelOffset <= offset ? item : cacheList[index - 1];
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright (c) 2003-
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
/**
|