@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.
- package/LICENSE.md +1 -1
- package/{src → dist}/engineconfig.d.ts +6 -15
- package/dist/index-editor.css +38 -15
- package/dist/index.css +37 -37
- package/dist/index.css.map +1 -1
- package/{src → dist}/index.d.ts +0 -1
- package/dist/index.js +588 -94
- package/dist/index.js.map +1 -1
- package/{src → dist}/model/model.d.ts +10 -4
- package/{src → dist}/model/selection.d.ts +1 -1
- package/{src → dist}/view/downcastwriter.d.ts +3 -2
- package/{src → dist}/view/element.d.ts +2 -2
- package/{src → dist}/view/matcher.d.ts +4 -2
- package/dist/view/styles/background.d.ts +18 -0
- package/{src → dist}/view/styles/border.d.ts +0 -12
- package/{src → dist}/view/styles/margin.d.ts +0 -13
- package/{src → dist}/view/styles/padding.d.ts +0 -13
- package/{src → dist}/view/styles/utils.d.ts +12 -0
- package/package.json +20 -39
- package/src/controller/datacontroller.js +0 -522
- package/src/controller/editingcontroller.js +0 -181
- package/src/conversion/conversion.js +0 -606
- package/src/conversion/conversionhelpers.js +0 -33
- package/src/conversion/downcastdispatcher.js +0 -563
- package/src/conversion/downcasthelpers.js +0 -2160
- package/src/conversion/mapper.js +0 -1050
- package/src/conversion/modelconsumable.js +0 -331
- package/src/conversion/upcastdispatcher.js +0 -470
- package/src/conversion/upcasthelpers.js +0 -952
- package/src/conversion/viewconsumable.js +0 -541
- package/src/dataprocessor/basichtmlwriter.js +0 -22
- package/src/dataprocessor/dataprocessor.js +0 -5
- package/src/dataprocessor/htmldataprocessor.js +0 -107
- package/src/dataprocessor/htmlwriter.js +0 -5
- package/src/dataprocessor/xmldataprocessor.js +0 -127
- package/src/dev-utils/model.js +0 -396
- package/src/dev-utils/operationreplayer.js +0 -116
- package/src/dev-utils/utils.js +0 -122
- package/src/dev-utils/view.js +0 -990
- package/src/engineconfig.js +0 -5
- package/src/index.js +0 -134
- package/src/legacyerrors.js +0 -17
- package/src/model/batch.js +0 -98
- package/src/model/differ.js +0 -1288
- package/src/model/document.js +0 -398
- package/src/model/documentfragment.js +0 -332
- package/src/model/documentselection.js +0 -1026
- package/src/model/element.js +0 -323
- package/src/model/history.js +0 -206
- package/src/model/item.js +0 -5
- package/src/model/liveposition.js +0 -93
- package/src/model/liverange.js +0 -121
- package/src/model/markercollection.js +0 -436
- package/src/model/model.js +0 -866
- package/src/model/node.js +0 -371
- package/src/model/nodelist.js +0 -244
- package/src/model/operation/attributeoperation.js +0 -172
- package/src/model/operation/detachoperation.js +0 -87
- package/src/model/operation/insertoperation.js +0 -153
- package/src/model/operation/markeroperation.js +0 -136
- package/src/model/operation/mergeoperation.js +0 -184
- package/src/model/operation/moveoperation.js +0 -179
- package/src/model/operation/nooperation.js +0 -48
- package/src/model/operation/operation.js +0 -78
- package/src/model/operation/operationfactory.js +0 -44
- package/src/model/operation/renameoperation.js +0 -128
- package/src/model/operation/rootattributeoperation.js +0 -173
- package/src/model/operation/rootoperation.js +0 -106
- package/src/model/operation/splitoperation.js +0 -214
- package/src/model/operation/transform.js +0 -2211
- package/src/model/operation/utils.js +0 -217
- package/src/model/position.js +0 -1041
- package/src/model/range.js +0 -880
- package/src/model/rootelement.js +0 -82
- package/src/model/schema.js +0 -1542
- package/src/model/selection.js +0 -814
- package/src/model/text.js +0 -92
- package/src/model/textproxy.js +0 -202
- package/src/model/treewalker.js +0 -313
- package/src/model/typecheckable.js +0 -16
- package/src/model/utils/autoparagraphing.js +0 -63
- package/src/model/utils/deletecontent.js +0 -509
- package/src/model/utils/getselectedcontent.js +0 -126
- package/src/model/utils/insertcontent.js +0 -750
- package/src/model/utils/insertobject.js +0 -135
- package/src/model/utils/modifyselection.js +0 -187
- package/src/model/utils/selection-post-fixer.js +0 -264
- package/src/model/writer.js +0 -1318
- package/src/view/attributeelement.js +0 -220
- package/src/view/containerelement.js +0 -91
- package/src/view/datatransfer.js +0 -106
- package/src/view/document.js +0 -139
- package/src/view/documentfragment.js +0 -251
- package/src/view/documentselection.js +0 -270
- package/src/view/domconverter.js +0 -1661
- package/src/view/downcastwriter.js +0 -1589
- package/src/view/editableelement.js +0 -74
- package/src/view/element.js +0 -1053
- package/src/view/elementdefinition.js +0 -5
- package/src/view/emptyelement.js +0 -83
- package/src/view/filler.js +0 -161
- package/src/view/item.js +0 -5
- package/src/view/matcher.js +0 -437
- package/src/view/node.js +0 -238
- package/src/view/observer/arrowkeysobserver.js +0 -40
- package/src/view/observer/bubblingemittermixin.js +0 -215
- package/src/view/observer/bubblingeventinfo.js +0 -49
- package/src/view/observer/clickobserver.js +0 -26
- package/src/view/observer/compositionobserver.js +0 -64
- package/src/view/observer/domeventdata.js +0 -63
- package/src/view/observer/domeventobserver.js +0 -81
- package/src/view/observer/fakeselectionobserver.js +0 -95
- package/src/view/observer/focusobserver.js +0 -166
- package/src/view/observer/inputobserver.js +0 -236
- package/src/view/observer/keyobserver.js +0 -36
- package/src/view/observer/mouseobserver.js +0 -26
- package/src/view/observer/mutationobserver.js +0 -219
- package/src/view/observer/observer.js +0 -92
- package/src/view/observer/pointerobserver.js +0 -26
- package/src/view/observer/selectionobserver.js +0 -318
- package/src/view/observer/tabobserver.js +0 -42
- package/src/view/observer/touchobserver.js +0 -26
- package/src/view/placeholder.js +0 -285
- package/src/view/position.js +0 -341
- package/src/view/range.js +0 -451
- package/src/view/rawelement.js +0 -115
- package/src/view/renderer.js +0 -1148
- package/src/view/rooteditableelement.js +0 -78
- package/src/view/selection.js +0 -594
- package/src/view/styles/background.d.ts +0 -33
- package/src/view/styles/background.js +0 -74
- package/src/view/styles/border.js +0 -316
- package/src/view/styles/margin.js +0 -34
- package/src/view/styles/padding.js +0 -34
- package/src/view/styles/utils.js +0 -219
- package/src/view/stylesmap.js +0 -941
- package/src/view/text.js +0 -110
- package/src/view/textproxy.js +0 -136
- package/src/view/tokenlist.js +0 -194
- package/src/view/treewalker.js +0 -389
- package/src/view/typecheckable.js +0 -19
- package/src/view/uielement.js +0 -194
- package/src/view/upcastwriter.js +0 -363
- package/src/view/view.js +0 -579
- package/theme/placeholder.css +0 -36
- package/theme/renderer.css +0 -9
- /package/{src → dist}/controller/datacontroller.d.ts +0 -0
- /package/{src → dist}/controller/editingcontroller.d.ts +0 -0
- /package/{src → dist}/conversion/conversion.d.ts +0 -0
- /package/{src → dist}/conversion/conversionhelpers.d.ts +0 -0
- /package/{src → dist}/conversion/downcastdispatcher.d.ts +0 -0
- /package/{src → dist}/conversion/downcasthelpers.d.ts +0 -0
- /package/{src → dist}/conversion/mapper.d.ts +0 -0
- /package/{src → dist}/conversion/modelconsumable.d.ts +0 -0
- /package/{src → dist}/conversion/upcastdispatcher.d.ts +0 -0
- /package/{src → dist}/conversion/upcasthelpers.d.ts +0 -0
- /package/{src → dist}/conversion/viewconsumable.d.ts +0 -0
- /package/{src → dist}/dataprocessor/basichtmlwriter.d.ts +0 -0
- /package/{src → dist}/dataprocessor/dataprocessor.d.ts +0 -0
- /package/{src → dist}/dataprocessor/htmldataprocessor.d.ts +0 -0
- /package/{src → dist}/dataprocessor/htmlwriter.d.ts +0 -0
- /package/{src → dist}/dataprocessor/xmldataprocessor.d.ts +0 -0
- /package/{src → dist}/dev-utils/model.d.ts +0 -0
- /package/{src → dist}/dev-utils/operationreplayer.d.ts +0 -0
- /package/{src → dist}/dev-utils/utils.d.ts +0 -0
- /package/{src → dist}/dev-utils/view.d.ts +0 -0
- /package/{src → dist}/legacyerrors.d.ts +0 -0
- /package/{src → dist}/model/batch.d.ts +0 -0
- /package/{src → dist}/model/differ.d.ts +0 -0
- /package/{src → dist}/model/document.d.ts +0 -0
- /package/{src → dist}/model/documentfragment.d.ts +0 -0
- /package/{src → dist}/model/documentselection.d.ts +0 -0
- /package/{src → dist}/model/element.d.ts +0 -0
- /package/{src → dist}/model/history.d.ts +0 -0
- /package/{src → dist}/model/item.d.ts +0 -0
- /package/{src → dist}/model/liveposition.d.ts +0 -0
- /package/{src → dist}/model/liverange.d.ts +0 -0
- /package/{src → dist}/model/markercollection.d.ts +0 -0
- /package/{src → dist}/model/node.d.ts +0 -0
- /package/{src → dist}/model/nodelist.d.ts +0 -0
- /package/{src → dist}/model/operation/attributeoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/detachoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/insertoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/markeroperation.d.ts +0 -0
- /package/{src → dist}/model/operation/mergeoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/moveoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/nooperation.d.ts +0 -0
- /package/{src → dist}/model/operation/operation.d.ts +0 -0
- /package/{src → dist}/model/operation/operationfactory.d.ts +0 -0
- /package/{src → dist}/model/operation/renameoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/rootattributeoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/rootoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/splitoperation.d.ts +0 -0
- /package/{src → dist}/model/operation/transform.d.ts +0 -0
- /package/{src → dist}/model/operation/utils.d.ts +0 -0
- /package/{src → dist}/model/position.d.ts +0 -0
- /package/{src → dist}/model/range.d.ts +0 -0
- /package/{src → dist}/model/rootelement.d.ts +0 -0
- /package/{src → dist}/model/schema.d.ts +0 -0
- /package/{src → dist}/model/text.d.ts +0 -0
- /package/{src → dist}/model/textproxy.d.ts +0 -0
- /package/{src → dist}/model/treewalker.d.ts +0 -0
- /package/{src → dist}/model/typecheckable.d.ts +0 -0
- /package/{src → dist}/model/utils/autoparagraphing.d.ts +0 -0
- /package/{src → dist}/model/utils/deletecontent.d.ts +0 -0
- /package/{src → dist}/model/utils/getselectedcontent.d.ts +0 -0
- /package/{src → dist}/model/utils/insertcontent.d.ts +0 -0
- /package/{src → dist}/model/utils/insertobject.d.ts +0 -0
- /package/{src → dist}/model/utils/modifyselection.d.ts +0 -0
- /package/{src → dist}/model/utils/selection-post-fixer.d.ts +0 -0
- /package/{src → dist}/model/writer.d.ts +0 -0
- /package/{src → dist}/view/attributeelement.d.ts +0 -0
- /package/{src → dist}/view/containerelement.d.ts +0 -0
- /package/{src → dist}/view/datatransfer.d.ts +0 -0
- /package/{src → dist}/view/document.d.ts +0 -0
- /package/{src → dist}/view/documentfragment.d.ts +0 -0
- /package/{src → dist}/view/documentselection.d.ts +0 -0
- /package/{src → dist}/view/domconverter.d.ts +0 -0
- /package/{src → dist}/view/editableelement.d.ts +0 -0
- /package/{src → dist}/view/elementdefinition.d.ts +0 -0
- /package/{src → dist}/view/emptyelement.d.ts +0 -0
- /package/{src → dist}/view/filler.d.ts +0 -0
- /package/{src → dist}/view/item.d.ts +0 -0
- /package/{src → dist}/view/node.d.ts +0 -0
- /package/{src → dist}/view/observer/arrowkeysobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/bubblingemittermixin.d.ts +0 -0
- /package/{src → dist}/view/observer/bubblingeventinfo.d.ts +0 -0
- /package/{src → dist}/view/observer/clickobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/compositionobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/domeventdata.d.ts +0 -0
- /package/{src → dist}/view/observer/domeventobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/fakeselectionobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/focusobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/inputobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/keyobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/mouseobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/mutationobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/observer.d.ts +0 -0
- /package/{src → dist}/view/observer/pointerobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/selectionobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/tabobserver.d.ts +0 -0
- /package/{src → dist}/view/observer/touchobserver.d.ts +0 -0
- /package/{src → dist}/view/placeholder.d.ts +0 -0
- /package/{src → dist}/view/position.d.ts +0 -0
- /package/{src → dist}/view/range.d.ts +0 -0
- /package/{src → dist}/view/rawelement.d.ts +0 -0
- /package/{src → dist}/view/renderer.d.ts +0 -0
- /package/{src → dist}/view/rooteditableelement.d.ts +0 -0
- /package/{src → dist}/view/selection.d.ts +0 -0
- /package/{src → dist}/view/stylesmap.d.ts +0 -0
- /package/{src → dist}/view/text.d.ts +0 -0
- /package/{src → dist}/view/textproxy.d.ts +0 -0
- /package/{src → dist}/view/tokenlist.d.ts +0 -0
- /package/{src → dist}/view/treewalker.d.ts +0 -0
- /package/{src → dist}/view/typecheckable.d.ts +0 -0
- /package/{src → dist}/view/uielement.d.ts +0 -0
- /package/{src → dist}/view/upcastwriter.d.ts +0 -0
- /package/{src → dist}/view/view.d.ts +0 -0
|
@@ -1,470 +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/upcastdispatcher
|
|
7
|
-
*/
|
|
8
|
-
import { ViewConsumable } from './viewconsumable.js';
|
|
9
|
-
import { ModelRange } from '../model/range.js';
|
|
10
|
-
import { ModelPosition } from '../model/position.js';
|
|
11
|
-
import { ModelSchemaContext } from '../model/schema.js'; // eslint-disable-line no-duplicate-imports
|
|
12
|
-
import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing.js';
|
|
13
|
-
import { CKEditorError, EmitterMixin } from '@ckeditor/ckeditor5-utils';
|
|
14
|
-
/**
|
|
15
|
-
* Upcast dispatcher is a central point of the view-to-model conversion, which is a process of
|
|
16
|
-
* converting a given {@link module:engine/view/documentfragment~ViewDocumentFragment view document fragment} or
|
|
17
|
-
* {@link module:engine/view/element~ViewElement view element} into a correct model structure.
|
|
18
|
-
*
|
|
19
|
-
* During the conversion process, the dispatcher fires events for all {@link module:engine/view/node~ViewNode view nodes}
|
|
20
|
-
* from the converted view document fragment.
|
|
21
|
-
* Special callbacks called "converters" should listen to these events in order to convert the view nodes.
|
|
22
|
-
*
|
|
23
|
-
* The second parameter of the callback is the `data` object with the following properties:
|
|
24
|
-
*
|
|
25
|
-
* * `data.viewItem` contains a {@link module:engine/view/node~ViewNode view node} or a
|
|
26
|
-
* {@link module:engine/view/documentfragment~ViewDocumentFragment view document fragment}
|
|
27
|
-
* that is converted at the moment and might be handled by the callback.
|
|
28
|
-
* * `data.modelRange` is used to point to the result
|
|
29
|
-
* of the current conversion (e.g. the element that is being inserted)
|
|
30
|
-
* and is always a {@link module:engine/model/range~ModelRange} when the conversion succeeds.
|
|
31
|
-
* * `data.modelCursor` is a {@link module:engine/model/position~ModelPosition position} on which the converter should insert
|
|
32
|
-
* the newly created items.
|
|
33
|
-
*
|
|
34
|
-
* The third parameter of the callback is an instance of {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi}
|
|
35
|
-
* which provides additional tools for converters.
|
|
36
|
-
*
|
|
37
|
-
* You can read more about conversion in the {@glink framework/deep-dive/conversion/upcast Upcast conversion} guide.
|
|
38
|
-
*
|
|
39
|
-
* Examples of event-based converters:
|
|
40
|
-
*
|
|
41
|
-
* ```ts
|
|
42
|
-
* // A converter for links (<a>).
|
|
43
|
-
* editor.data.upcastDispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
|
|
44
|
-
* if ( conversionApi.consumable.consume( data.viewItem, { name: true, attributes: [ 'href' ] } ) ) {
|
|
45
|
-
* // The <a> element is inline and is represented by an attribute in the model.
|
|
46
|
-
* // This is why you need to convert only children.
|
|
47
|
-
* const { modelRange } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
|
|
48
|
-
*
|
|
49
|
-
* for ( let item of modelRange.getItems() ) {
|
|
50
|
-
* if ( conversionApi.schema.checkAttribute( item, 'linkHref' ) ) {
|
|
51
|
-
* conversionApi.writer.setAttribute( 'linkHref', data.viewItem.getAttribute( 'href' ), item );
|
|
52
|
-
* }
|
|
53
|
-
* }
|
|
54
|
-
* }
|
|
55
|
-
* } );
|
|
56
|
-
*
|
|
57
|
-
* // Convert <p> element's font-size style.
|
|
58
|
-
* // Note: You should use a low-priority observer in order to ensure that
|
|
59
|
-
* // it is executed after the element-to-element converter.
|
|
60
|
-
* editor.data.upcastDispatcher.on( 'element:p', ( evt, data, conversionApi ) => {
|
|
61
|
-
* const { consumable, schema, writer } = conversionApi;
|
|
62
|
-
*
|
|
63
|
-
* if ( !consumable.consume( data.viewItem, { style: 'font-size' } ) ) {
|
|
64
|
-
* return;
|
|
65
|
-
* }
|
|
66
|
-
*
|
|
67
|
-
* const fontSize = data.viewItem.getStyle( 'font-size' );
|
|
68
|
-
*
|
|
69
|
-
* // Do not go for the model element after data.modelCursor because it might happen
|
|
70
|
-
* // that a single view element was converted to multiple model elements. Get all of them.
|
|
71
|
-
* for ( const item of data.modelRange.getItems( { shallow: true } ) ) {
|
|
72
|
-
* if ( schema.checkAttribute( item, 'fontSize' ) ) {
|
|
73
|
-
* writer.setAttribute( 'fontSize', fontSize, item );
|
|
74
|
-
* }
|
|
75
|
-
* }
|
|
76
|
-
* }, { priority: 'low' } );
|
|
77
|
-
*
|
|
78
|
-
* // Convert all elements which have no custom converter into a paragraph (autoparagraphing).
|
|
79
|
-
* editor.data.upcastDispatcher.on( 'element', ( evt, data, conversionApi ) => {
|
|
80
|
-
* // Check if an element can be converted.
|
|
81
|
-
* if ( !conversionApi.consumable.test( data.viewItem, { name: data.viewItem.name } ) ) {
|
|
82
|
-
* // When an element is already consumed by higher priority converters, do nothing.
|
|
83
|
-
* return;
|
|
84
|
-
* }
|
|
85
|
-
*
|
|
86
|
-
* const paragraph = conversionApi.writer.createElement( 'paragraph' );
|
|
87
|
-
*
|
|
88
|
-
* // Try to safely insert a paragraph at the model cursor - it will find an allowed parent for the current element.
|
|
89
|
-
* if ( !conversionApi.safeInsert( paragraph, data.modelCursor ) ) {
|
|
90
|
-
* // When an element was not inserted, it means that you cannot insert a paragraph at this position.
|
|
91
|
-
* return;
|
|
92
|
-
* }
|
|
93
|
-
*
|
|
94
|
-
* // Consume the inserted element.
|
|
95
|
-
* conversionApi.consumable.consume( data.viewItem, { name: data.viewItem.name } ) );
|
|
96
|
-
*
|
|
97
|
-
* // Convert the children to a paragraph.
|
|
98
|
-
* const { modelRange } = conversionApi.convertChildren( data.viewItem, paragraph ) );
|
|
99
|
-
*
|
|
100
|
-
* // Update `modelRange` and `modelCursor` in the `data` as a conversion result.
|
|
101
|
-
* conversionApi.updateConversionResult( paragraph, data );
|
|
102
|
-
* }, { priority: 'low' } );
|
|
103
|
-
* ```
|
|
104
|
-
*
|
|
105
|
-
* @fires viewCleanup
|
|
106
|
-
* @fires element
|
|
107
|
-
* @fires text
|
|
108
|
-
* @fires documentFragment
|
|
109
|
-
*/
|
|
110
|
-
export class UpcastDispatcher extends /* #__PURE__ */ EmitterMixin() {
|
|
111
|
-
/**
|
|
112
|
-
* An interface passed by the dispatcher to the event callbacks.
|
|
113
|
-
*/
|
|
114
|
-
conversionApi;
|
|
115
|
-
/**
|
|
116
|
-
* The list of elements that were created during splitting.
|
|
117
|
-
*
|
|
118
|
-
* After the conversion process, the list is cleared.
|
|
119
|
-
*/
|
|
120
|
-
_splitParts = new Map();
|
|
121
|
-
/**
|
|
122
|
-
* The list of cursor parent elements that were created during splitting.
|
|
123
|
-
*
|
|
124
|
-
* After the conversion process the list is cleared.
|
|
125
|
-
*/
|
|
126
|
-
_cursorParents = new Map();
|
|
127
|
-
/**
|
|
128
|
-
* The position in the temporary structure where the converted content is inserted. The structure reflects the context of
|
|
129
|
-
* the target position where the content will be inserted. This property is built based on the context parameter of the
|
|
130
|
-
* convert method.
|
|
131
|
-
*/
|
|
132
|
-
_modelCursor = null;
|
|
133
|
-
/**
|
|
134
|
-
* The list of elements that were created during the splitting but should not get removed on conversion end even if they are empty.
|
|
135
|
-
*
|
|
136
|
-
* The list is cleared after the conversion process.
|
|
137
|
-
*/
|
|
138
|
-
_emptyElementsToKeep = new Set();
|
|
139
|
-
/**
|
|
140
|
-
* Creates an upcast dispatcher that operates using the passed API.
|
|
141
|
-
*
|
|
142
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi
|
|
143
|
-
* @param conversionApi Additional properties for an interface that will be passed to events fired
|
|
144
|
-
* by the upcast dispatcher.
|
|
145
|
-
*/
|
|
146
|
-
constructor(conversionApi) {
|
|
147
|
-
super();
|
|
148
|
-
this.conversionApi = {
|
|
149
|
-
...conversionApi,
|
|
150
|
-
consumable: null,
|
|
151
|
-
writer: null,
|
|
152
|
-
store: null,
|
|
153
|
-
convertItem: (viewItem, modelCursor) => this._convertItem(viewItem, modelCursor),
|
|
154
|
-
convertChildren: (viewElement, positionOrElement) => this._convertChildren(viewElement, positionOrElement),
|
|
155
|
-
safeInsert: (modelNode, position) => this._safeInsert(modelNode, position),
|
|
156
|
-
updateConversionResult: (modelElement, data) => this._updateConversionResult(modelElement, data),
|
|
157
|
-
// Advanced API - use only if custom position handling is needed.
|
|
158
|
-
splitToAllowedParent: (modelNode, modelCursor) => this._splitToAllowedParent(modelNode, modelCursor),
|
|
159
|
-
getSplitParts: modelElement => this._getSplitParts(modelElement),
|
|
160
|
-
keepEmptyElement: modelElement => this._keepEmptyElement(modelElement)
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Starts the conversion process. The entry point for the conversion.
|
|
165
|
-
*
|
|
166
|
-
* @fires element
|
|
167
|
-
* @fires text
|
|
168
|
-
* @fires documentFragment
|
|
169
|
-
* @param viewElement The part of the view to be converted.
|
|
170
|
-
* @param writer An instance of the model writer.
|
|
171
|
-
* @param context Elements will be converted according to this context.
|
|
172
|
-
* @returns Model data that is the result of the conversion process
|
|
173
|
-
* wrapped in `DocumentFragment`. Converted marker elements will be set as the document fragment's
|
|
174
|
-
* {@link module:engine/model/documentfragment~ModelDocumentFragment#markers static markers map}.
|
|
175
|
-
*/
|
|
176
|
-
convert(viewElement, writer, context = ['$root']) {
|
|
177
|
-
this.fire('viewCleanup', viewElement);
|
|
178
|
-
// Create context tree and set position in the top element.
|
|
179
|
-
// Items will be converted according to this position.
|
|
180
|
-
this._modelCursor = createContextTree(context, writer);
|
|
181
|
-
// Store writer in conversion as a conversion API
|
|
182
|
-
// to be sure that conversion process will use the same batch.
|
|
183
|
-
this.conversionApi.writer = writer;
|
|
184
|
-
// Create consumable values list for conversion process.
|
|
185
|
-
this.conversionApi.consumable = ViewConsumable.createFrom(viewElement);
|
|
186
|
-
// Custom data stored by converter for conversion process.
|
|
187
|
-
this.conversionApi.store = {};
|
|
188
|
-
// Do the conversion.
|
|
189
|
-
const { modelRange } = this._convertItem(viewElement, this._modelCursor);
|
|
190
|
-
// Conversion result is always a document fragment so let's create it.
|
|
191
|
-
const documentFragment = writer.createDocumentFragment();
|
|
192
|
-
// When there is a conversion result.
|
|
193
|
-
if (modelRange) {
|
|
194
|
-
// Remove all empty elements that were created while splitting.
|
|
195
|
-
this._removeEmptyElements();
|
|
196
|
-
// Move all items that were converted in context tree to the document fragment.
|
|
197
|
-
const parent = this._modelCursor.parent;
|
|
198
|
-
const children = parent._removeChildren(0, parent.childCount);
|
|
199
|
-
documentFragment._insertChild(0, children);
|
|
200
|
-
// Extract temporary markers elements from model and set as static markers collection.
|
|
201
|
-
documentFragment.markers = extractMarkersFromModelFragment(documentFragment, writer);
|
|
202
|
-
}
|
|
203
|
-
// Clear context position.
|
|
204
|
-
this._modelCursor = null;
|
|
205
|
-
// Clear split elements & parents lists.
|
|
206
|
-
this._splitParts.clear();
|
|
207
|
-
this._cursorParents.clear();
|
|
208
|
-
this._emptyElementsToKeep.clear();
|
|
209
|
-
// Clear conversion API.
|
|
210
|
-
this.conversionApi.writer = null;
|
|
211
|
-
this.conversionApi.store = null;
|
|
212
|
-
// Return fragment as conversion result.
|
|
213
|
-
return documentFragment;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#convertItem
|
|
217
|
-
*/
|
|
218
|
-
_convertItem(viewItem, modelCursor) {
|
|
219
|
-
const data = { viewItem, modelCursor, modelRange: null };
|
|
220
|
-
if (viewItem.is('element')) {
|
|
221
|
-
this.fire(`element:${viewItem.name}`, data, this.conversionApi);
|
|
222
|
-
}
|
|
223
|
-
else if (viewItem.is('$text')) {
|
|
224
|
-
this.fire('text', data, this.conversionApi);
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
this.fire('documentFragment', data, this.conversionApi);
|
|
228
|
-
}
|
|
229
|
-
// Handle incorrect conversion result.
|
|
230
|
-
if (data.modelRange && !(data.modelRange instanceof ModelRange)) {
|
|
231
|
-
/**
|
|
232
|
-
* Incorrect conversion result was dropped.
|
|
233
|
-
*
|
|
234
|
-
* {@link module:engine/model/range~ModelRange Model range} should be a conversion result.
|
|
235
|
-
*
|
|
236
|
-
* @error view-conversion-dispatcher-incorrect-result
|
|
237
|
-
*/
|
|
238
|
-
throw new CKEditorError('view-conversion-dispatcher-incorrect-result', this);
|
|
239
|
-
}
|
|
240
|
-
return { modelRange: data.modelRange, modelCursor: data.modelCursor };
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#convertChildren
|
|
244
|
-
*/
|
|
245
|
-
_convertChildren(viewItem, elementOrModelCursor) {
|
|
246
|
-
let nextModelCursor = elementOrModelCursor.is('position') ?
|
|
247
|
-
elementOrModelCursor : ModelPosition._createAt(elementOrModelCursor, 0);
|
|
248
|
-
const modelRange = new ModelRange(nextModelCursor);
|
|
249
|
-
for (const viewChild of Array.from(viewItem.getChildren())) {
|
|
250
|
-
const result = this._convertItem(viewChild, nextModelCursor);
|
|
251
|
-
if (result.modelRange instanceof ModelRange) {
|
|
252
|
-
modelRange.end = result.modelRange.end;
|
|
253
|
-
nextModelCursor = result.modelCursor;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return { modelRange, modelCursor: nextModelCursor };
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#safeInsert
|
|
260
|
-
*/
|
|
261
|
-
_safeInsert(modelNode, position) {
|
|
262
|
-
// Find allowed parent for element that we are going to insert.
|
|
263
|
-
// If current parent does not allow to insert element but one of the ancestors does
|
|
264
|
-
// then split nodes to allowed parent.
|
|
265
|
-
const splitResult = this._splitToAllowedParent(modelNode, position);
|
|
266
|
-
// When there is no split result it means that we can't insert element to model tree, so let's skip it.
|
|
267
|
-
if (!splitResult) {
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
// Insert element on allowed position.
|
|
271
|
-
this.conversionApi.writer.insert(modelNode, splitResult.position);
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#updateConversionResult
|
|
276
|
-
*/
|
|
277
|
-
_updateConversionResult(modelElement, data) {
|
|
278
|
-
const parts = this._getSplitParts(modelElement);
|
|
279
|
-
const writer = this.conversionApi.writer;
|
|
280
|
-
// Set conversion result range - only if not set already.
|
|
281
|
-
if (!data.modelRange) {
|
|
282
|
-
data.modelRange = writer.createRange(writer.createPositionBefore(modelElement), writer.createPositionAfter(parts[parts.length - 1]));
|
|
283
|
-
}
|
|
284
|
-
const savedCursorParent = this._cursorParents.get(modelElement);
|
|
285
|
-
// Now we need to check where the `modelCursor` should be.
|
|
286
|
-
if (savedCursorParent) {
|
|
287
|
-
// If we split parent to insert our element then we want to continue conversion in the new part of the split parent.
|
|
288
|
-
//
|
|
289
|
-
// before: <allowed><notAllowed>foo[]</notAllowed></allowed>
|
|
290
|
-
// after: <allowed><notAllowed>foo</notAllowed> <converted></converted> <notAllowed>[]</notAllowed></allowed>
|
|
291
|
-
data.modelCursor = writer.createPositionAt(savedCursorParent, 0);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
// Otherwise just continue after inserted element.
|
|
295
|
-
data.modelCursor = data.modelRange.end;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
|
|
300
|
-
*/
|
|
301
|
-
_splitToAllowedParent(node, modelCursor) {
|
|
302
|
-
const { schema, writer } = this.conversionApi;
|
|
303
|
-
// Try to find allowed parent.
|
|
304
|
-
let allowedParent = schema.findAllowedParent(modelCursor, node);
|
|
305
|
-
if (allowedParent) {
|
|
306
|
-
// When current position parent allows to insert node then return this position.
|
|
307
|
-
if (allowedParent === modelCursor.parent) {
|
|
308
|
-
return { position: modelCursor };
|
|
309
|
-
}
|
|
310
|
-
// When allowed parent is in context tree (it's outside the converted tree).
|
|
311
|
-
if (this._modelCursor.parent.getAncestors().includes(allowedParent)) {
|
|
312
|
-
allowedParent = null;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (!allowedParent) {
|
|
316
|
-
// Check if the node wrapped with a paragraph would be accepted by the schema.
|
|
317
|
-
if (!isParagraphable(modelCursor, node, schema)) {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
return {
|
|
321
|
-
position: wrapInParagraph(modelCursor, writer)
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
// Split element to allowed parent.
|
|
325
|
-
const splitResult = this.conversionApi.writer.split(modelCursor, allowedParent);
|
|
326
|
-
// Using the range returned by `model.Writer#split`, we will pair original elements with their split parts.
|
|
327
|
-
//
|
|
328
|
-
// The range returned from the writer spans "over the split" or, precisely saying, from the end of the original element (the one
|
|
329
|
-
// that got split) to the beginning of the other part of that element:
|
|
330
|
-
//
|
|
331
|
-
// <limit><a><b><c>X[]Y</c></b><a></limit> ->
|
|
332
|
-
// <limit><a><b><c>X[</c></b></a><a><b><c>]Y</c></b></a>
|
|
333
|
-
//
|
|
334
|
-
// After the split there cannot be any full node between the positions in `splitRange`. The positions are touching.
|
|
335
|
-
// Also, because of how splitting works, it is easy to notice, that "closing tags" are in the reverse order than "opening tags".
|
|
336
|
-
// Also, since we split all those elements, each of them has to have the other part.
|
|
337
|
-
//
|
|
338
|
-
// With those observations in mind, we will pair the original elements with their split parts by saving "closing tags" and matching
|
|
339
|
-
// them with "opening tags" in the reverse order. For that we can use a stack.
|
|
340
|
-
const stack = [];
|
|
341
|
-
for (const treeWalkerValue of splitResult.range.getWalker()) {
|
|
342
|
-
if (treeWalkerValue.type == 'elementEnd') {
|
|
343
|
-
stack.push(treeWalkerValue.item);
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
// There should not be any text nodes after the element is split, so the only other value is `elementStart`.
|
|
347
|
-
const originalPart = stack.pop();
|
|
348
|
-
const splitPart = treeWalkerValue.item;
|
|
349
|
-
this._registerSplitPair(originalPart, splitPart);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
const cursorParent = splitResult.range.end.parent;
|
|
353
|
-
this._cursorParents.set(node, cursorParent);
|
|
354
|
-
return {
|
|
355
|
-
position: splitResult.position,
|
|
356
|
-
cursorParent
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Registers that a `splitPart` element is a split part of the `originalPart` element.
|
|
361
|
-
*
|
|
362
|
-
* The data set by this method is used by {@link #_getSplitParts} and {@link #_removeEmptyElements}.
|
|
363
|
-
*/
|
|
364
|
-
_registerSplitPair(originalPart, splitPart) {
|
|
365
|
-
if (!this._splitParts.has(originalPart)) {
|
|
366
|
-
this._splitParts.set(originalPart, [originalPart]);
|
|
367
|
-
}
|
|
368
|
-
const list = this._splitParts.get(originalPart);
|
|
369
|
-
this._splitParts.set(splitPart, list);
|
|
370
|
-
list.push(splitPart);
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#getSplitParts
|
|
374
|
-
*/
|
|
375
|
-
_getSplitParts(element) {
|
|
376
|
-
let parts;
|
|
377
|
-
if (!this._splitParts.has(element)) {
|
|
378
|
-
parts = [element];
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
parts = this._splitParts.get(element);
|
|
382
|
-
}
|
|
383
|
-
return parts;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Mark an element that were created during the splitting to not get removed on conversion end even if it is empty.
|
|
387
|
-
*/
|
|
388
|
-
_keepEmptyElement(element) {
|
|
389
|
-
this._emptyElementsToKeep.add(element);
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Checks if there are any empty elements created while splitting and removes them.
|
|
393
|
-
*
|
|
394
|
-
* This method works recursively to re-check empty elements again after at least one element was removed in the initial call,
|
|
395
|
-
* as some elements might have become empty after other empty elements were removed from them.
|
|
396
|
-
*/
|
|
397
|
-
_removeEmptyElements() {
|
|
398
|
-
// For every parent, prepare an array of children (empty elements) to remove from it.
|
|
399
|
-
// Then, in next step, we will remove all children together, which is faster than removing them one by one.
|
|
400
|
-
const toRemove = new Map();
|
|
401
|
-
for (const element of this._splitParts.keys()) {
|
|
402
|
-
if (element.isEmpty && !this._emptyElementsToKeep.has(element)) {
|
|
403
|
-
const children = toRemove.get(element.parent) || [];
|
|
404
|
-
children.push(element);
|
|
405
|
-
this._splitParts.delete(element);
|
|
406
|
-
toRemove.set(element.parent, children);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
for (const [parent, children] of toRemove) {
|
|
410
|
-
parent._removeChildrenArray(children);
|
|
411
|
-
}
|
|
412
|
-
if (toRemove.size) {
|
|
413
|
-
this._removeEmptyElements();
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Traverses given model item and searches elements which marks marker range. Found element is removed from
|
|
419
|
-
* ModelDocumentFragment but path of this element is stored in a Map which is then returned.
|
|
420
|
-
*
|
|
421
|
-
* @param modelItem Fragment of model.
|
|
422
|
-
* @returns List of static markers.
|
|
423
|
-
*/
|
|
424
|
-
function extractMarkersFromModelFragment(modelItem, writer) {
|
|
425
|
-
const markerElements = new Set();
|
|
426
|
-
const markers = new Map();
|
|
427
|
-
// Create ModelTreeWalker.
|
|
428
|
-
const range = ModelRange._createIn(modelItem).getItems();
|
|
429
|
-
// Walk through ModelDocumentFragment and collect marker elements.
|
|
430
|
-
for (const item of range) {
|
|
431
|
-
// Check if current element is a marker.
|
|
432
|
-
if (item.is('element', '$marker')) {
|
|
433
|
-
markerElements.add(item);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
// Walk through collected marker elements store its path and remove its from the ModelDocumentFragment.
|
|
437
|
-
for (const markerElement of markerElements) {
|
|
438
|
-
const markerName = markerElement.getAttribute('data-name');
|
|
439
|
-
const currentPosition = writer.createPositionBefore(markerElement);
|
|
440
|
-
// When marker of given name is not stored it means that we have found the beginning of the range.
|
|
441
|
-
if (!markers.has(markerName)) {
|
|
442
|
-
markers.set(markerName, new ModelRange(currentPosition.clone()));
|
|
443
|
-
// Otherwise is means that we have found end of the marker range.
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
markers.get(markerName).end = currentPosition.clone();
|
|
447
|
-
}
|
|
448
|
-
// Remove marker element from ModelDocumentFragment.
|
|
449
|
-
writer.remove(markerElement);
|
|
450
|
-
}
|
|
451
|
-
return markers;
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Creates model fragment according to given context and returns position in the bottom (the deepest) element.
|
|
455
|
-
*/
|
|
456
|
-
function createContextTree(contextDefinition, writer) {
|
|
457
|
-
let position;
|
|
458
|
-
for (const item of new ModelSchemaContext(contextDefinition)) {
|
|
459
|
-
const attributes = {};
|
|
460
|
-
for (const key of item.getAttributeKeys()) {
|
|
461
|
-
attributes[key] = item.getAttribute(key);
|
|
462
|
-
}
|
|
463
|
-
const current = writer.createElement(item.name, attributes);
|
|
464
|
-
if (position) {
|
|
465
|
-
writer.insert(current, position);
|
|
466
|
-
}
|
|
467
|
-
position = ModelPosition._createAt(current, 0);
|
|
468
|
-
}
|
|
469
|
-
return position;
|
|
470
|
-
}
|