@ckeditor/ckeditor5-engine 33.0.0 → 34.2.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 +2 -2
- package/README.md +2 -1
- package/package.json +22 -22
- package/src/conversion/downcasthelpers.js +90 -19
- package/src/conversion/mapper.js +2 -2
- package/src/conversion/upcastdispatcher.js +31 -1
- package/src/conversion/upcasthelpers.js +23 -1
- package/src/index.js +11 -0
- package/src/model/differ.js +33 -15
- package/src/model/document.js +20 -28
- package/src/model/history.js +160 -22
- package/src/model/model.js +120 -3
- package/src/model/schema.js +79 -10
- package/src/model/treewalker.js +2 -3
- package/src/model/utils/deletecontent.js +15 -2
- package/src/model/utils/findoptimalinsertionrange.js +68 -0
- package/src/model/utils/insertobject.js +173 -0
- package/src/view/attributeelement.js +0 -10
- package/src/view/domconverter.js +27 -4
- package/src/view/downcastwriter.js +5 -49
- package/src/view/element.js +2 -26
- package/src/view/emptyelement.js +0 -3
- package/src/view/filler.js +1 -1
- package/src/view/matcher.js +2 -2
- package/src/view/observer/clickobserver.js +0 -1
- package/src/view/observer/inputobserver.js +1 -1
- package/src/view/observer/tabobserver.js +68 -0
- package/src/view/placeholder.js +1 -1
- package/src/view/rawelement.js +0 -3
- package/src/view/renderer.js +4 -0
- package/src/view/uielement.js +0 -3
- package/src/view/view.js +4 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module engine/model/utils/insertobject
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import first from '@ckeditor/ckeditor5-utils/src/first';
|
|
11
|
+
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
12
|
+
|
|
13
|
+
import { findOptimalInsertionRange } from './findoptimalinsertionrange';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Inserts an {@glink framework/guides/deep-dive/schema#object-elements object element} at a specific position in the editor content.
|
|
17
|
+
*
|
|
18
|
+
* **Note:** Use {@link module:engine/model/model~Model#insertObject} instead of this function.
|
|
19
|
+
* This function is only exposed to be reusable in algorithms which change the {@link module:engine/model/model~Model#insertObject}
|
|
20
|
+
* method's behavior.
|
|
21
|
+
*
|
|
22
|
+
* **Note**: For more documentation and examples, see {@link module:engine/model/model~Model#insertObject}.
|
|
23
|
+
*
|
|
24
|
+
* @param {module:engine/model/model~Model} model The model in context of which the insertion
|
|
25
|
+
* should be performed.
|
|
26
|
+
* @param {module:engine/model/element~Element} object An object to be inserted into the model document.
|
|
27
|
+
* @param {module:engine/model/selection~Selectable} [selectable=model.document.selection]
|
|
28
|
+
* A selectable where the content should be inserted. If not specified, the current
|
|
29
|
+
* {@link module:engine/model/document~Document#selection document selection} will be used instead.
|
|
30
|
+
* @param {Number|'before'|'end'|'after'|'on'|'in'} placeOrOffset Specifies the exact place or offset for the insertion to take place,
|
|
31
|
+
* relative to `selectable`.
|
|
32
|
+
* @param {Object} [options] Additional options.
|
|
33
|
+
* @param {'auto'|'before'|'after'} [options.findOptimalPosition] An option that, when set, adjusts the insertion position (relative to
|
|
34
|
+
* `selectable` and `placeOrOffset`) so that the content of `selectable` is not split upon insertion (a.k.a. non-destructive insertion).
|
|
35
|
+
* * When `'auto'`, the algorithm will decide whether to insert the object before or after `selectable` to avoid content splitting.
|
|
36
|
+
* * When `'before'`, the closest position before `selectable` will be used that will not result in content splitting.
|
|
37
|
+
* * When `'after'`, the closest position after `selectable` will be used that will not result in content splitting.
|
|
38
|
+
*
|
|
39
|
+
* Note that this option works only for block objects. Inline objects are inserted into text and do not split blocks.
|
|
40
|
+
* @param {'on'|'after'} [options.setSelection] An option that, when set, moves the
|
|
41
|
+
* {@link module:engine/model/document~Document#selection document selection} after inserting the object.
|
|
42
|
+
* * When `'on'`, the document selection will be set on the inserted object.
|
|
43
|
+
* * When `'after'`, the document selection will move to the closest text node after the inserted object. If there is no
|
|
44
|
+
* such text node, a paragraph will be created and the document selection will be moved inside it.
|
|
45
|
+
* @returns {module:engine/model/range~Range} A range which contains all the performed changes. This is a range that, if removed,
|
|
46
|
+
* would return the model to the state before the insertion. If no changes were preformed by `insertObject()`, returns a range collapsed
|
|
47
|
+
* at the insertion position.
|
|
48
|
+
*/
|
|
49
|
+
export default function insertObject( model, object, selectable, placeOrOffset, options = {} ) {
|
|
50
|
+
if ( !model.schema.isObject( object ) ) {
|
|
51
|
+
/**
|
|
52
|
+
* Tried to insert an element with {@link module:engine/model/utils/insertobject insertObject()} function
|
|
53
|
+
* that is not defined as an object in schema.
|
|
54
|
+
* See {@link module:engine/model/schema~SchemaItemDefinition#isObject `SchemaItemDefinition`}.
|
|
55
|
+
* If you want to insert content that is not an object you might want to use
|
|
56
|
+
* {@link module:engine/model/utils/insertcontent insertContent()} function.
|
|
57
|
+
* @error insertobject-element-not-an-object
|
|
58
|
+
*/
|
|
59
|
+
throw new CKEditorError( 'insertobject-element-not-an-object', model, { object } );
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Normalize selectable to a selection instance.
|
|
63
|
+
let originalSelection;
|
|
64
|
+
|
|
65
|
+
if ( !selectable ) {
|
|
66
|
+
originalSelection = model.document.selection;
|
|
67
|
+
} else if ( selectable.is( 'selection' ) ) {
|
|
68
|
+
originalSelection = selectable;
|
|
69
|
+
} else {
|
|
70
|
+
originalSelection = model.createSelection( selectable, placeOrOffset );
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Adjust the insertion selection.
|
|
74
|
+
let insertionSelection = originalSelection;
|
|
75
|
+
|
|
76
|
+
if ( options.findOptimalPosition && model.schema.isBlock( object ) ) {
|
|
77
|
+
insertionSelection = model.createSelection( findOptimalInsertionRange( originalSelection, model, options.findOptimalPosition ) );
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Collect attributes to be copied on the inserted object.
|
|
81
|
+
const firstSelectedBlock = first( originalSelection.getSelectedBlocks() );
|
|
82
|
+
const attributesToCopy = {};
|
|
83
|
+
|
|
84
|
+
if ( firstSelectedBlock ) {
|
|
85
|
+
Object.assign( attributesToCopy, model.schema.getAttributesWithProperty( firstSelectedBlock, 'copyOnReplace', true ) );
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return model.change( writer => {
|
|
89
|
+
// Remove the selected content to find out what the parent of the inserted object would be.
|
|
90
|
+
// It would be removed inside model.insertContent() anyway.
|
|
91
|
+
if ( !insertionSelection.isCollapsed ) {
|
|
92
|
+
model.deleteContent( insertionSelection, { doNotAutoparagraph: true } );
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let elementToInsert = object;
|
|
96
|
+
const insertionPositionParent = insertionSelection.anchor.parent;
|
|
97
|
+
|
|
98
|
+
// Autoparagraphing of an inline objects.
|
|
99
|
+
if (
|
|
100
|
+
!model.schema.checkChild( insertionPositionParent, object ) &&
|
|
101
|
+
model.schema.checkChild( insertionPositionParent, 'paragraph' ) &&
|
|
102
|
+
model.schema.checkChild( 'paragraph', object )
|
|
103
|
+
) {
|
|
104
|
+
elementToInsert = writer.createElement( 'paragraph' );
|
|
105
|
+
|
|
106
|
+
writer.insert( object, elementToInsert );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Apply attributes that are allowed on the inserted object (or paragraph if autoparagraphed).
|
|
110
|
+
model.schema.setAllowedAttributes( elementToInsert, attributesToCopy, writer );
|
|
111
|
+
|
|
112
|
+
// Insert the prepared content at the optionally adjusted selection.
|
|
113
|
+
const affectedRange = model.insertContent( elementToInsert, insertionSelection );
|
|
114
|
+
|
|
115
|
+
// Nothing got inserted.
|
|
116
|
+
if ( affectedRange.isCollapsed ) {
|
|
117
|
+
return affectedRange;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if ( options.setSelection ) {
|
|
121
|
+
updateSelection( writer, object, options.setSelection, attributesToCopy );
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return affectedRange;
|
|
125
|
+
} );
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Updates document selection based on given `place` parameter in relation to `contextElement` element.
|
|
129
|
+
//
|
|
130
|
+
// @private
|
|
131
|
+
// @param {module:engine/model/writer~Writer} writer An instance of the model writer.
|
|
132
|
+
// @param {module:engine/model/element~Element} contextElement An element to set the attributes on.
|
|
133
|
+
// @param {'on'|'after'} place The place where selection should be set in relation to the `contextElement` element.
|
|
134
|
+
// Value `on` will set selection on the passed `contextElement`. Value `after` will set selection after `contextElement`.
|
|
135
|
+
// @param {Object} attributes Attributes keys and values to set on a paragraph that this function can create when
|
|
136
|
+
// `place` parameter is equal to `after` but there is no element with `$text` node to set selection in.
|
|
137
|
+
function updateSelection( writer, contextElement, place, paragraphAttributes ) {
|
|
138
|
+
const model = writer.model;
|
|
139
|
+
|
|
140
|
+
if ( place == 'after' ) {
|
|
141
|
+
let nextElement = contextElement.nextSibling;
|
|
142
|
+
|
|
143
|
+
// Check whether an element next to the inserted element is defined and can contain a text.
|
|
144
|
+
const canSetSelection = nextElement && model.schema.checkChild( nextElement, '$text' );
|
|
145
|
+
|
|
146
|
+
// If the element is missing, but a paragraph could be inserted next to the element, let's add it.
|
|
147
|
+
if ( !canSetSelection && model.schema.checkChild( contextElement.parent, 'paragraph' ) ) {
|
|
148
|
+
nextElement = writer.createElement( 'paragraph' );
|
|
149
|
+
|
|
150
|
+
model.schema.setAllowedAttributes( nextElement, paragraphAttributes, writer );
|
|
151
|
+
model.insertContent( nextElement, writer.createPositionAfter( contextElement ) );
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Put the selection inside the element, at the beginning.
|
|
155
|
+
if ( nextElement ) {
|
|
156
|
+
writer.setSelection( nextElement, 0 );
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if ( place == 'on' ) {
|
|
160
|
+
writer.setSelection( contextElement, 'on' );
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
/**
|
|
164
|
+
* The unsupported `options.setSelection` parameter was passed
|
|
165
|
+
* to the {@link module:engine/model/utils/insertobject insertObject()} function.
|
|
166
|
+
* Check the {@link module:engine/model/utils/insertobject insertObject()} API documentation for allowed
|
|
167
|
+
* `options.setSelection` parameter values.
|
|
168
|
+
*
|
|
169
|
+
* @error insertobject-invalid-place-parameter-value
|
|
170
|
+
*/
|
|
171
|
+
throw new CKEditorError( 'insertobject-invalid-place-parameter-value', model );
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -24,16 +24,6 @@ const DEFAULT_PRIORITY = 10;
|
|
|
24
24
|
* To create a new attribute element instance use the
|
|
25
25
|
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement `DowncastWriter#createAttributeElement()`} method.
|
|
26
26
|
*
|
|
27
|
-
* **Note:** Attribute elements by default can wrap {@link module:engine/view/text~Text},
|
|
28
|
-
* {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/uielement~UIElement},
|
|
29
|
-
* {@link module:engine/view/rawelement~RawElement} and other attribute elements with higher priority. Other elements while placed inside
|
|
30
|
-
* an attribute element will split it (or nest in case of an `AttributeElement`). This behavior can be modified by changing
|
|
31
|
-
* the `isAllowedInsideAttributeElement` option while creating
|
|
32
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
|
|
33
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement},
|
|
34
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} or
|
|
35
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement}.
|
|
36
|
-
*
|
|
37
27
|
* @extends module:engine/view/element~Element
|
|
38
28
|
*/
|
|
39
29
|
export default class AttributeElement extends Element {
|
package/src/view/domconverter.js
CHANGED
|
@@ -35,7 +35,6 @@ const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
|
35
35
|
const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
36
36
|
const UNSAFE_ATTRIBUTE_NAME_PREFIX = 'data-ck-unsafe-attribute-';
|
|
37
37
|
const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
38
|
-
const UNSAFE_ELEMENTS = [ 'script', 'style' ];
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
|
|
@@ -127,6 +126,15 @@ export default class DomConverter {
|
|
|
127
126
|
'object', 'iframe', 'input', 'button', 'textarea', 'select', 'option', 'video', 'embed', 'audio', 'img', 'canvas'
|
|
128
127
|
];
|
|
129
128
|
|
|
129
|
+
/**
|
|
130
|
+
* A list of elements which may affect the editing experience. To avoid this, those elements are replaced with
|
|
131
|
+
* `<span data-ck-unsafe-element="[element name]"></span>` while rendering in the editing mode.
|
|
132
|
+
*
|
|
133
|
+
* @readonly
|
|
134
|
+
* @member {Array.<String>} module:engine/view/domconverter~DomConverter#unsafeElements
|
|
135
|
+
*/
|
|
136
|
+
this.unsafeElements = [ 'script', 'style' ];
|
|
137
|
+
|
|
130
138
|
/**
|
|
131
139
|
* The DOM-to-view mapping.
|
|
132
140
|
*
|
|
@@ -493,7 +501,22 @@ export default class DomConverter {
|
|
|
493
501
|
yield this._getBlockFiller( domDocument );
|
|
494
502
|
}
|
|
495
503
|
|
|
496
|
-
|
|
504
|
+
const transparentRendering = childView.is( 'element' ) && childView.getCustomProperty( 'dataPipeline:transparentRendering' );
|
|
505
|
+
|
|
506
|
+
if ( transparentRendering && this.renderingMode == 'data' ) {
|
|
507
|
+
yield* this.viewChildrenToDom( childView, domDocument, options );
|
|
508
|
+
} else {
|
|
509
|
+
if ( transparentRendering ) {
|
|
510
|
+
/**
|
|
511
|
+
* The `dataPipeline:transparentRendering` flag is supported only in the data pipeline.
|
|
512
|
+
*
|
|
513
|
+
* @error domconverter-transparent-rendering-unsupported-in-editing-pipeline
|
|
514
|
+
*/
|
|
515
|
+
logWarning( 'domconverter-transparent-rendering-unsupported-in-editing-pipeline', { viewElement: childView } );
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
yield this.viewToDom( childView, domDocument, options );
|
|
519
|
+
}
|
|
497
520
|
|
|
498
521
|
offset++;
|
|
499
522
|
}
|
|
@@ -657,7 +680,7 @@ export default class DomConverter {
|
|
|
657
680
|
const attrs = domNode.attributes;
|
|
658
681
|
|
|
659
682
|
if ( attrs ) {
|
|
660
|
-
for ( let
|
|
683
|
+
for ( let l = attrs.length, i = 0; i < l; i++ ) {
|
|
661
684
|
viewElement._setAttribute( attrs[ i ].name, attrs[ i ].value );
|
|
662
685
|
}
|
|
663
686
|
}
|
|
@@ -1549,7 +1572,7 @@ export default class DomConverter {
|
|
|
1549
1572
|
_shouldRenameElement( elementName ) {
|
|
1550
1573
|
const name = elementName.toLowerCase();
|
|
1551
1574
|
|
|
1552
|
-
return this.renderingMode === 'editing' &&
|
|
1575
|
+
return this.renderingMode === 'editing' && this.unsafeElements.includes( name );
|
|
1553
1576
|
}
|
|
1554
1577
|
|
|
1555
1578
|
/**
|
|
@@ -185,10 +185,6 @@ export default class DowncastWriter {
|
|
|
185
185
|
* // Set `id` of a marker element so it is not joined or merged with "normal" elements.
|
|
186
186
|
* writer.createAttributeElement( 'span', { class: 'my-marker' }, { id: 'marker:my' } );
|
|
187
187
|
*
|
|
188
|
-
* **Note:** By default an `AttributeElement` is split by a
|
|
189
|
-
* {@link module:engine/view/containerelement~ContainerElement `ContainerElement`} but this behavior can be modified
|
|
190
|
-
* with `isAllowedInsideAttributeElement` option set while {@link #createContainerElement creating the element}.
|
|
191
|
-
*
|
|
192
188
|
* @param {String} name Name of the element.
|
|
193
189
|
* @param {Object} [attributes] Element's attributes.
|
|
194
190
|
* @param {Object} [options] Element's options.
|
|
@@ -237,7 +233,7 @@ export default class DowncastWriter {
|
|
|
237
233
|
* ] );
|
|
238
234
|
*
|
|
239
235
|
* // Create element with specific options.
|
|
240
|
-
* writer.createContainerElement( 'span', { class: 'placeholder' }, {
|
|
236
|
+
* writer.createContainerElement( 'span', { class: 'placeholder' }, { renderUnsafeAttributes: [ 'foo' ] } );
|
|
241
237
|
*
|
|
242
238
|
* @param {String} name Name of the element.
|
|
243
239
|
* @param {Object} [attributes] Elements attributes.
|
|
@@ -245,9 +241,6 @@ export default class DowncastWriter {
|
|
|
245
241
|
* A node or a list of nodes to be inserted into the created element. If no children were specified, element's `options`
|
|
246
242
|
* can be passed in this argument.
|
|
247
243
|
* @param {Object} [options] Element's options.
|
|
248
|
-
* @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is
|
|
249
|
-
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
250
|
-
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
251
244
|
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
252
245
|
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
253
246
|
* @returns {module:engine/view/containerelement~ContainerElement} Created element.
|
|
@@ -263,10 +256,6 @@ export default class DowncastWriter {
|
|
|
263
256
|
|
|
264
257
|
const containerElement = new ContainerElement( this.document, name, attributes, children );
|
|
265
258
|
|
|
266
|
-
if ( options.isAllowedInsideAttributeElement !== undefined ) {
|
|
267
|
-
containerElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
259
|
if ( options.renderUnsafeAttributes ) {
|
|
271
260
|
containerElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
272
261
|
}
|
|
@@ -310,9 +299,6 @@ export default class DowncastWriter {
|
|
|
310
299
|
* @param {String} name Name of the element.
|
|
311
300
|
* @param {Object} [attributes] Elements attributes.
|
|
312
301
|
* @param {Object} [options] Element's options.
|
|
313
|
-
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
|
|
314
|
-
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
315
|
-
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
316
302
|
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
317
303
|
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
318
304
|
* @returns {module:engine/view/emptyelement~EmptyElement} Created element.
|
|
@@ -320,10 +306,6 @@ export default class DowncastWriter {
|
|
|
320
306
|
createEmptyElement( name, attributes, options = {} ) {
|
|
321
307
|
const emptyElement = new EmptyElement( this.document, name, attributes );
|
|
322
308
|
|
|
323
|
-
if ( options.isAllowedInsideAttributeElement !== undefined ) {
|
|
324
|
-
emptyElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
309
|
if ( options.renderUnsafeAttributes ) {
|
|
328
310
|
emptyElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
329
311
|
}
|
|
@@ -354,23 +336,15 @@ export default class DowncastWriter {
|
|
|
354
336
|
* @param {String} name The name of the element.
|
|
355
337
|
* @param {Object} [attributes] Element attributes.
|
|
356
338
|
* @param {Function} [renderFunction] A custom render function.
|
|
357
|
-
* @param {Object} [options] Element's options.
|
|
358
|
-
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
|
|
359
|
-
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
360
|
-
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
361
339
|
* @returns {module:engine/view/uielement~UIElement} The created element.
|
|
362
340
|
*/
|
|
363
|
-
createUIElement( name, attributes, renderFunction
|
|
341
|
+
createUIElement( name, attributes, renderFunction ) {
|
|
364
342
|
const uiElement = new UIElement( this.document, name, attributes );
|
|
365
343
|
|
|
366
344
|
if ( renderFunction ) {
|
|
367
345
|
uiElement.render = renderFunction;
|
|
368
346
|
}
|
|
369
347
|
|
|
370
|
-
if ( options.isAllowedInsideAttributeElement !== undefined ) {
|
|
371
|
-
uiElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
348
|
return uiElement;
|
|
375
349
|
}
|
|
376
350
|
|
|
@@ -397,9 +371,6 @@ export default class DowncastWriter {
|
|
|
397
371
|
* @param {Object} [attributes] Element attributes.
|
|
398
372
|
* @param {Function} [renderFunction] A custom render function.
|
|
399
373
|
* @param {Object} [options] Element's options.
|
|
400
|
-
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
|
|
401
|
-
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
|
|
402
|
-
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
403
374
|
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
|
|
404
375
|
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
|
|
405
376
|
* @returns {module:engine/view/rawelement~RawElement} The created element.
|
|
@@ -409,10 +380,6 @@ export default class DowncastWriter {
|
|
|
409
380
|
|
|
410
381
|
rawElement.render = renderFunction || ( () => {} );
|
|
411
382
|
|
|
412
|
-
if ( options.isAllowedInsideAttributeElement !== undefined ) {
|
|
413
|
-
rawElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
383
|
if ( options.renderUnsafeAttributes ) {
|
|
417
384
|
rawElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
|
|
418
385
|
}
|
|
@@ -790,7 +757,7 @@ export default class DowncastWriter {
|
|
|
790
757
|
|
|
791
758
|
// Break attributes on nodes that do exist in the model tree so they can have attributes, other elements
|
|
792
759
|
// can't have an attribute in model and won't get wrapped with an AttributeElement while down-casted.
|
|
793
|
-
const breakAttributes = !
|
|
760
|
+
const breakAttributes = !node.is( 'uiElement' );
|
|
794
761
|
|
|
795
762
|
if ( !lastGroup || lastGroup.breakAttributes != breakAttributes ) {
|
|
796
763
|
groups.push( {
|
|
@@ -979,16 +946,6 @@ export default class DowncastWriter {
|
|
|
979
946
|
* Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-nonselection-collapsed-range` when passed range
|
|
980
947
|
* is collapsed and different than view selection.
|
|
981
948
|
*
|
|
982
|
-
* **Note:** Attribute elements by default can wrap {@link module:engine/view/text~Text},
|
|
983
|
-
* {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/uielement~UIElement},
|
|
984
|
-
* {@link module:engine/view/rawelement~RawElement} and other attribute elements with higher priority. Other elements while placed
|
|
985
|
-
* inside an attribute element will split it (or nest it in case of an `AttributeElement`). This behavior can be modified by changing
|
|
986
|
-
* the `isAllowedInsideAttributeElement` option while using
|
|
987
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
|
|
988
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement},
|
|
989
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} or
|
|
990
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement}.
|
|
991
|
-
*
|
|
992
949
|
* @param {module:engine/view/range~Range} range Range to wrap.
|
|
993
950
|
* @param {module:engine/view/attributeelement~AttributeElement} attribute Attribute element to use as wrapper.
|
|
994
951
|
* @returns {module:engine/view/range~Range} range Range after wrapping, spanning over wrapping attribute element.
|
|
@@ -1399,7 +1356,6 @@ export default class DowncastWriter {
|
|
|
1399
1356
|
const child = parent.getChild( i );
|
|
1400
1357
|
const isText = child.is( '$text' );
|
|
1401
1358
|
const isAttribute = child.is( 'attributeElement' );
|
|
1402
|
-
const isAllowedInsideAttributeElement = child.isAllowedInsideAttributeElement;
|
|
1403
1359
|
|
|
1404
1360
|
//
|
|
1405
1361
|
// (In all examples, assume that `wrapElement` is `<span class="foo">` element.)
|
|
@@ -1418,7 +1374,7 @@ export default class DowncastWriter {
|
|
|
1418
1374
|
//
|
|
1419
1375
|
// <p>abc</p> --> <p><span class="foo">abc</span></p>
|
|
1420
1376
|
// <p><strong>abc</strong></p> --> <p><span class="foo"><strong>abc</strong></span></p>
|
|
1421
|
-
else if ( isText ||
|
|
1377
|
+
else if ( isText || !isAttribute || shouldABeOutsideB( wrapElement, child ) ) {
|
|
1422
1378
|
// Clone attribute.
|
|
1423
1379
|
const newAttribute = wrapElement._clone();
|
|
1424
1380
|
|
|
@@ -1436,7 +1392,7 @@ export default class DowncastWriter {
|
|
|
1436
1392
|
//
|
|
1437
1393
|
// <p><a href="foo.html">abc</a></p> --> <p><a href="foo.html"><span class="foo">abc</span></a></p>
|
|
1438
1394
|
//
|
|
1439
|
-
else if ( isAttribute ) {
|
|
1395
|
+
else /* if ( isAttribute ) */ {
|
|
1440
1396
|
this._wrapChildren( child, 0, child.childCount, wrapElement );
|
|
1441
1397
|
}
|
|
1442
1398
|
|
package/src/view/element.js
CHANGED
|
@@ -130,15 +130,6 @@ export default class Element extends Node {
|
|
|
130
130
|
*/
|
|
131
131
|
this._customProperties = new Map();
|
|
132
132
|
|
|
133
|
-
/**
|
|
134
|
-
* Whether an element is allowed inside an AttributeElement and can be wrapped with
|
|
135
|
-
* {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
136
|
-
*
|
|
137
|
-
* @protected
|
|
138
|
-
* @member {Boolean}
|
|
139
|
-
*/
|
|
140
|
-
this._isAllowedInsideAttributeElement = false;
|
|
141
|
-
|
|
142
133
|
/**
|
|
143
134
|
* A list of attribute names that should be rendered in the editing pipeline even though filtering mechanisms
|
|
144
135
|
* implemented in the {@link module:engine/view/domconverter~DomConverter} (for instance,
|
|
@@ -175,17 +166,6 @@ export default class Element extends Node {
|
|
|
175
166
|
return this._children.length === 0;
|
|
176
167
|
}
|
|
177
168
|
|
|
178
|
-
/**
|
|
179
|
-
* Whether the element is allowed inside an AttributeElement and can be wrapped with
|
|
180
|
-
* {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
181
|
-
*
|
|
182
|
-
* @readonly
|
|
183
|
-
* @type {Boolean}
|
|
184
|
-
*/
|
|
185
|
-
get isAllowedInsideAttributeElement() {
|
|
186
|
-
return this._isAllowedInsideAttributeElement;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
169
|
/**
|
|
190
170
|
* Checks whether this object is of the given.
|
|
191
171
|
*
|
|
@@ -350,11 +330,6 @@ export default class Element extends Node {
|
|
|
350
330
|
return false;
|
|
351
331
|
}
|
|
352
332
|
|
|
353
|
-
// Check isAllowedInsideAttributeElement property.
|
|
354
|
-
if ( this.isAllowedInsideAttributeElement != otherElement.isAllowedInsideAttributeElement ) {
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
333
|
// Check number of attributes, classes and styles.
|
|
359
334
|
if ( this._attrs.size !== otherElement._attrs.size || this._classes.size !== otherElement._classes.size ||
|
|
360
335
|
this._styles.size !== otherElement._styles.size ) {
|
|
@@ -633,7 +608,8 @@ export default class Element extends Node {
|
|
|
633
608
|
// is changed by e.g. toWidget() function from ckeditor5-widget. Perhaps this should be one of custom props.
|
|
634
609
|
cloned.getFillerOffset = this.getFillerOffset;
|
|
635
610
|
|
|
636
|
-
|
|
611
|
+
// Clone unsafe attributes list.
|
|
612
|
+
cloned._unsafeAttributesToRender = this._unsafeAttributesToRender;
|
|
637
613
|
|
|
638
614
|
return cloned;
|
|
639
615
|
}
|
package/src/view/emptyelement.js
CHANGED
|
@@ -37,9 +37,6 @@ export default class EmptyElement extends Element {
|
|
|
37
37
|
constructor( document, name, attrs, children ) {
|
|
38
38
|
super( document, name, attrs, children );
|
|
39
39
|
|
|
40
|
-
// Override the default of the base class.
|
|
41
|
-
this._isAllowedInsideAttributeElement = true;
|
|
42
|
-
|
|
43
40
|
/**
|
|
44
41
|
* Returns `null` because filler is not needed for EmptyElements.
|
|
45
42
|
*
|
package/src/view/filler.js
CHANGED
|
@@ -56,7 +56,7 @@ export const NBSP_FILLER = domDocument => domDocument.createTextNode( '\u00A0' )
|
|
|
56
56
|
export const MARKED_NBSP_FILLER = domDocument => {
|
|
57
57
|
const span = domDocument.createElement( 'span' );
|
|
58
58
|
span.dataset.ckeFiller = true;
|
|
59
|
-
span.
|
|
59
|
+
span.innerText = '\u00A0';
|
|
60
60
|
|
|
61
61
|
return span;
|
|
62
62
|
};
|
package/src/view/matcher.js
CHANGED
|
@@ -739,7 +739,7 @@ function matchStyles( patterns, element ) {
|
|
|
739
739
|
* styles: /^border.*$/
|
|
740
740
|
* }
|
|
741
741
|
*
|
|
742
|
-
* Refer to the {@glink
|
|
742
|
+
* Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
|
|
743
743
|
* and {@link module:engine/view/matcher~MatcherPattern} documentation.
|
|
744
744
|
*
|
|
745
745
|
* @param {Object} pattern Pattern with missing properties.
|
|
@@ -769,7 +769,7 @@ function matchStyles( patterns, element ) {
|
|
|
769
769
|
* classes: 'foobar'
|
|
770
770
|
* }
|
|
771
771
|
*
|
|
772
|
-
* Refer to the {@glink
|
|
772
|
+
* Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
|
|
773
773
|
* and the {@link module:engine/view/matcher~MatcherPattern} documentation.
|
|
774
774
|
*
|
|
775
775
|
* @param {Object} pattern Pattern with missing properties.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module engine/view/observer/tabobserver
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import Observer from './observer';
|
|
11
|
+
import BubblingEventInfo from './bubblingeventinfo';
|
|
12
|
+
|
|
13
|
+
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tab observer introduces the {@link module:engine/view/document~Document#event:tab `Document#tab`} event.
|
|
17
|
+
*
|
|
18
|
+
* Note that because {@link module:engine/view/observer/tabobserver~TabObserver} is attached by the
|
|
19
|
+
* {@link module:engine/view/view~View}, this event is available by default.
|
|
20
|
+
*
|
|
21
|
+
* @extends module:engine/view/observer/observer~Observer
|
|
22
|
+
*/
|
|
23
|
+
export default class TabObserver extends Observer {
|
|
24
|
+
/**
|
|
25
|
+
* @inheritDoc
|
|
26
|
+
*/
|
|
27
|
+
constructor( view ) {
|
|
28
|
+
super( view );
|
|
29
|
+
|
|
30
|
+
const doc = this.document;
|
|
31
|
+
|
|
32
|
+
doc.on( 'keydown', ( evt, data ) => {
|
|
33
|
+
if (
|
|
34
|
+
!this.isEnabled ||
|
|
35
|
+
data.keyCode != keyCodes.tab ||
|
|
36
|
+
data.ctrlKey
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const event = new BubblingEventInfo( doc, 'tab', doc.selection.getFirstRange() );
|
|
42
|
+
|
|
43
|
+
doc.fire( event, data );
|
|
44
|
+
|
|
45
|
+
if ( event.stop.called ) {
|
|
46
|
+
evt.stop();
|
|
47
|
+
}
|
|
48
|
+
} );
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @inheritDoc
|
|
53
|
+
*/
|
|
54
|
+
observe() {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Event fired when the user presses a tab key.
|
|
59
|
+
*
|
|
60
|
+
* Introduced by {@link module:engine/view/observer/tabobserver~TabObserver}.
|
|
61
|
+
*
|
|
62
|
+
* Note that because {@link module:engine/view/observer/tabobserver~TabObserver} is attached by the
|
|
63
|
+
* {@link module:engine/view/view~View}, this event is available by default.
|
|
64
|
+
*
|
|
65
|
+
* @event module:engine/view/document~Document#event:tab
|
|
66
|
+
*
|
|
67
|
+
* @param {module:engine/view/observer/domeventdata~DomEventData} data
|
|
68
|
+
*/
|
package/src/view/placeholder.js
CHANGED
|
@@ -276,7 +276,7 @@ function getChildPlaceholderHostSubstitute( parent ) {
|
|
|
276
276
|
if ( parent.childCount ) {
|
|
277
277
|
const firstChild = parent.getChild( 0 );
|
|
278
278
|
|
|
279
|
-
if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) ) {
|
|
279
|
+
if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) && !firstChild.is( 'attributeElement' ) ) {
|
|
280
280
|
return firstChild;
|
|
281
281
|
}
|
|
282
282
|
}
|
package/src/view/rawelement.js
CHANGED
|
@@ -47,9 +47,6 @@ export default class RawElement extends Element {
|
|
|
47
47
|
constructor( document, name, attrs, children ) {
|
|
48
48
|
super( document, name, attrs, children );
|
|
49
49
|
|
|
50
|
-
// Override the default of the base class.
|
|
51
|
-
this._isAllowedInsideAttributeElement = true;
|
|
52
|
-
|
|
53
50
|
/**
|
|
54
51
|
* Returns `null` because filler is not needed for raw elements.
|
|
55
52
|
*
|
package/src/view/renderer.js
CHANGED
|
@@ -234,6 +234,10 @@ export default class Renderer {
|
|
|
234
234
|
else if ( this._inlineFiller && this._inlineFiller.parentNode ) {
|
|
235
235
|
// While the user is making selection, preserve the inline filler at its original position.
|
|
236
236
|
inlineFillerPosition = this.domConverter.domPositionToView( this._inlineFiller );
|
|
237
|
+
|
|
238
|
+
if ( inlineFillerPosition.parent.is( '$text' ) ) {
|
|
239
|
+
inlineFillerPosition = ViewPosition._createBefore( inlineFillerPosition.parent );
|
|
240
|
+
}
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
for ( const element of this.markedAttributes ) {
|
package/src/view/uielement.js
CHANGED
|
@@ -50,9 +50,6 @@ export default class UIElement extends Element {
|
|
|
50
50
|
constructor( document, name, attributes, children ) {
|
|
51
51
|
super( document, name, attributes, children );
|
|
52
52
|
|
|
53
|
-
// Override the default of the base class.
|
|
54
|
-
this._isAllowedInsideAttributeElement = true;
|
|
55
|
-
|
|
56
53
|
/**
|
|
57
54
|
* Returns `null` because filler is not needed for UIElements.
|
|
58
55
|
*
|
package/src/view/view.js
CHANGED
|
@@ -23,6 +23,7 @@ import FocusObserver from './observer/focusobserver';
|
|
|
23
23
|
import CompositionObserver from './observer/compositionobserver';
|
|
24
24
|
import InputObserver from './observer/inputobserver';
|
|
25
25
|
import ArrowKeysObserver from './observer/arrowkeysobserver';
|
|
26
|
+
import TabObserver from './observer/tabobserver';
|
|
26
27
|
|
|
27
28
|
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
|
|
28
29
|
import mix from '@ckeditor/ckeditor5-utils/src/mix';
|
|
@@ -54,6 +55,8 @@ import env from '@ckeditor/ckeditor5-utils/src/env';
|
|
|
54
55
|
* * {@link module:engine/view/observer/keyobserver~KeyObserver},
|
|
55
56
|
* * {@link module:engine/view/observer/fakeselectionobserver~FakeSelectionObserver}.
|
|
56
57
|
* * {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
|
|
58
|
+
* * {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver}.
|
|
59
|
+
* * {@link module:engine/view/observer/tabobserver~TabObserver}.
|
|
57
60
|
*
|
|
58
61
|
* This class also {@link module:engine/view/view~View#attachDomRoot binds the DOM and the view elements}.
|
|
59
62
|
*
|
|
@@ -186,6 +189,7 @@ export default class View {
|
|
|
186
189
|
this.addObserver( FakeSelectionObserver );
|
|
187
190
|
this.addObserver( CompositionObserver );
|
|
188
191
|
this.addObserver( ArrowKeysObserver );
|
|
192
|
+
this.addObserver( TabObserver );
|
|
189
193
|
|
|
190
194
|
if ( env.isAndroid ) {
|
|
191
195
|
this.addObserver( InputObserver );
|