Socket
Socket
Sign inDemoInstall

@ckeditor/ckeditor5-engine

Package Overview
Dependencies
Maintainers
1
Versions
584
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-engine - npm Package Compare versions

Comparing version 19.0.1 to 20.0.0

38

package.json
{
"name": "@ckeditor/ckeditor5-engine",
"version": "19.0.1",
"version": "20.0.0",
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",

@@ -24,24 +24,24 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-utils": "^19.0.1",
"lodash-es": "^4.17.10"
"@ckeditor/ckeditor5-utils": "^20.0.0",
"lodash-es": "^4.17.15"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^19.0.1",
"@ckeditor/ckeditor5-block-quote": "^19.0.1",
"@ckeditor/ckeditor5-core": "^19.0.1",
"@ckeditor/ckeditor5-editor-classic": "^19.0.1",
"@ckeditor/ckeditor5-enter": "^19.0.1",
"@ckeditor/ckeditor5-essentials": "^19.0.1",
"@ckeditor/ckeditor5-heading": "^19.0.1",
"@ckeditor/ckeditor5-link": "^19.0.1",
"@ckeditor/ckeditor5-list": "^19.0.1",
"@ckeditor/ckeditor5-paragraph": "^19.1.0",
"@ckeditor/ckeditor5-table": "^19.1.0",
"@ckeditor/ckeditor5-theme-lark": "^19.1.0",
"@ckeditor/ckeditor5-typing": "^19.0.1",
"@ckeditor/ckeditor5-undo": "^19.0.1",
"@ckeditor/ckeditor5-widget": "^19.1.0"
"@ckeditor/ckeditor5-basic-styles": "^20.0.0",
"@ckeditor/ckeditor5-block-quote": "^20.0.0",
"@ckeditor/ckeditor5-core": "^20.0.0",
"@ckeditor/ckeditor5-editor-classic": "^20.0.0",
"@ckeditor/ckeditor5-enter": "^20.0.0",
"@ckeditor/ckeditor5-essentials": "^20.0.0",
"@ckeditor/ckeditor5-heading": "^20.0.0",
"@ckeditor/ckeditor5-link": "^20.0.0",
"@ckeditor/ckeditor5-list": "^20.0.0",
"@ckeditor/ckeditor5-paragraph": "^20.0.0",
"@ckeditor/ckeditor5-table": "^20.0.0",
"@ckeditor/ckeditor5-theme-lark": "^20.0.0",
"@ckeditor/ckeditor5-typing": "^20.0.0",
"@ckeditor/ckeditor5-undo": "^20.0.0",
"@ckeditor/ckeditor5-widget": "^20.0.0"
},
"engines": {
"node": ">=8.0.0",
"node": ">=12.0.0",
"npm": ">=5.7.1"

@@ -48,0 +48,0 @@ },

@@ -235,3 +235,3 @@ /**

* Creates a copy of this element and returns it. Created element has the same name and attributes as the original element.
* If clone is deep, the original element's children are also cloned. If not, then empty element is removed.
* If clone is deep, the original element's children are also cloned. If not, then empty element is returned.
*

@@ -238,0 +238,0 @@ * @protected

@@ -554,5 +554,6 @@ /**

* @param {Boolean} [options.ignoreWhitespaces] Whether text node with whitespaces only should be considered empty.
* @param {Boolean} [options.ignoreMarkers] Whether markers should be ignored.
* @returns {Boolean}
*/
hasContent( rangeOrElement, options ) {
hasContent( rangeOrElement, options = {} ) {
const range = rangeOrElement instanceof ModelElement ? ModelRange._createIn( rangeOrElement ) : rangeOrElement;

@@ -564,11 +565,13 @@

const { ignoreWhitespaces = false, ignoreMarkers = false } = options;
// Check if there are any markers which affects data in this given range.
for ( const intersectingMarker of this.markers.getMarkersIntersectingRange( range ) ) {
if ( intersectingMarker.affectsData ) {
return true;
if ( !ignoreMarkers ) {
for ( const intersectingMarker of this.markers.getMarkersIntersectingRange( range ) ) {
if ( intersectingMarker.affectsData ) {
return true;
}
}
}
const { ignoreWhitespaces = false } = options || {};
for ( const item of range.getItems() ) {

@@ -575,0 +578,0 @@ if ( item.is( 'textProxy' ) ) {

@@ -29,5 +29,5 @@ /**

*
* * {@glink framework/guides/architecture/editing-engine#schema "Schema"} section of the
* {@glink framework/guides/architecture/editing-engine Introduction to the "Editing engine architecture"}.
* * {@glink framework/guides/deep-dive/schema "Schema" deep dive} guide.
* * {@glink framework/guides/architecture/editing-engine#schema Schema} section of the
* {@glink framework/guides/architecture/editing-engine Introduction to the Editing engine architecture}.
* * {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*

@@ -175,3 +175,3 @@ * @mixes module:utils/observablemixin~ObservableMixin

/**
* Returns a definition of the given item or `undefined` if item is not registered.
* Returns a definition of the given item or `undefined` if an item is not registered.
*

@@ -217,3 +217,3 @@ * This method should normally be used for reflection purposes (e.g. defining a clone of a certain element,

* Returns `true` if the given item is defined to be
* a block by {@link module:engine/model/schema~SchemaItemDefinition}'s `isBlock` property.
* a block by the {@link module:engine/model/schema~SchemaItemDefinition}'s `isBlock` property.
*

@@ -226,3 +226,3 @@ * schema.isBlock( 'paragraph' ); // -> true

*
* See the {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of the "Schema" deep dive}
* See the {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of the Schema deep dive}
* guide for more details.

@@ -241,7 +241,7 @@ *

*
* It considers the item to be a limit element if its
* It considers an item to be a limit element if its
* {@link module:engine/model/schema~SchemaItemDefinition}'s
* {@link module:engine/model/schema~SchemaItemDefinition#isLimit `isLimit`} or
* {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property
* were set to `true`.
* was set to `true`.
*

@@ -253,3 +253,3 @@ * schema.isLimit( 'paragraph' ); // -> false

*
* See the {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of the "Schema" deep dive}
* See the {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of the Schema deep dive}
* guide for more details.

@@ -270,8 +270,8 @@ *

/**
* Returns `true` if the given item is should be treated as an object element.
* Returns `true` if the given item should be treated as an object element.
*
* It considers the item to be an object element if its
* It considers an item to be an object element if its
* {@link module:engine/model/schema~SchemaItemDefinition}'s
* {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property
* were set to `true`.
* was set to `true`.
*

@@ -284,3 +284,3 @@ * schema.isObject( 'paragraph' ); // -> false

*
* See the {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of the "Schema" deep dive}
* See the {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of the Schema deep dive}
* guide for more details.

@@ -298,3 +298,3 @@ *

* Returns `true` if the given item is defined to be
* an inline element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isInline` property.
* an inline element by the {@link module:engine/model/schema~SchemaItemDefinition}'s `isInline` property.
*

@@ -307,3 +307,3 @@ * schema.isInline( 'paragraph' ); // -> false

*
* See the {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of the "Schema" deep dive}
* See the {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of the Schema deep dive}
* guide for more details.

@@ -750,9 +750,9 @@ *

/**
* Tries to find position ancestors that allows to insert given node.
* Tries to find position ancestors that allow to insert a given node.
* It starts searching from the given position and goes node by node to the top of the model tree
* as long as {@link module:engine/model/schema~Schema#isLimit limit element},
* {@link module:engine/model/schema~Schema#isObject object element} or top-most ancestor won't be reached.
* as long as a {@link module:engine/model/schema~Schema#isLimit limit element}, an
* {@link module:engine/model/schema~Schema#isObject object element} or a topmost ancestor is not reached.
*
* @param {module:engine/model/position~Position} position Position from searching will start.
* @param {module:engine/model/node~Node|String} node Node for which allowed parent should be found or its name.
* @param {module:engine/model/position~Position} position The position that the search will start from.
* @param {module:engine/model/node~Node|String} node The node for which an allowed parent should be found or its name.
* @returns {module:engine/model/element~Element|null} element Allowed parent or null if nothing was found.

@@ -887,3 +887,3 @@ */

* @private
* @param {module:engine/model/range~Range} range Range to process.
* @param {module:engine/model/range~Range} range The range to process.
* @param {String} attribute The name of the attribute to check.

@@ -922,3 +922,3 @@ * @returns {Iterable.<module:engine/model/range~Range>} Ranges in which the attribute is allowed.

* Event fired when the {@link #checkChild} method is called. It allows plugging in
* additional behavior – e.g. implementing rules which cannot be defined using the declarative
* additional behavior, for example implementing rules which cannot be defined using the declarative
* {@link module:engine/model/schema~SchemaItemDefinition} interface.

@@ -932,3 +932,3 @@ *

* {@link module:utils/observablemixin~ObservableMixin#decorate decorated} with it. Thanks to that you can
* use this event in a various way, but the most important use case is overriding standard behaviour of the
* use this event in various ways, but the most important use case is overriding standard behavior of the
* `checkChild()` method. Let's see a typical listener template:

@@ -944,3 +944,3 @@ *

* normalized to a {@link module:engine/model/schema~SchemaContext} instance and `child` to a
* {@link module:engine/model/schema~SchemaCompiledItemDefinition} instance, so you don't have to worry about
* {@link module:engine/model/schema~SchemaCompiledItemDefinition} instance, so you do not have to worry about
* the various ways how `context` and `child` may be passed to `checkChild()`.

@@ -950,3 +950,3 @@ *

*
* So, in order to implement a rule "disallow `heading1` in `blockQuote`" you can add such a listener:
* So, in order to implement a rule "disallow `heading1` in `blockQuote`", you can add such a listener:
*

@@ -965,4 +965,4 @@ * schema.on( 'checkChild', ( evt, args ) => {

*
* Allowing elements in specific contexts will be a far less common use case, because it's normally handled by
* `allowIn` rule from {@link module:engine/model/schema~SchemaItemDefinition} but if you have a complex scenario
* Allowing elements in specific contexts will be a far less common use case, because it is normally handled by the
* `allowIn` rule from {@link module:engine/model/schema~SchemaItemDefinition}. But if you have a complex scenario
* where `listItem` should be allowed only in element `foo` which must be in element `bar`, then this would be the way:

@@ -988,3 +988,3 @@ *

* Event fired when the {@link #checkAttribute} method is called. It allows plugging in
* additional behavior – e.g. implementing rules which cannot be defined using the declarative
* additional behavior, for example implementing rules which cannot be defined using the declarative
* {@link module:engine/model/schema~SchemaItemDefinition} interface.

@@ -996,5 +996,5 @@ *

*
* The {@link #checkAttribute} method fires an event because it's
* The {@link #checkAttribute} method fires an event because it is
* {@link module:utils/observablemixin~ObservableMixin#decorate decorated} with it. Thanks to that you can
* use this event in a various way, but the most important use case is overriding standard behaviour of the
* use this event in various ways, but the most important use case is overriding the standard behavior of the
* `checkAttribute()` method. Let's see a typical listener template:

@@ -1009,6 +1009,6 @@ *

* parameter contains arguments passed to `checkAttribute( context, attributeName )`. However, the `context` parameter is already
* normalized to a {@link module:engine/model/schema~SchemaContext} instance, so you don't have to worry about
* normalized to a {@link module:engine/model/schema~SchemaContext} instance, so you do not have to worry about
* the various ways how `context` may be passed to `checkAttribute()`.
*
* So, in order to implement a rule "disallow `bold` in a text which is in a `heading1` you can add such a listener:
* So, in order to implement a rule "disallow `bold` in a text which is in a `heading1`, you can add such a listener:
*

@@ -1027,4 +1027,4 @@ * schema.on( 'checkAttribute', ( evt, args ) => {

*
* Allowing attributes in specific contexts will be a far less common use case, because it's normally handled by
* `allowAttributes` rule from {@link module:engine/model/schema~SchemaItemDefinition} but if you have a complex scenario
* Allowing attributes in specific contexts will be a far less common use case, because it is normally handled by the
* `allowAttributes` rule from {@link module:engine/model/schema~SchemaItemDefinition}. But if you have a complex scenario
* where `bold` should be allowed only in element `foo` which must be in element `bar`, then this would be the way:

@@ -1080,3 +1080,3 @@ *

* Read more about the meaning of these types in the
* {@glink framework/guides/deep-dive/schema#defining-additional-semantics Dedicated section of the "Schema" deep dive} guide.
* {@glink framework/guides/deep-dive/schema#defining-additional-semantics dedicated section of the Schema deep dive} guide.
*

@@ -1184,3 +1184,3 @@ * # Generic items

* Read more about the block elements in the
* {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of the "Schema" deep dive} guide.
* {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of the Schema deep dive} guide.
*

@@ -1192,3 +1192,3 @@ * @property {Boolean} isInline

* Read more about the inline elements in the
* {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of the "Schema" deep dive} guide.
* {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of the Schema deep dive} guide.
*

@@ -1201,3 +1201,3 @@ * @property {Boolean} isLimit

* Read more about the limit elements in the
* {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of the "Schema" deep dive} guide.
* {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of the Schema deep dive} guide.
*

@@ -1212,3 +1212,3 @@ * @property {Boolean} isObject

* Read more about the object elements in the
* {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of the "Schema" deep dive} guide.
* {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of the Schema deep dive} guide.
*/

@@ -1215,0 +1215,0 @@

@@ -83,4 +83,4 @@ /**

const startPos = selRange.start;
const endPos = LivePosition.fromPosition( selRange.end, 'toNext' );
// Get the live positions for the range adjusted to span only blocks selected from the user perspective.
const [ startPosition, endPosition ] = getLivePositionsForSelectedBlocks( selRange );

@@ -101,3 +101,3 @@ // 2. Remove the content if there is any.

if ( !options.leaveUnmerged ) {
mergeBranches( writer, startPos, endPos );
mergeBranches( writer, startPosition, endPosition );

@@ -110,6 +110,6 @@ // TMP this will be replaced with a postfixer.

// <h1>Fo{o</h1><p>b}a<b>r</b><p> -> <h1>Fo{}a<b>r</b><h1> -> <h1>Fo{}ar<h1>.
schema.removeDisallowedAttributes( startPos.parent.getChildren(), writer );
schema.removeDisallowedAttributes( startPosition.parent.getChildren(), writer );
}
collapseSelectionAt( writer, selection, startPos );
collapseSelectionAt( writer, selection, startPosition );

@@ -119,63 +119,240 @@ // 4. Add a paragraph to set selection in it.

// If autoparagraphing is off, we assume that you know what you do so we leave the selection wherever it was.
if ( !options.doNotAutoparagraph && shouldAutoparagraph( schema, startPos ) ) {
insertParagraph( writer, startPos, selection );
if ( !options.doNotAutoparagraph && shouldAutoparagraph( schema, startPosition ) ) {
insertParagraph( writer, startPosition, selection );
}
endPos.detach();
startPosition.detach();
endPosition.detach();
} );
}
// Returns the live positions for the range adjusted to span only blocks selected from the user perspective. Example:
//
// <heading1>[foo</heading1>
// <paragraph>bar</paragraph>
// <heading1>]abc</heading1> <-- this block is not considered as selected
//
// This is the same behavior as in Selection#getSelectedBlocks() "special case".
function getLivePositionsForSelectedBlocks( range ) {
const model = range.root.document.model;
const startPosition = range.start;
let endPosition = range.end;
// If the end of selection is at the start position of last block in the selection, then
// shrink it to not include that trailing block. Note that this should happen only for not empty selection.
if ( model.hasContent( range, { ignoreMarkers: true } ) ) {
const endBlock = getParentBlock( endPosition );
if ( endBlock && endPosition.isTouching( model.createPositionAt( endBlock, 0 ) ) ) {
// Create forward selection as a probe to find a valid position after excluding last block from the range.
const selection = model.createSelection( range );
// Modify the forward selection in backward direction to shrink it and remove first position of following block from it.
// This is how modifySelection works and here we are making use of it.
model.modifySelection( selection, { direction: 'backward' } );
endPosition = selection.getLastPosition();
}
}
return [
LivePosition.fromPosition( startPosition, 'toPrevious' ),
LivePosition.fromPosition( endPosition, 'toNext' )
];
}
// Finds the lowest element in position's ancestors which is a block.
// Returns null if a limit element is encountered before reaching a block element.
function getParentBlock( position ) {
const element = position.parent;
const schema = element.root.document.model.schema;
const ancestors = element.getAncestors( { parentFirst: true, includeSelf: true } );
for ( const element of ancestors ) {
if ( schema.isLimit( element ) ) {
return null;
}
if ( schema.isBlock( element ) ) {
return element;
}
}
}
// This function is a result of reaching the Ballmer's peak for just the right amount of time.
// Even I had troubles documenting it after a while and after reading it again I couldn't believe that it really works.
function mergeBranches( writer, startPos, endPos ) {
const startParent = startPos.parent;
const endParent = endPos.parent;
function mergeBranches( writer, startPosition, endPosition ) {
const model = writer.model;
// If both positions ended up in the same parent, then there's nothing more to merge:
// <$root><p>x[]</p><p>{}y</p></$root> => <$root><p>xy</p>[]{}</$root>
if ( startParent == endParent ) {
// Verify if there is a need and possibility to merge.
if ( !checkShouldMerge( writer.model.schema, startPosition, endPosition ) ) {
return;
}
// If one of the positions is a limit element, then there's nothing to merge because we don't want to cross the limit boundaries.
if ( writer.model.schema.isLimit( startParent ) || writer.model.schema.isLimit( endParent ) ) {
// If the start element on the common ancestor level is empty, and the end element on the same level is not empty
// then merge those to the right element so that it's properties are preserved (name, attributes).
// Because of OT merging is used instead of removing elements.
//
// Merge left:
// <heading1>foo[</heading1> -> <heading1>foo[]bar</heading1>
// <paragraph>]bar</paragraph> -> --^
//
// Merge right:
// <heading1>[</heading1> ->
// <paragraph>]bar</paragraph> -> <paragraph>[]bar</paragraph>
//
// Merge left:
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> <heading1>foo[]bar</heading1>
// <paragraph>]bar</paragraph> -> --^
// </blockQuote> -> </blockQuote>
//
// Merge right:
// <blockQuote> -> <blockQuote>
// <heading1>[</heading1> ->
// <paragraph>]bar</paragraph> -> <paragraph>[]bar</paragraph>
// </blockQuote> -> </blockQuote>
// Merging should not go deeper than common ancestor.
const [ startAncestor, endAncestor ] = getAncestorsJustBelowCommonAncestor( startPosition, endPosition );
if ( !model.hasContent( startAncestor, { ignoreMarkers: true } ) && model.hasContent( endAncestor, { ignoreMarkers: true } ) ) {
mergeBranchesRight( writer, startPosition, endPosition, startAncestor.parent );
} else {
mergeBranchesLeft( writer, startPosition, endPosition, startAncestor.parent );
}
}
// Merging blocks to the left (properties of the left block are preserved).
// Simple example:
// <heading1>foo[</heading1> -> <heading1>foo[bar</heading1>]
// <paragraph>]bar</paragraph> -> --^
//
// Nested example:
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> <heading1>foo[bar</heading1>
// </blockQuote> -> </blockQuote>] ^
// <blockBlock> -> |
// <paragraph>]bar</paragraph> -> ---
// </blockBlock> ->
//
function mergeBranchesLeft( writer, startPosition, endPosition, commonAncestor ) {
const startElement = startPosition.parent;
const endElement = endPosition.parent;
// Merging reached the common ancestor element, stop here.
if ( startElement == commonAncestor || endElement == commonAncestor ) {
return;
}
// Check if operations we'll need to do won't need to cross object or limit boundaries.
// E.g., we can't merge endParent into startParent in this case:
// <limit><startParent>x[]</startParent></limit><endParent>{}</endParent>
if ( !checkCanBeMerged( startPos, endPos, writer.model.schema ) ) {
// Remember next positions to merge in next recursive step (also used as modification points pointers).
startPosition = writer.createPositionAfter( startElement );
endPosition = writer.createPositionBefore( endElement );
// Move endElement just after startElement if they aren't siblings.
if ( !endPosition.isEqual( startPosition ) ) {
//
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> <heading1>foo</heading1>[<paragraph>bar</paragraph>
// </blockQuote> -> </blockQuote> ^
// <blockBlock> -> <blockBlock> |
// <paragraph>]bar</paragraph> -> ] ---
// </blockBlock> -> </blockBlock>
//
writer.insert( endElement, startPosition );
}
// Merge two siblings (nodes on sides of startPosition):
//
// <blockQuote> -> <blockQuote>
// <heading1>foo</heading1>[<paragraph>bar</paragraph> -> <heading1>foo[bar</heading1>
// </blockQuote> -> </blockQuote>
// <blockBlock> -> <blockBlock>
// ] -> ]
// </blockBlock> -> </blockBlock>
//
// Or in simple case (without moving elements in above if):
// <heading1>foo</heading1>[<paragraph>bar</paragraph>] -> <heading1>foo[bar</heading1>]
//
writer.merge( startPosition );
// Remove empty end ancestors:
//
// <blockQuote> -> <blockQuote>
// <heading1>foo[bar</heading1> -> <heading1>foo[bar</heading1>
// </blockQuote> -> </blockQuote>
// <blockBlock> ->
// ] -> ]
// </blockBlock> ->
//
while ( endPosition.parent.isEmpty ) {
const parentToRemove = endPosition.parent;
endPosition = writer.createPositionBefore( parentToRemove );
writer.remove( parentToRemove );
}
// Verify if there is a need and possibility to merge next level.
if ( !checkShouldMerge( writer.model.schema, startPosition, endPosition ) ) {
return;
}
// Remember next positions to merge. For example:
// <a><b>x[]</b></a><c><d>{}y</d></c>
// will become:
// <a><b>xy</b>[]</a><c>{}</c>
startPos = writer.createPositionAfter( startParent );
endPos = writer.createPositionBefore( endParent );
// Continue merging next level (blockQuote with blockBlock in the examples above if it would not be empty and got removed).
mergeBranchesLeft( writer, startPosition, endPosition, commonAncestor );
}
if ( !endPos.isEqual( startPos ) ) {
// In this case, before we merge, we need to move `endParent` to the `startPos`:
// <a><b>x[]</b></a><c><d>{}y</d></c>
// becomes:
// <a><b>x</b>[]<d>y</d></a><c>{}</c>
writer.insert( endParent, startPos );
// Merging blocks to the right (properties of the right block are preserved).
// Simple example:
// <heading1>foo[</heading1> -> --v
// <paragraph>]bar</paragraph> -> [<paragraph>foo]bar</paragraph>
//
// Nested example:
// <blockQuote> ->
// <heading1>foo[</heading1> -> ---
// </blockQuote> -> |
// <blockBlock> -> [<blockBlock> v
// <paragraph>]bar</paragraph> -> <paragraph>foo]bar</paragraph>
// </blockBlock> -> </blockBlock>
//
function mergeBranchesRight( writer, startPosition, endPosition, commonAncestor ) {
const startElement = startPosition.parent;
const endElement = endPosition.parent;
// Merging reached the common ancestor element, stop here.
if ( startElement == commonAncestor || endElement == commonAncestor ) {
return;
}
// Merge two siblings:
// <a>x</a>[]<b>y</b> -> <a>xy</a> (the usual case)
// <a><b>x</b>[]<d>y</d></a><c></c> -> <a><b>xy</b>[]</a><c></c> (this is the "move parent" case shown above)
writer.merge( startPos );
// Remember next positions to merge in next recursive step (also used as modification points pointers).
startPosition = writer.createPositionAfter( startElement );
endPosition = writer.createPositionBefore( endElement );
// Move startElement just before endElement if they aren't siblings.
if ( !endPosition.isEqual( startPosition ) ) {
//
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> [ ---
// </blockQuote> -> </blockQuote> |
// <blockBlock> -> <blockBlock> v
// <paragraph>]bar</paragraph> -> <heading1>foo</heading1>]<paragraph>bar</paragraph>
// </blockBlock> -> </blockBlock>
//
writer.insert( startElement, endPosition );
}
// Remove empty end ancestors:
// <a>fo[o</a><b><a><c>bar]</c></a></b>
// becomes:
// <a>fo[]</a><b><a>{}</a></b>
// So we can remove <a> and <b>.
while ( endPos.parent.isEmpty ) {
const parentToRemove = endPos.parent;
//
// <blockQuote> ->
// [ -> [
// </blockQuote> ->
// <blockBlock> -> <blockBlock>
// <heading1>foo</heading1>]<paragraph>bar</paragraph> -> <heading1>foo</heading1>]<paragraph>bar</paragraph>
// </blockBlock> -> </blockBlock>
//
while ( startPosition.parent.isEmpty ) {
const parentToRemove = startPosition.parent;
endPos = writer.createPositionBefore( parentToRemove );
startPosition = writer.createPositionBefore( parentToRemove );

@@ -185,6 +362,79 @@ writer.remove( parentToRemove );

// Continue merging next level.
mergeBranches( writer, startPos, endPos );
// Update endPosition after inserting and removing elements.
endPosition = writer.createPositionBefore( endElement );
// Merge right two siblings (nodes on sides of endPosition):
// ->
// [ -> [
// ->
// <blockBlock> -> <blockBlock>
// <heading1>foo</heading1>]<paragraph>bar</paragraph> -> <paragraph>foo]bar</paragraph>
// </blockBlock> -> </blockBlock>
//
// Or in simple case (without moving elements in above if):
// [<heading1>foo</heading1>]<paragraph>bar</paragraph> -> [<heading1>foo]bar</heading1>
//
mergeRight( writer, endPosition );
// Verify if there is a need and possibility to merge next level.
if ( !checkShouldMerge( writer.model.schema, startPosition, endPosition ) ) {
return;
}
// Continue merging next level (blockQuote with blockBlock in the examples above if it would not be empty and got removed).
mergeBranchesRight( writer, startPosition, endPosition, commonAncestor );
}
// There is no right merge operation so we need to simulate it.
function mergeRight( writer, position ) {
const startElement = position.nodeBefore;
const endElement = position.nodeAfter;
if ( startElement.name != endElement.name ) {
writer.rename( startElement, endElement.name );
}
writer.clearAttributes( startElement );
writer.setAttributes( Object.fromEntries( endElement.getAttributes() ), startElement );
writer.merge( position );
}
// Verifies if merging is needed and possible. It's not needed if both positions are in the same element
// and it's not possible if some element is a limit or the range crosses a limit element.
function checkShouldMerge( schema, startPosition, endPosition ) {
const startElement = startPosition.parent;
const endElement = endPosition.parent;
// If both positions ended up in the same parent, then there's nothing more to merge:
// <$root><p>x[</p><p>]y</p></$root> => <$root><p>xy</p>[]</$root>
if ( startElement == endElement ) {
return false;
}
// If one of the positions is a limit element, then there's nothing to merge because we don't want to cross the limit boundaries.
if ( schema.isLimit( startElement ) || schema.isLimit( endElement ) ) {
return false;
}
// Check if operations we'll need to do won't need to cross object or limit boundaries.
// E.g., we can't merge endElement into startElement in this case:
// <limit><startElement>x[</startElement></limit><endElement>]</endElement>
return isCrossingLimitElement( startPosition, endPosition, schema );
}
// Returns the elements that are the ancestors of the provided positions that are direct children of the common ancestor.
function getAncestorsJustBelowCommonAncestor( positionA, positionB ) {
const ancestorsA = positionA.getAncestors();
const ancestorsB = positionB.getAncestors();
let i = 0;
while ( ancestorsA[ i ] && ancestorsA[ i ] == ancestorsB[ i ] ) {
i++;
}
return [ ancestorsA[ i ], ancestorsB[ i ] ];
}
function shouldAutoparagraph( schema, position ) {

@@ -203,3 +453,3 @@ const isTextAllowed = schema.checkChild( position, '$text' );

// Usually, widget and caption are marked as objects/limits in the schema, so in this case merging will be blocked.
function checkCanBeMerged( leftPos, rightPos, schema ) {
function isCrossingLimitElement( leftPos, rightPos, schema ) {
const rangeToCheck = new Range( leftPos, rightPos );

@@ -206,0 +456,0 @@

@@ -76,3 +76,3 @@ /**

} else {
writer.append( item._clone( true ), frag );
writer.append( writer.cloneElement( item, true ), frag );
}

@@ -79,0 +79,0 @@ }

@@ -85,3 +85,10 @@ /**

if ( correctedRange ) {
// "Selection fixing" algorithms sometimes get lost. In consequence, it may happen
// that a new range is returned but, in fact, it has the same positions as the original
// range anyway. If this range is not discarded, a new selection will be set and that,
// for instance, would destroy the selection attributes. Let's make sure that the post-fixer
// actually worked first before setting a new selection.
//
// https://github.com/ckeditor/ckeditor5/issues/6693
if ( correctedRange && !correctedRange.isEqual( modelRange ) ) {
ranges.push( correctedRange );

@@ -88,0 +95,0 @@ wasFixed = true;

@@ -120,2 +120,14 @@ /**

/**
* Creates a copy of the element and returns it. Created element has the same name and attributes as the original element.
* If clone is deep, the original element's children are also cloned. If not, then empty element is returned.
*
* @param {module:engine/model/element~Element} element The element to clone.
* @param {Boolean} [deep=true] If set to `true` clones element and all its children recursively. When set to `false`,
* element will be cloned without any child.
*/
cloneElement( element, deep = true ) {
return element._clone( deep );
}
/**
* Inserts item on given position.

@@ -122,0 +134,0 @@ *

@@ -933,2 +933,3 @@ /**

!isText( node1 ) && !isText( node2 ) &&
node1.nodeType !== Node.COMMENT_NODE && node2.nodeType !== Node.COMMENT_NODE &&
node1.tagName.toLowerCase() === node2.tagName.toLowerCase();

@@ -935,0 +936,0 @@ }

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc