element.\n\t *\t\t\t} );\n\t *\t\t} );\n\t *\n\t * Refer to the documentation of {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}\n\t * and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} to learn how to write\n\t * custom converters.\n\t *\n\t * @param {String} groupName The name of dispatchers group to add the converters to.\n\t * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}\n\t */\n\tfor( groupName ) {\n\t\tif ( !this._helpers.has( groupName ) ) {\n\t\t\t/**\n\t\t\t * Trying to add a converter to an unknown dispatchers group.\n\t\t\t *\n\t\t\t * @error conversion-for-unknown-group\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'conversion-for-unknown-group: Trying to add a converter to an unknown dispatchers group.', this );\n\t\t}\n\n\t\treturn this._helpers.get( groupName );\n\t}\n\n\t/**\n\t * Sets up converters between the model and the view that convert a model element to a view element (and vice versa).\n\t * For example, the model `Foo ` is `Foo
` in the view.\n\t *\n\t *\t\t// A simple conversion from the `paragraph` model element to the `` view element (and vice versa).\n\t *\t\teditor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );\n\t *\n\t *\t\t// Override other converters by specifying a converter definition with a higher priority.\n\t *\t\teditor.conversion.elementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );\n\t *\n\t *\t\t// View specified as an object instead of a string.\n\t *\t\teditor.conversion.elementToElement( {\n\t *\t\t\tmodel: 'fancyParagraph',\n\t *\t\t\tview: {\n\t *\t\t\t\tname: 'p',\n\t *\t\t\t\tclasses: 'fancy'\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t *\t\t// Use `upcastAlso` to define other view elements that should also be converted to a `paragraph` element.\n\t *\t\teditor.conversion.elementToElement( {\n\t *\t\t\tmodel: 'paragraph',\n\t *\t\t\tview: 'p',\n\t *\t\t\tupcastAlso: [\n\t *\t\t\t\t'div',\n\t *\t\t\t\t{\n\t *\t\t\t\t\t// Any element with the `display: block` style.\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\tdisplay: 'block'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t}\n\t *\t\t\t]\n\t *\t\t} );\n\t *\n\t *\t\t// `upcastAlso` set as callback enables a conversion of a wide range of different view elements.\n\t *\t\teditor.conversion.elementToElement( {\n\t *\t\t\tmodel: 'heading',\n\t *\t\t\tview: 'h2',\n\t *\t\t\t// Convert \"headling-like\" paragraphs to headings.\n\t *\t\t\tupcastAlso: viewElement => {\n\t *\t\t\t\tconst fontSize = viewElement.getStyle( 'font-size' );\n\t *\n\t *\t\t\t\tif ( !fontSize ) {\n\t *\t\t\t\t\treturn null;\n\t *\t\t\t\t}\n\t *\n\t *\t\t\t\tconst match = fontSize.match( /(\\d+)\\s*px/ );\n\t *\n\t *\t\t\t\tif ( !match ) {\n\t *\t\t\t\t\treturn null;\n\t *\t\t\t\t}\n\t *\n\t *\t\t\t\tconst size = Number( match[ 1 ] );\n\t *\n\t *\t\t\t\tif ( size > 26 ) {\n\t *\t\t\t\t\t// Returned value can be an object with the matched properties.\n\t *\t\t\t\t\t// These properties will be \"consumed\" during the conversion.\n\t *\t\t\t\t\t// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.\n\t *\n\t *\t\t\t\t\treturn { name: true, styles: [ 'font-size' ] };\n\t *\t\t\t\t}\n\t *\n\t *\t\t\t\treturn null;\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * `definition.model` is a `String` with a model element name to convert from or to.\n\t * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.\n\t *\n\t * @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.\n\t */\n\telementToElement( definition ) {\n\t\t// Set up downcast converter.\n\t\tthis.for( 'downcast' ).elementToElement( definition );\n\n\t\t// Set up upcast converter.\n\t\tfor ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {\n\t\t\tthis.for( 'upcast' )\n\t\t\t\t.elementToElement( {\n\t\t\t\t\tmodel,\n\t\t\t\t\tview,\n\t\t\t\t\tconverterPriority: definition.converterPriority\n\t\t\t\t} );\n\t\t}\n\t}\n\n\t/**\n\t * Sets up converters between the model and the view that convert a model attribute to a view element (and vice versa).\n\t * For example, a model text node with `\"Foo\"` as data and the `bold` attribute is `Foo ` in the view.\n\t *\n\t *\t\t// A simple conversion from the `bold=true` attribute to the `` view element (and vice versa).\n\t *\t\teditor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );\n\t *\n\t *\t\t// Override other converters by specifying a converter definition with a higher priority.\n\t *\t\teditor.conversion.attributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );\n\t *\n\t *\t\t// View specified as an object instead of a string.\n\t *\t\teditor.conversion.attributeToElement( {\n\t *\t\t\tmodel: 'bold',\n\t *\t\t\tview: {\n\t *\t\t\t\tname: 'span',\n\t *\t\t\t\tclasses: 'bold'\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t *\t\t// Use `config.model.name` to define the conversion only from a given node type, `$text` in this case.\n\t *\t\t// The same attribute on different elements may then be handled by a different converter.\n\t *\t\teditor.conversion.attributeToElement( {\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'textDecoration',\n\t *\t\t\t\tvalues: [ 'underline', 'lineThrough' ],\n\t *\t\t\t\tname: '$text'\n\t *\t\t\t},\n\t *\t\t\tview: {\n\t *\t\t\t\tunderline: {\n\t *\t\t\t\t\tname: 'span',\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'text-decoration': 'underline'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t},\n\t *\t\t\t\tlineThrough: {\n\t *\t\t\t\t\tname: 'span',\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'text-decoration': 'line-through'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t *\t\t// Use `upcastAlso` to define other view elements that should also be converted to the `bold` attribute.\n\t *\t\teditor.conversion.attributeToElement( {\n\t *\t\t\tmodel: 'bold',\n\t *\t\t\tview: 'strong',\n\t *\t\t\tupcastAlso: [\n\t *\t\t\t\t'b',\n\t *\t\t\t\t{\n\t *\t\t\t\t\tname: 'span',\n\t *\t\t\t\t\tclasses: 'bold'\n\t *\t\t\t\t},\n\t *\t\t\t\t{\n\t *\t\t\t\t\tname: 'span',\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'font-weight': 'bold'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t},\n\t *\t\t\t\tviewElement => {\n\t *\t\t\t\t\tconst fontWeight = viewElement.getStyle( 'font-weight' );\n\t *\n\t *\t\t\t\t\tif ( viewElement.is( 'span' ) && fontWeight && /\\d+/.test() && Number( fontWeight ) > 500 ) {\n\t *\t\t\t\t\t\t// Returned value can be an object with the matched properties.\n\t *\t\t\t\t\t\t// These properties will be \"consumed\" during the conversion.\n\t *\t\t\t\t\t\t// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.\n\t *\n\t *\t\t\t\t\t\treturn {\n\t *\t\t\t\t\t\t\tname: true,\n\t *\t\t\t\t\t\t\tstyles: [ 'font-weight' ]\n\t *\t\t\t\t\t\t};\n\t *\t\t\t\t\t}\n\t *\t\t\t\t}\n\t *\t\t\t]\n\t *\t\t} );\n\t *\n\t *\t\t// Conversion from and to a model attribute key whose value is an enum (`fontSize=big|small`).\n\t *\t\t// `upcastAlso` set as callback enables a conversion of a wide range of different view elements.\n\t *\t\teditor.conversion.attributeToElement( {\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'fontSize',\n\t *\t\t\t\tvalues: [ 'big', 'small' ]\n\t *\t\t\t},\n\t *\t\t\tview: {\n\t *\t\t\t\tbig: {\n\t *\t\t\t\t\tname: 'span',\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'font-size': '1.2em'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t},\n\t *\t\t\t\tsmall: {\n\t *\t\t\t\t\tname: 'span',\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'font-size': '0.8em'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t}\n\t *\t\t\t},\n\t *\t\t\tupcastAlso: {\n\t *\t\t\t\tbig: viewElement => {\n\t *\t\t\t\t\tconst fontSize = viewElement.getStyle( 'font-size' );\n\t *\n\t *\t\t\t\t\tif ( !fontSize ) {\n\t *\t\t\t\t\t\treturn null;\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\tconst match = fontSize.match( /(\\d+)\\s*px/ );\n\t *\n\t *\t\t\t\t\tif ( !match ) {\n\t *\t\t\t\t\t\treturn null;\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\tconst size = Number( match[ 1 ] );\n\t *\n\t *\t\t\t\t\tif ( viewElement.is( 'span' ) && size > 10 ) {\n\t *\t\t\t\t\t\t// Returned value can be an object with the matched properties.\n\t *\t\t\t\t\t\t// These properties will be \"consumed\" during the conversion.\n\t *\t\t\t\t\t\t// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.\n\t *\n\t *\t\t\t\t\t\treturn { name: true, styles: [ 'font-size' ] };\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\treturn null;\n\t *\t\t\t\t},\n\t *\t\t\t\tsmall: viewElement => {\n\t *\t\t\t\t\tconst fontSize = viewElement.getStyle( 'font-size' );\n\t *\n\t *\t\t\t\t\tif ( !fontSize ) {\n\t *\t\t\t\t\t\treturn null;\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\tconst match = fontSize.match( /(\\d+)\\s*px/ );\n\t *\n\t *\t\t\t\t\tif ( !match ) {\n\t *\t\t\t\t\t\treturn null;\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\tconst size = Number( match[ 1 ] );\n\t *\n\t *\t\t\t\t\tif ( viewElement.is( 'span' ) && size < 10 ) {\n\t *\t\t\t\t\t\t// Returned value can be an object with the matched properties.\n\t *\t\t\t\t\t\t// These properties will be \"consumed\" during the conversion.\n\t *\t\t\t\t\t\t// See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.\n\t *\n\t *\t\t\t\t\t\treturn { name: true, styles: [ 'font-size' ] };\n\t *\t\t\t\t\t}\n\t *\n\t *\t\t\t\t\treturn null;\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * The `definition.model` parameter specifies which model attribute should be converted from or to. It can be a `{ key, value }` object\n\t * describing the attribute key and value to convert or a `String` specifying just the attribute key (then `value` is set to `true`).\n\t * See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.\n\t *\n\t * @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.\n\t */\n\tattributeToElement( definition ) {\n\t\t// Set up downcast converter.\n\t\tthis.for( 'downcast' ).attributeToElement( definition );\n\n\t\t// Set up upcast converter.\n\t\tfor ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {\n\t\t\tthis.for( 'upcast' )\n\t\t\t\t.elementToAttribute( {\n\t\t\t\t\tview,\n\t\t\t\t\tmodel,\n\t\t\t\t\tconverterPriority: definition.converterPriority\n\t\t\t\t} );\n\t\t}\n\t}\n\n\t/**\n\t * Sets up converters between the model and the view that convert a model attribute to a view attribute (and vice versa).\n\t * For example, ` ` is converted to ` ` (the same attribute key and value).\n\t * This type of converters is intended to be used with {@link module:engine/model/element~Element model element} nodes.\n\t * To convert text attributes {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement converter`}\n\t * should be set up.\n\t *\n\t *\t\t// A simple conversion from the `source` model attribute to the `src` view attribute (and vice versa).\n\t *\t\teditor.conversion.attributeToAttribute( { model: 'source', view: 'src' } );\n\t *\n\t *\t\t// Attribute values are strictly specified.\n\t *\t\teditor.conversion.attributeToAttribute( {\n\t *\t\t\tmodel: {\n\t *\t\t\t\tname: 'image',\n\t *\t\t\t\tkey: 'aside',\n\t *\t\t\t\tvalues: [ 'aside' ]\n\t *\t\t\t},\n\t *\t\t\tview: {\n\t *\t\t\t\taside: {\n\t *\t\t\t\t\tname: 'img',\n\t *\t\t\t\t\tkey: 'class',\n\t *\t\t\t\t\tvalue: [ 'aside', 'half-size' ]\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t *\t\t// Set the style attribute.\n\t *\t\teditor.conversion.attributeToAttribute( {\n\t *\t\t\tmodel: {\n\t *\t\t\t\tname: 'image',\n\t *\t\t\t\tkey: 'aside',\n\t *\t\t\t\tvalues: [ 'aside' ]\n\t *\t\t\t},\n\t *\t\t\tview: {\n\t *\t\t\t\taside: {\n\t *\t\t\t\t\tname: 'img',\n\t *\t\t\t\t\tkey: 'style',\n\t *\t\t\t\t\tvalue: {\n\t *\t\t\t\t\t\tfloat: 'right',\n\t *\t\t\t\t\t\twidth: '50%',\n\t *\t\t\t\t\t\tmargin: '5px'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t *\t\t// Conversion from and to a model attribute key whose value is an enum (`align=right|center`).\n\t *\t\t// Use `upcastAlso` to define other view elements that should also be converted to the `align=right` attribute.\n\t *\t\teditor.conversion.attributeToAttribute( {\n\t *\t\t\tmodel: {\n\t *\t\t\t\tkey: 'align',\n\t *\t\t\t\tvalues: [ 'right', 'center' ]\n\t *\t\t\t},\n\t *\t\t\tview: {\n\t *\t\t\t\tright: {\n\t *\t\t\t\t\tkey: 'class',\n\t *\t\t\t\t\tvalue: 'align-right'\n\t *\t\t\t\t},\n\t *\t\t\t\tcenter: {\n\t *\t\t\t\t\tkey: 'class',\n\t *\t\t\t\t\tvalue: 'align-center'\n\t *\t\t\t\t}\n\t *\t\t\t},\n\t *\t\t\tupcastAlso: {\n\t *\t\t\t\tright: {\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'text-align': 'right'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t},\n\t *\t\t\t\tcenter: {\n\t *\t\t\t\t\tstyles: {\n\t *\t\t\t\t\t\t'text-align': 'center'\n\t *\t\t\t\t\t}\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * The `definition.model` parameter specifies which model attribute should be converted from and to.\n\t * It can be a `{ key, [ values ], [ name ] }` object or a `String`, which will be treated like `{ key: definition.model }`.\n\t * The `key` property is the model attribute key to convert from and to.\n\t * The `values` are the possible model attribute values. If `values` is not set, the model attribute value will be the same as the\n\t * view attribute value.\n\t * If `name` is set, the conversion will be set up only for model elements with the given name.\n\t *\n\t * The `definition.view` parameter specifies which view attribute should be converted from and to.\n\t * It can be a `{ key, value, [ name ] }` object or a `String`, which will be treated like `{ key: definition.view }`.\n\t * The `key` property is the view attribute key to convert from and to.\n\t * The `value` is the view attribute value to convert from and to. If `definition.value` is not set, the view attribute value will be\n\t * the same as the model attribute value.\n\t * If `key` is `'class'`, `value` can be a `String` or an array of `String`s.\n\t * If `key` is `'style'`, `value` is an object with key-value pairs.\n\t * In other cases, `value` is a `String`.\n\t * If `name` is set, the conversion will be set up only for model elements with the given name.\n\t * If `definition.model.values` is set, `definition.view` is an object that assigns values from `definition.model.values`\n\t * to `{ key, value, [ name ] }` objects.\n\t *\n\t * `definition.upcastAlso` specifies which other matching view elements should also be upcast to the given model configuration.\n\t * If `definition.model.values` is set, `definition.upcastAlso` should be an object assigning values from `definition.model.values`\n\t * to {@link module:engine/view/matcher~MatcherPattern}s or arrays of {@link module:engine/view/matcher~MatcherPattern}s.\n\t *\n\t * **Note:** `definition.model` and `definition.view` form should be mirrored, so the same types of parameters should\n\t * be given in both parameters.\n\t *\n\t * @param {Object} definition The converter definition.\n\t * @param {String|Object} definition.model The model attribute to convert from and to.\n\t * @param {String|Object} definition.view The view attribute to convert from and to.\n\t * @param {module:engine/view/matcher~MatcherPattern|Array.} [definition.upcastAlso]\n\t * Any view element matching `definition.upcastAlso` will also be converted to the given model attribute. `definition.upcastAlso`\n\t * is used only if `config.model.values` is specified.\n\t */\n\tattributeToAttribute( definition ) {\n\t\t// Set up downcast converter.\n\t\tthis.for( 'downcast' ).attributeToAttribute( definition );\n\n\t\t// Set up upcast converter.\n\t\tfor ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {\n\t\t\tthis.for( 'upcast' )\n\t\t\t\t.attributeToAttribute( {\n\t\t\t\t\tview,\n\t\t\t\t\tmodel\n\t\t\t\t} );\n\t\t}\n\t}\n\n\t/**\n\t * Creates and caches conversion helpers for given dispatchers group.\n\t *\n\t * @private\n\t * @param {Object} options\n\t * @param {String} options.name Group name.\n\t * @param {Array.} options.dispatchers\n\t * @param {Boolean} options.isDowncast\n\t */\n\t_createConversionHelpers( { name, dispatchers, isDowncast } ) {\n\t\tif ( this._helpers.has( name ) ) {\n\t\t\t/**\n\t\t\t * Trying to register a group name that has already been registered.\n\t\t\t *\n\t\t\t * @error conversion-group-exists\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'conversion-group-exists: Trying to register a group name that has already been registered.', this );\n\t\t}\n\n\t\tconst helpers = isDowncast ? new DowncastHelpers( dispatchers ) : new UpcastHelpers( dispatchers );\n\n\t\tthis._helpers.set( name, helpers );\n\t}\n}\n\n/**\n * Defines how the model should be converted from and to the view.\n *\n * @typedef {Object} module:engine/conversion/conversion~ConverterDefinition\n *\n * @property {*} [model] The model conversion definition. Describes the model element or model attribute to convert. This parameter differs\n * for different functions that accept `ConverterDefinition`. See the description of the function to learn how to set it.\n * @property {module:engine/view/elementdefinition~ElementDefinition|Object} view The definition of the view element to convert from and\n * to. If `model` describes multiple values, `view` is an object that assigns these values (`view` object keys) to view element definitions\n * (`view` object values).\n * @property {module:engine/view/matcher~MatcherPattern|Array.} [upcastAlso]\n * Any view element matching `upcastAlso` will also be converted to the model. If `model` describes multiple values, `upcastAlso`\n * is an object that assigns these values (`upcastAlso` object keys) to {@link module:engine/view/matcher~MatcherPattern}s\n * (`upcastAlso` object values).\n * @property {module:utils/priorities~PriorityString} [converterPriority] The converter priority.\n */\n\n// Helper function that creates a joint array out of an item passed in `definition.view` and items passed in\n// `definition.upcastAlso`.\n//\n// @param {module:engine/conversion/conversion~ConverterDefinition} definition\n// @returns {Array} Array containing view definitions.\nfunction* _getAllUpcastDefinitions( definition ) {\n\tif ( definition.model.values ) {\n\t\tfor ( const value of definition.model.values ) {\n\t\t\tconst model = { key: definition.model.key, value };\n\t\t\tconst view = definition.view[ value ];\n\t\t\tconst upcastAlso = definition.upcastAlso ? definition.upcastAlso[ value ] : undefined;\n\n\t\t\tyield* _getUpcastDefinition( model, view, upcastAlso );\n\t\t}\n\t} else {\n\t\tyield* _getUpcastDefinition( definition.model, definition.view, definition.upcastAlso );\n\t}\n}\n\nfunction* _getUpcastDefinition( model, view, upcastAlso ) {\n\tyield { model, view };\n\n\tif ( upcastAlso ) {\n\t\tupcastAlso = Array.isArray( upcastAlso ) ? upcastAlso : [ upcastAlso ];\n\n\t\tfor ( const upcastAlsoItem of upcastAlso ) {\n\t\t\tyield { model, view: upcastAlsoItem };\n\t\t}\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/batch\n */\n\n/**\n * A batch instance groups model changes ({@link module:engine/model/operation/operation~Operation operations}). All operations\n * grouped in a single batch can be reverted together, so you can also think about a batch as of a single undo step. If you want\n * to extend a given undo step, you can add more changes to the batch using {@link module:engine/model/model~Model#enqueueChange}:\n *\n *\t\tmodel.enqueueChange( batch, writer => {\n *\t\t\twriter.insertText( 'foo', paragraph, 'end' );\n *\t\t} );\n *\n * @see module:engine/model/model~Model#enqueueChange\n * @see module:engine/model/model~Model#change\n */\nexport default class Batch {\n\t/**\n\t * Creates a batch instance.\n\t *\n\t * @see module:engine/model/model~Model#enqueueChange\n\t * @see module:engine/model/model~Model#change\n\t * @param {'transparent'|'default'} [type='default'] The type of the batch.\n\t */\n\tconstructor( type = 'default' ) {\n\t\t/**\n\t\t * An array of operations that compose this batch.\n\t\t *\n\t\t * @readonly\n\t\t * @type {Array.}\n\t\t */\n\t\tthis.operations = [];\n\n\t\t/**\n\t\t * The type of the batch.\n\t\t *\n\t\t * It can be one of the following values:\n\t\t * * `'default'` – All \"normal\" batches. This is the most commonly used type.\n\t\t * * `'transparent'` – A batch that should be ignored by other features, i.e. an initial batch or collaborative editing\n\t\t * changes.\n\t\t *\n\t\t * @readonly\n\t\t * @type {'transparent'|'default'}\n\t\t */\n\t\tthis.type = type;\n\t}\n\n\t/**\n\t * Returns the base version of this batch, which is equal to the base version of the first operation in the batch.\n\t * If there are no operations in the batch or neither operation has the base version set, it returns `null`.\n\t *\n\t * @readonly\n\t * @type {Number|null}\n\t */\n\tget baseVersion() {\n\t\tfor ( const op of this.operations ) {\n\t\t\tif ( op.baseVersion !== null ) {\n\t\t\t\treturn op.baseVersion;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Adds an operation to the batch instance.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} operation An operation to add.\n\t * @returns {module:engine/model/operation/operation~Operation} The added operation.\n\t */\n\taddOperation( operation ) {\n\t\toperation.batch = this;\n\t\tthis.operations.push( operation );\n\n\t\treturn operation;\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/operation\n */\n\n/**\n * Abstract base operation class.\n *\n * @abstract\n */\nexport default class Operation {\n\t/**\n\t * Base operation constructor.\n\t *\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( baseVersion ) {\n\t\t/**\n\t\t * {@link module:engine/model/document~Document#version} on which operation can be applied. If you try to\n\t\t * {@link module:engine/model/model~Model#applyOperation apply} operation with different base version than the\n\t\t * {@link module:engine/model/document~Document#version document version} the\n\t\t * {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.\n\t\t *\n\t\t * @member {Number}\n\t\t */\n\t\tthis.baseVersion = baseVersion;\n\n\t\t/**\n\t\t * Defines whether operation is executed on attached or detached {@link module:engine/model/item~Item items}.\n\t\t *\n\t\t * @readonly\n\t\t * @member {Boolean} #isDocumentOperation\n\t\t */\n\t\tthis.isDocumentOperation = this.baseVersion !== null;\n\n\t\t/**\n\t\t * {@link module:engine/model/batch~Batch Batch} to which the operation is added or `null` if the operation is not\n\t\t * added to any batch yet.\n\t\t *\n\t\t * @member {module:engine/model/batch~Batch|null} #batch\n\t\t */\n\t\tthis.batch = null;\n\n\t\t/**\n\t\t * Operation type.\n\t\t *\n\t\t * @readonly\n\t\t * @member {String} #type\n\t\t */\n\n\t\t/**\n\t\t * Creates and returns an operation that has the same parameters as this operation.\n\t\t *\n\t\t * @method #clone\n\t\t * @returns {module:engine/model/operation/operation~Operation} Clone of this operation.\n\t\t */\n\n\t\t/**\n\t\t * Creates and returns a reverse operation. Reverse operation when executed right after\n\t\t * the original operation will bring back tree model state to the point before the original\n\t\t * operation execution. In other words, it reverses changes done by the original operation.\n\t\t *\n\t\t * Keep in mind that tree model state may change since executing the original operation,\n\t\t * so reverse operation will be \"outdated\". In that case you will need to transform it by\n\t\t * all operations that were executed after the original operation.\n\t\t *\n\t\t * @method #getReversed\n\t\t * @returns {module:engine/model/operation/operation~Operation} Reversed operation.\n\t\t */\n\n\t\t/**\n\t\t * Executes the operation - modifications described by the operation properties will be applied to the model tree.\n\t\t *\n\t\t * @protected\n\t\t * @method #_execute\n\t\t */\n\t}\n\n\t/**\n\t * Checks whether the operation's parameters are correct and the operation can be correctly executed. Throws\n\t * an error if operation is not valid.\n\t *\n\t * @protected\n\t * @method #_validate\n\t */\n\t_validate() {\n\t}\n\n\t/**\n\t * Custom toJSON method to solve child-parent circular dependencies.\n\t *\n\t * @method #toJSON\n\t * @returns {Object} Clone of this object with the operation property replaced with string.\n\t */\n\ttoJSON() {\n\t\t// This method creates only a shallow copy, all nested objects should be defined separately.\n\t\t// See https://github.com/ckeditor/ckeditor5-engine/issues/1477.\n\t\tconst json = Object.assign( {}, this );\n\n\t\tjson.__className = this.constructor.className;\n\n\t\t// Remove reference to the parent `Batch` to avoid circular dependencies.\n\t\tdelete json.batch;\n\n\t\t// Only document operations are shared with other clients so it is not necessary to keep this information.\n\t\tdelete json.isDocumentOperation;\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * Name of the operation class used for serialization.\n\t *\n\t * @type {String}\n\t */\n\tstatic get className() {\n\t\treturn 'Operation';\n\t}\n\n\t/**\n\t * Creates Operation object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} doc Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/operation~Operation}\n\t */\n\tstatic fromJSON( json ) {\n\t\treturn new this( json.baseVersion );\n\t}\n\n\t// @if CK_DEBUG_ENGINE // log() {\n\t// @if CK_DEBUG_ENGINE // \tconsole.log( this.toString() );\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module module:engine/model/documentfragment\n */\n\nimport NodeList from './nodelist';\nimport Element from './element';\nimport Text from './text';\nimport TextProxy from './textproxy';\nimport isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';\n\n// @if CK_DEBUG_ENGINE // const { stringifyMap } = require( '../dev-utils/utils' );\n\n/**\n * DocumentFragment represents a part of model which does not have a common root but it's top-level nodes\n * can be seen as siblings. In other words, it is a detached part of model tree, without a root.\n *\n * DocumentFragment has own {@link module:engine/model/markercollection~MarkerCollection}. Markers from this collection\n * will be set to the {@link module:engine/model/model~Model#markers model markers} by a\n * {@link module:engine/model/writer~Writer#insert} function.\n */\nexport default class DocumentFragment {\n\t/**\n\t * Creates an empty `DocumentFragment`.\n\t *\n\t * **Note:** Constructor of this class shouldn't be used directly in the code.\n\t * Use the {@link module:engine/model/writer~Writer#createDocumentFragment} method instead.\n\t *\n\t * @protected\n\t * @param {module:engine/model/node~Node|Iterable.} [children]\n\t * Nodes to be contained inside the `DocumentFragment`.\n\t */\n\tconstructor( children ) {\n\t\t/**\n\t\t * DocumentFragment static markers map. This is a list of names and {@link module:engine/model/range~Range ranges}\n\t\t * which will be set as Markers to {@link module:engine/model/model~Model#markers model markers collection}\n\t\t * when DocumentFragment will be inserted to the document.\n\t\t *\n\t\t * @readonly\n\t\t * @member {Map} module:engine/model/documentfragment~DocumentFragment#markers\n\t\t */\n\t\tthis.markers = new Map();\n\n\t\t/**\n\t\t * List of nodes contained inside the document fragment.\n\t\t *\n\t\t * @private\n\t\t * @member {module:engine/model/nodelist~NodeList} module:engine/model/documentfragment~DocumentFragment#_children\n\t\t */\n\t\tthis._children = new NodeList();\n\n\t\tif ( children ) {\n\t\t\tthis._insertChild( 0, children );\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that iterates over all nodes contained inside this document fragment.\n\t *\n\t * @returns {Iterable.}\n\t */\n\t[ Symbol.iterator ]() {\n\t\treturn this.getChildren();\n\t}\n\n\t/**\n\t * Number of this document fragment's children.\n\t *\n\t * @readonly\n\t * @type {Number}\n\t */\n\tget childCount() {\n\t\treturn this._children.length;\n\t}\n\n\t/**\n\t * Sum of {@link module:engine/model/node~Node#offsetSize offset sizes} of all of this document fragment's children.\n\t *\n\t * @readonly\n\t * @type {Number}\n\t */\n\tget maxOffset() {\n\t\treturn this._children.maxOffset;\n\t}\n\n\t/**\n\t * Is `true` if there are no nodes inside this document fragment, `false` otherwise.\n\t *\n\t * @readonly\n\t * @type {Boolean}\n\t */\n\tget isEmpty() {\n\t\treturn this.childCount === 0;\n\t}\n\n\t/**\n\t * Artificial root of `DocumentFragment`. Returns itself. Added for compatibility reasons.\n\t *\n\t * @readonly\n\t * @type {module:engine/model/documentfragment~DocumentFragment}\n\t */\n\tget root() {\n\t\treturn this;\n\t}\n\n\t/**\n\t * Artificial parent of `DocumentFragment`. Returns `null`. Added for compatibility reasons.\n\t *\n\t * @readonly\n\t * @type {null}\n\t */\n\tget parent() {\n\t\treturn null;\n\t}\n\n\t/**\n\t * Checks whether this object is of the given type.\n\t *\n\t *\t\tdocFrag.is( 'documentFragment' ); // -> true\n\t *\t\tdocFrag.is( 'model:documentFragment' ); // -> true\n\t *\n\t *\t\tdocFrag.is( 'view:documentFragment' ); // -> false\n\t *\t\tdocFrag.is( 'element' ); // -> false\n\t *\t\tdocFrag.is( 'node' ); // -> false\n\t *\n\t * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.\n\t *\n\t * @param {String} type\n\t * @returns {Boolean}\n\t */\n\tis( type ) {\n\t\treturn type == 'documentFragment' || type == 'model:documentFragment';\n\t}\n\n\t/**\n\t * Gets the child at the given index. Returns `null` if incorrect index was passed.\n\t *\n\t * @param {Number} index Index of child.\n\t * @returns {module:engine/model/node~Node|null} Child node.\n\t */\n\tgetChild( index ) {\n\t\treturn this._children.getNode( index );\n\t}\n\n\t/**\n\t * Returns an iterator that iterates over all of this document fragment's children.\n\t *\n\t * @returns {Iterable.}\n\t */\n\tgetChildren() {\n\t\treturn this._children[ Symbol.iterator ]();\n\t}\n\n\t/**\n\t * Returns an index of the given child node. Returns `null` if given node is not a child of this document fragment.\n\t *\n\t * @param {module:engine/model/node~Node} node Child node to look for.\n\t * @returns {Number|null} Child node's index.\n\t */\n\tgetChildIndex( node ) {\n\t\treturn this._children.getNodeIndex( node );\n\t}\n\n\t/**\n\t * Returns the starting offset of given child. Starting offset is equal to the sum of\n\t * {@link module:engine/model/node~Node#offsetSize offset sizes} of all node's siblings that are before it. Returns `null` if\n\t * given node is not a child of this document fragment.\n\t *\n\t * @param {module:engine/model/node~Node} node Child node to look for.\n\t * @returns {Number|null} Child node's starting offset.\n\t */\n\tgetChildStartOffset( node ) {\n\t\treturn this._children.getNodeStartOffset( node );\n\t}\n\n\t/**\n\t * Returns path to a `DocumentFragment`, which is an empty array. Added for compatibility reasons.\n\t *\n\t * @returns {Array}\n\t */\n\tgetPath() {\n\t\treturn [];\n\t}\n\n\t/**\n\t * Returns a descendant node by its path relative to this element.\n\t *\n\t *\t\t// ac \n\t *\t\tthis.getNodeByPath( [ 0 ] ); // -> \"a\"\n\t *\t\tthis.getNodeByPath( [ 1 ] ); // -> \n\t *\t\tthis.getNodeByPath( [ 1, 0 ] ); // -> \"c\"\n\t *\n\t * @param {Array.} relativePath Path of the node to find, relative to this element.\n\t * @returns {module:engine/model/node~Node|module:engine/model/documentfragment~DocumentFragment}\n\t */\n\tgetNodeByPath( relativePath ) {\n\t\tlet node = this; // eslint-disable-line consistent-this\n\n\t\tfor ( const index of relativePath ) {\n\t\t\tnode = node.getChild( node.offsetToIndex( index ) );\n\t\t}\n\n\t\treturn node;\n\t}\n\n\t/**\n\t * Converts offset \"position\" to index \"position\".\n\t *\n\t * Returns index of a node that occupies given offset. If given offset is too low, returns `0`. If given offset is\n\t * too high, returns index after last child}.\n\t *\n\t *\t\tconst textNode = new Text( 'foo' );\n\t *\t\tconst pElement = new Element( 'p' );\n\t *\t\tconst docFrag = new DocumentFragment( [ textNode, pElement ] );\n\t *\t\tdocFrag.offsetToIndex( -1 ); // Returns 0, because offset is too low.\n\t *\t\tdocFrag.offsetToIndex( 0 ); // Returns 0, because offset 0 is taken by `textNode` which is at index 0.\n\t *\t\tdocFrag.offsetToIndex( 1 ); // Returns 0, because `textNode` has `offsetSize` equal to 3, so it occupies offset 1 too.\n\t *\t\tdocFrag.offsetToIndex( 2 ); // Returns 0.\n\t *\t\tdocFrag.offsetToIndex( 3 ); // Returns 1.\n\t *\t\tdocFrag.offsetToIndex( 4 ); // Returns 2. There are no nodes at offset 4, so last available index is returned.\n\t *\n\t * @param {Number} offset Offset to look for.\n\t * @returns {Number} Index of a node that occupies given offset.\n\t */\n\toffsetToIndex( offset ) {\n\t\treturn this._children.offsetToIndex( offset );\n\t}\n\n\t/**\n\t * Converts `DocumentFragment` instance to plain object and returns it.\n\t * Takes care of converting all of this document fragment's children.\n\t *\n\t * @returns {Object} `DocumentFragment` instance converted to plain object.\n\t */\n\ttoJSON() {\n\t\tconst json = [];\n\n\t\tfor ( const node of this._children ) {\n\t\t\tjson.push( node.toJSON() );\n\t\t}\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * Creates a `DocumentFragment` instance from given plain object (i.e. parsed JSON string).\n\t * Converts `DocumentFragment` children to proper nodes.\n\t *\n\t * @param {Object} json Plain object to be converted to `DocumentFragment`.\n\t * @returns {module:engine/model/documentfragment~DocumentFragment} `DocumentFragment` instance created using given plain object.\n\t */\n\tstatic fromJSON( json ) {\n\t\tconst children = [];\n\n\t\tfor ( const child of json ) {\n\t\t\tif ( child.name ) {\n\t\t\t\t// If child has name property, it is an Element.\n\t\t\t\tchildren.push( Element.fromJSON( child ) );\n\t\t\t} else {\n\t\t\t\t// Otherwise, it is a Text node.\n\t\t\t\tchildren.push( Text.fromJSON( child ) );\n\t\t\t}\n\t\t}\n\n\t\treturn new DocumentFragment( children );\n\t}\n\n\t/**\n\t * {@link #_insertChild Inserts} one or more nodes at the end of this document fragment.\n\t *\n\t * @protected\n\t * @param {module:engine/model/item~Item|Iterable.} items Items to be inserted.\n\t */\n\t_appendChild( items ) {\n\t\tthis._insertChild( this.childCount, items );\n\t}\n\n\t/**\n\t * Inserts one or more nodes at the given index and sets {@link module:engine/model/node~Node#parent parent} of these nodes\n\t * to this document fragment.\n\t *\n\t * @protected\n\t * @param {Number} index Index at which nodes should be inserted.\n\t * @param {module:engine/model/item~Item|Iterable.} items Items to be inserted.\n\t */\n\t_insertChild( index, items ) {\n\t\tconst nodes = normalize( items );\n\n\t\tfor ( const node of nodes ) {\n\t\t\t// If node that is being added to this element is already inside another element, first remove it from the old parent.\n\t\t\tif ( node.parent !== null ) {\n\t\t\t\tnode._remove();\n\t\t\t}\n\n\t\t\tnode.parent = this;\n\t\t}\n\n\t\tthis._children._insertNodes( index, nodes );\n\t}\n\n\t/**\n\t * Removes one or more nodes starting at the given index\n\t * and sets {@link module:engine/model/node~Node#parent parent} of these nodes to `null`.\n\t *\n\t * @protected\n\t * @param {Number} index Index of the first node to remove.\n\t * @param {Number} [howMany=1] Number of nodes to remove.\n\t * @returns {Array.} Array containing removed nodes.\n\t */\n\t_removeChildren( index, howMany = 1 ) {\n\t\tconst nodes = this._children._removeNodes( index, howMany );\n\n\t\tfor ( const node of nodes ) {\n\t\t\tnode.parent = null;\n\t\t}\n\n\t\treturn nodes;\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn 'documentFragment';\n\t// @if CK_DEBUG_ENGINE // }\n\n\t// @if CK_DEBUG_ENGINE // log() {\n\t// @if CK_DEBUG_ENGINE // \tconsole.log( 'ModelDocumentFragment: ' + this );\n\t// @if CK_DEBUG_ENGINE // }\n\n\t// @if CK_DEBUG_ENGINE // printTree() {\n\t// @if CK_DEBUG_ENGINE //\tlet string = 'ModelDocumentFragment: [';\n\n\t// @if CK_DEBUG_ENGINE //\tfor ( const child of this.getChildren() ) {\n\t// @if CK_DEBUG_ENGINE //\t\tstring += '\\n';\n\n\t// @if CK_DEBUG_ENGINE //\t\tif ( child.is( 'text' ) ) {\n\t// @if CK_DEBUG_ENGINE //\t\t\tconst textAttrs = stringifyMap( child._attrs );\n\n\t// @if CK_DEBUG_ENGINE //\t\t\tstring += '\\t'.repeat( 1 );\n\n\t// @if CK_DEBUG_ENGINE //\t\t\tif ( textAttrs !== '' ) {\n\t// @if CK_DEBUG_ENGINE //\t\t\t\tstring += `<$text${ textAttrs }>` + child.data + '$text>';\n\t// @if CK_DEBUG_ENGINE //\t\t\t} else {\n\t// @if CK_DEBUG_ENGINE //\t\t\t\tstring += child.data;\n\t// @if CK_DEBUG_ENGINE //\t\t\t}\n\t// @if CK_DEBUG_ENGINE //\t\t} else {\n\t// @if CK_DEBUG_ENGINE //\t\t\tstring += child.printTree( 1 );\n\t// @if CK_DEBUG_ENGINE //\t\t}\n\t// @if CK_DEBUG_ENGINE //\t}\n\n\t// @if CK_DEBUG_ENGINE //\tstring += '\\n]';\n\n\t// @if CK_DEBUG_ENGINE //\treturn string;\n\t// @if CK_DEBUG_ENGINE // }\n\n\t// @if CK_DEBUG_ENGINE // logTree() {\n\t// @if CK_DEBUG_ENGINE // \tconsole.log( this.printTree() );\n\t// @if CK_DEBUG_ENGINE // }\n}\n\n// Converts strings to Text and non-iterables to arrays.\n//\n// @param {String|module:engine/model/item~Item|Iterable.}\n// @returns {Iterable.}\nfunction normalize( nodes ) {\n\t// Separate condition because string is iterable.\n\tif ( typeof nodes == 'string' ) {\n\t\treturn [ new Text( nodes ) ];\n\t}\n\n\tif ( !isIterable( nodes ) ) {\n\t\tnodes = [ nodes ];\n\t}\n\n\t// Array.from to enable .map() on non-arrays.\n\treturn Array.from( nodes )\n\t\t.map( node => {\n\t\t\tif ( typeof node == 'string' ) {\n\t\t\t\treturn new Text( node );\n\t\t\t}\n\n\t\t\tif ( node instanceof TextProxy ) {\n\t\t\t\treturn new Text( node.data, node.getAttributes() );\n\t\t\t}\n\n\t\t\treturn node;\n\t\t} );\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/utils\n */\n\nimport Node from '../node';\nimport Text from '../text';\nimport TextProxy from '../textproxy';\nimport Range from '../range';\nimport DocumentFragment from '../documentfragment';\nimport NodeList from '../nodelist';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * Contains functions used for composing model tree by {@link module:engine/model/operation/operation~Operation operations}.\n * Those functions are built on top of {@link module:engine/model/node~Node node}, and it's child classes', APIs.\n *\n * @protected\n * @namespace utils\n */\n\n/**\n * Inserts given nodes at given position.\n *\n * @protected\n * @function module:engine/model/operation/utils~utils.insert\n * @param {module:engine/model/position~Position} position Position at which nodes should be inserted.\n * @param {module:engine/model/node~NodeSet} nodes Nodes to insert.\n * @returns {module:engine/model/range~Range} Range spanning over inserted elements.\n */\nexport function _insert( position, nodes ) {\n\tnodes = _normalizeNodes( nodes );\n\n\t// We have to count offset before inserting nodes because they can get merged and we would get wrong offsets.\n\tconst offset = nodes.reduce( ( sum, node ) => sum + node.offsetSize, 0 );\n\tconst parent = position.parent;\n\n\t// Insertion might be in a text node, we should split it if that's the case.\n\t_splitNodeAtPosition( position );\n\tconst index = position.index;\n\n\t// Insert nodes at given index. After splitting we have a proper index and insertion is between nodes,\n\t// using basic `Element` API.\n\tparent._insertChild( index, nodes );\n\n\t// Merge text nodes, if possible. Merging is needed only at points where inserted nodes \"touch\" \"old\" nodes.\n\t_mergeNodesAtIndex( parent, index + nodes.length );\n\t_mergeNodesAtIndex( parent, index );\n\n\treturn new Range( position, position.getShiftedBy( offset ) );\n}\n\n/**\n * Removed nodes in given range. Only {@link module:engine/model/range~Range#isFlat flat} ranges are accepted.\n *\n * @protected\n * @function module:engine/model/operation/utils~utils._remove\n * @param {module:engine/model/range~Range} range Range containing nodes to remove.\n * @returns {Array.}\n */\nexport function _remove( range ) {\n\tif ( !range.isFlat ) {\n\t\t/**\n\t\t * Trying to remove a range which starts and ends in different element.\n\t\t *\n\t\t * @error operation-utils-remove-range-not-flat\n\t\t */\n\t\tthrow new CKEditorError(\n\t\t\t'operation-utils-remove-range-not-flat: ' +\n\t\t\t'Trying to remove a range which starts and ends in different element.',\n\t\t\tthis\n\t\t);\n\t}\n\n\tconst parent = range.start.parent;\n\n\t// Range may be inside text nodes, we have to split them if that's the case.\n\t_splitNodeAtPosition( range.start );\n\t_splitNodeAtPosition( range.end );\n\n\t// Remove the text nodes using basic `Element` API.\n\tconst removed = parent._removeChildren( range.start.index, range.end.index - range.start.index );\n\n\t// Merge text nodes, if possible. After some nodes were removed, node before and after removed range will be\n\t// touching at the position equal to the removed range beginning. We check merging possibility there.\n\t_mergeNodesAtIndex( parent, range.start.index );\n\n\treturn removed;\n}\n\n/**\n * Moves nodes in given range to given target position. Only {@link module:engine/model/range~Range#isFlat flat} ranges are accepted.\n *\n * @protected\n * @function module:engine/model/operation/utils~utils.move\n * @param {module:engine/model/range~Range} sourceRange Range containing nodes to move.\n * @param {module:engine/model/position~Position} targetPosition Position to which nodes should be moved.\n * @returns {module:engine/model/range~Range} Range containing moved nodes.\n */\nexport function _move( sourceRange, targetPosition ) {\n\tif ( !sourceRange.isFlat ) {\n\t\t/**\n\t\t * Trying to move a range which starts and ends in different element.\n\t\t *\n\t\t * @error operation-utils-move-range-not-flat\n\t\t */\n\t\tthrow new CKEditorError(\n\t\t\t'operation-utils-move-range-not-flat: ' +\n\t\t\t'Trying to move a range which starts and ends in different element.',\n\t\t\tthis\n\t\t);\n\t}\n\n\tconst nodes = _remove( sourceRange );\n\n\t// We have to fix `targetPosition` because model changed after nodes from `sourceRange` got removed and\n\t// that change might have an impact on `targetPosition`.\n\ttargetPosition = targetPosition._getTransformedByDeletion( sourceRange.start, sourceRange.end.offset - sourceRange.start.offset );\n\n\treturn _insert( targetPosition, nodes );\n}\n\n/**\n * Sets given attribute on nodes in given range. The attributes are only set on top-level nodes of the range, not on its children.\n *\n * @protected\n * @function module:engine/model/operation/utils~utils._setAttribute\n * @param {module:engine/model/range~Range} range Range containing nodes that should have the attribute set. Must be a flat range.\n * @param {String} key Key of attribute to set.\n * @param {*} value Attribute value.\n */\nexport function _setAttribute( range, key, value ) {\n\t// Range might start or end in text nodes, so we have to split them.\n\t_splitNodeAtPosition( range.start );\n\t_splitNodeAtPosition( range.end );\n\n\t// Iterate over all items in the range.\n\tfor ( const item of range.getItems( { shallow: true } ) ) {\n\t\t// Iterator will return `TextProxy` instances but we know that those text proxies will\n\t\t// always represent full text nodes (this is guaranteed thanks to splitting we did before).\n\t\t// So, we can operate on those text proxies' text nodes.\n\t\tconst node = item.is( 'textProxy' ) ? item.textNode : item;\n\n\t\tif ( value !== null ) {\n\t\t\tnode._setAttribute( key, value );\n\t\t} else {\n\t\t\tnode._removeAttribute( key );\n\t\t}\n\n\t\t// After attributes changing it may happen that some text nodes can be merged. Try to merge with previous node.\n\t\t_mergeNodesAtIndex( node.parent, node.index );\n\t}\n\n\t// Try to merge last changed node with it's previous sibling (not covered by the loop above).\n\t_mergeNodesAtIndex( range.end.parent, range.end.index );\n}\n\n/**\n * Normalizes given object or an array of objects to an array of {@link module:engine/model/node~Node nodes}. See\n * {@link module:engine/model/node~NodeSet NodeSet} for details on how normalization is performed.\n *\n * @protected\n * @function module:engine/model/operation/utils~utils.normalizeNodes\n * @param {module:engine/model/node~NodeSet} nodes Objects to normalize.\n * @returns {Array.} Normalized nodes.\n */\nexport function _normalizeNodes( nodes ) {\n\tconst normalized = [];\n\n\tif ( !( nodes instanceof Array ) ) {\n\t\tnodes = [ nodes ];\n\t}\n\n\t// Convert instances of classes other than Node.\n\tfor ( let i = 0; i < nodes.length; i++ ) {\n\t\tif ( typeof nodes[ i ] == 'string' ) {\n\t\t\tnormalized.push( new Text( nodes[ i ] ) );\n\t\t} else if ( nodes[ i ] instanceof TextProxy ) {\n\t\t\tnormalized.push( new Text( nodes[ i ].data, nodes[ i ].getAttributes() ) );\n\t\t} else if ( nodes[ i ] instanceof DocumentFragment || nodes[ i ] instanceof NodeList ) {\n\t\t\tfor ( const child of nodes[ i ] ) {\n\t\t\t\tnormalized.push( child );\n\t\t\t}\n\t\t} else if ( nodes[ i ] instanceof Node ) {\n\t\t\tnormalized.push( nodes[ i ] );\n\t\t}\n\t\t// Skip unrecognized type.\n\t}\n\n\t// Merge text nodes.\n\tfor ( let i = 1; i < normalized.length; i++ ) {\n\t\tconst node = normalized[ i ];\n\t\tconst prev = normalized[ i - 1 ];\n\n\t\tif ( node instanceof Text && prev instanceof Text && _haveSameAttributes( node, prev ) ) {\n\t\t\t// Doing this instead changing `prev.data` because `data` is readonly.\n\t\t\tnormalized.splice( i - 1, 2, new Text( prev.data + node.data, prev.getAttributes() ) );\n\t\t\ti--;\n\t\t}\n\t}\n\n\treturn normalized;\n}\n\n// Checks if nodes before and after given index in given element are {@link module:engine/model/text~Text text nodes} and\n// merges them into one node if they have same attributes.\n//\n// Merging is done by removing two text nodes and inserting a new text node containing data from both merged text nodes.\n//\n// @private\n// @param {module:engine/model/element~Element} element Parent element of nodes to merge.\n// @param {Number} index Index between nodes to merge.\nfunction _mergeNodesAtIndex( element, index ) {\n\tconst nodeBefore = element.getChild( index - 1 );\n\tconst nodeAfter = element.getChild( index );\n\n\t// Check if both of those nodes are text objects with same attributes.\n\tif ( nodeBefore && nodeAfter && nodeBefore.is( 'text' ) && nodeAfter.is( 'text' ) && _haveSameAttributes( nodeBefore, nodeAfter ) ) {\n\t\t// Append text of text node after index to the before one.\n\t\tconst mergedNode = new Text( nodeBefore.data + nodeAfter.data, nodeBefore.getAttributes() );\n\n\t\t// Remove separate text nodes.\n\t\telement._removeChildren( index - 1, 2 );\n\n\t\t// Insert merged text node.\n\t\telement._insertChild( index - 1, mergedNode );\n\t}\n}\n\n// Checks if given position is in a text node, and if so, splits the text node in two text nodes, each of them\n// containing a part of original text node.\n//\n// @private\n// @param {module:engine/model/position~Position} position Position at which node should be split.\nfunction _splitNodeAtPosition( position ) {\n\tconst textNode = position.textNode;\n\tconst element = position.parent;\n\n\tif ( textNode ) {\n\t\tconst offsetDiff = position.offset - textNode.startOffset;\n\t\tconst index = textNode.index;\n\n\t\telement._removeChildren( index, 1 );\n\n\t\tconst firstPart = new Text( textNode.data.substr( 0, offsetDiff ), textNode.getAttributes() );\n\t\tconst secondPart = new Text( textNode.data.substr( offsetDiff ), textNode.getAttributes() );\n\n\t\telement._insertChild( index, [ firstPart, secondPart ] );\n\t}\n}\n\n// Checks whether two given nodes have same attributes.\n//\n// @private\n// @param {module:engine/model/node~Node} nodeA Node to check.\n// @param {module:engine/model/node~Node} nodeB Node to check.\n// @returns {Boolean} `true` if nodes have same attributes, `false` otherwise.\nfunction _haveSameAttributes( nodeA, nodeB ) {\n\tconst iteratorA = nodeA.getAttributes();\n\tconst iteratorB = nodeB.getAttributes();\n\n\tfor ( const attr of iteratorA ) {\n\t\tif ( attr[ 1 ] !== nodeB.getAttribute( attr[ 0 ] ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\titeratorB.next();\n\t}\n\n\treturn iteratorB.next().done;\n}\n\n/**\n * Value that can be normalized to an array of {@link module:engine/model/node~Node nodes}.\n *\n * Non-arrays are normalized as follows:\n * * {@link module:engine/model/node~Node Node} is left as is,\n * * {@link module:engine/model/textproxy~TextProxy TextProxy} and `String` are normalized to {@link module:engine/model/text~Text Text},\n * * {@link module:engine/model/nodelist~NodeList NodeList} is normalized to an array containing all nodes that are in that node list,\n * * {@link module:engine/model/documentfragment~DocumentFragment DocumentFragment} is normalized to an array containing all of it's\n * * children.\n *\n * Arrays are processed item by item like non-array values and flattened to one array. Normalization always results in\n * a flat array of {@link module:engine/model/node~Node nodes}. Consecutive text nodes (or items normalized to text nodes) will be\n * merged if they have same attributes.\n *\n * @typedef {module:engine/model/node~Node|module:engine/model/textproxy~TextProxy|String|\n * module:engine/model/nodelist~NodeList|module:engine/model/documentfragment~DocumentFragment|Iterable}\n * module:engine/model/node~NodeSet\n */\n","import baseIsEqual from './_baseIsEqual.js';\n\n/**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n *\n * **Note:** This method supports comparing arrays, array buffers, booleans,\n * date objects, error objects, maps, numbers, `Object` objects, regexes,\n * sets, strings, symbols, and typed arrays. `Object` objects are compared\n * by their own, not inherited, enumerable properties. Functions and DOM\n * nodes are compared by strict equality, i.e. `===`.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n */\nfunction isEqual(value, other) {\n return baseIsEqual(value, other);\n}\n\nexport default isEqual;\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/attributeoperation\n */\n\nimport Operation from './operation';\nimport Range from '../range';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\nimport { _setAttribute } from './utils';\nimport { isEqual } from 'lodash-es';\n\n/**\n * Operation to change nodes' attribute.\n *\n * Using this class you can add, remove or change value of the attribute.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class AttributeOperation extends Operation {\n\t/**\n\t * Creates an operation that changes, removes or adds attributes.\n\t *\n\t * If only `newValue` is set, attribute will be added on a node. Note that all nodes in operation's range must not\n\t * have an attribute with the same key as the added attribute.\n\t *\n\t * If only `oldValue` is set, then attribute with given key will be removed. Note that all nodes in operation's range\n\t * must have an attribute with that key added.\n\t *\n\t * If both `newValue` and `oldValue` are set, then the operation will change the attribute value. Note that all nodes in\n\t * operation's ranges must already have an attribute with given key and `oldValue` as value\n\t *\n\t * @param {module:engine/model/range~Range} range Range on which the operation should be applied. Must be a flat range.\n\t * @param {String} key Key of an attribute to change or remove.\n\t * @param {*} oldValue Old value of the attribute with given key or `null`, if attribute was not set before.\n\t * @param {*} newValue New value of the attribute with given key or `null`, if operation should remove attribute.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( range, key, oldValue, newValue, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Range on which operation should be applied.\n\t\t *\n\t\t * @readonly\n\t\t * @member {module:engine/model/range~Range}\n\t\t */\n\t\tthis.range = range.clone();\n\n\t\t/**\n\t\t * Key of an attribute to change or remove.\n\t\t *\n\t\t * @readonly\n\t\t * @member {String}\n\t\t */\n\t\tthis.key = key;\n\n\t\t/**\n\t\t * Old value of the attribute with given key or `null`, if attribute was not set before.\n\t\t *\n\t\t * @readonly\n\t\t * @member {*}\n\t\t */\n\t\tthis.oldValue = oldValue === undefined ? null : oldValue;\n\n\t\t/**\n\t\t * New value of the attribute with given key or `null`, if operation should remove attribute.\n\t\t *\n\t\t * @readonly\n\t\t * @member {*}\n\t\t */\n\t\tthis.newValue = newValue === undefined ? null : newValue;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\tif ( this.oldValue === null ) {\n\t\t\treturn 'addAttribute';\n\t\t} else if ( this.newValue === null ) {\n\t\t\treturn 'removeAttribute';\n\t\t} else {\n\t\t\treturn 'changeAttribute';\n\t\t}\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/attributeoperation~AttributeOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new AttributeOperation( this.range, this.key, this.oldValue, this.newValue, this.baseVersion );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}\n\t */\n\tgetReversed() {\n\t\treturn new AttributeOperation( this.range, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.range = this.range.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tif ( !this.range.isFlat ) {\n\t\t\t/**\n\t\t\t * The range to change is not flat.\n\t\t\t *\n\t\t\t * @error attribute-operation-range-not-flat\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'attribute-operation-range-not-flat: The range to change is not flat.', this );\n\t\t}\n\n\t\tfor ( const item of this.range.getItems( { shallow: true } ) ) {\n\t\t\tif ( this.oldValue !== null && !isEqual( item.getAttribute( this.key ), this.oldValue ) ) {\n\t\t\t\t/**\n\t\t\t\t * Changed node has different attribute value than operation's old attribute value.\n\t\t\t\t *\n\t\t\t\t * @error attribute-operation-wrong-old-value\n\t\t\t\t * @param {module:engine/model/item~Item} item\n\t\t\t\t * @param {String} key\n\t\t\t\t * @param {*} value\n\t\t\t\t */\n\t\t\t\tthrow new CKEditorError(\n\t\t\t\t\t'attribute-operation-wrong-old-value: Changed node has different attribute value than operation\\'s ' +\n\t\t\t\t\t'old attribute value.',\n\t\t\t\t\tthis,\n\t\t\t\t\t{ item, key: this.key, value: this.oldValue }\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif ( this.oldValue === null && this.newValue !== null && item.hasAttribute( this.key ) ) {\n\t\t\t\t/**\n\t\t\t\t * The attribute with given key already exists for the given node.\n\t\t\t\t *\n\t\t\t\t * @error attribute-operation-attribute-exists\n\t\t\t\t * @param {module:engine/model/node~Node} node\n\t\t\t\t * @param {String} key\n\t\t\t\t */\n\t\t\t\tthrow new CKEditorError(\n\t\t\t\t\t'attribute-operation-attribute-exists: The attribute with given key already exists.',\n\t\t\t\t\tthis,\n\t\t\t\t\t{ node: item, key: this.key }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\t// If value to set is same as old value, don't do anything.\n\t\tif ( !isEqual( this.oldValue, this.newValue ) ) {\n\t\t\t// Execution.\n\t\t\t_setAttribute( this.range, this.key, this.newValue );\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'AttributeOperation';\n\t}\n\n\t/**\n\t * Creates `AttributeOperation` object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\treturn new AttributeOperation( Range.fromJSON( json.range, document ), json.key, json.oldValue, json.newValue, json.baseVersion );\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `AttributeOperation( ${ this.baseVersion } ): ` +\n\t// @if CK_DEBUG_ENGINE //\t\t`\"${ this.key }\": ${ JSON.stringify( this.oldValue ) }` +\n\t// @if CK_DEBUG_ENGINE //\t\t` -> ${ JSON.stringify( this.newValue ) }, ${ this.range }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/detachoperation\n */\n\nimport Operation from './operation';\nimport Range from '../range';\nimport { _remove } from './utils';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n// @if CK_DEBUG_ENGINE // const ModelRange = require( '../range' ).default;\n\n/**\n * Operation to permanently remove node from detached root.\n * Note this operation is only a local operation and won't be send to the other clients.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class DetachOperation extends Operation {\n\t/**\n\t * Creates an insert operation.\n\t *\n\t * @param {module:engine/model/position~Position} sourcePosition\n\t * Position before the first {@link module:engine/model/item~Item model item} to move.\n\t * @param {Number} howMany Offset size of moved range. Moved range will start from `sourcePosition` and end at\n\t * `sourcePosition` with offset shifted by `howMany`.\n\t */\n\tconstructor( sourcePosition, howMany ) {\n\t\tsuper( null );\n\n\t\t/**\n\t\t * Position before the first {@link module:engine/model/item~Item model item} to detach.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} #sourcePosition\n\t\t */\n\t\tthis.sourcePosition = sourcePosition.clone();\n\n\t\t/**\n\t\t * Offset size of moved range.\n\t\t *\n\t\t * @member {Number} #howMany\n\t\t */\n\t\tthis.howMany = howMany;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\treturn 'detach';\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.sourcePosition = this.sourcePosition.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tif ( this.sourcePosition.root.document ) {\n\t\t\t/**\n\t\t\t * Cannot detach document node.\n\t\t\t *\n\t\t\t * @error detach-operation-on-document-node\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'detach-operation-on-document-node: Cannot detach document node.', this );\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\t_remove( Range._createFromPositionAndShift( this.sourcePosition, this.howMany ) );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'DetachOperation';\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // const range = ModelRange._createFromPositionAndShift( this.sourcePosition, this.howMany );\n\t// @if CK_DEBUG_ENGINE //\tconst nodes = Array.from( range.getItems() );\n\t// @if CK_DEBUG_ENGINE //\tconst nodeString = nodes.length > 1 ? `[ ${ nodes.length } ]` : nodes[ 0 ];\n\n\t// @if CK_DEBUG_ENGINE //\treturn `DetachOperation( ${ this.baseVersion } ): ${ nodeString } -> ${ range }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/moveoperation\n */\n\nimport Operation from './operation';\nimport Position from '../position';\nimport Range from '../range';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\nimport compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';\nimport { _move } from './utils';\n\n// @if CK_DEBUG_ENGINE // const ModelRange = require( '../range' ).default;\n\n/**\n * Operation to move a range of {@link module:engine/model/item~Item model items}\n * to given {@link module:engine/model/position~Position target position}.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class MoveOperation extends Operation {\n\t/**\n\t * Creates a move operation.\n\t *\n\t * @param {module:engine/model/position~Position} sourcePosition\n\t * Position before the first {@link module:engine/model/item~Item model item} to move.\n\t * @param {Number} howMany Offset size of moved range. Moved range will start from `sourcePosition` and end at\n\t * `sourcePosition` with offset shifted by `howMany`.\n\t * @param {module:engine/model/position~Position} targetPosition Position at which moved nodes will be inserted.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( sourcePosition, howMany, targetPosition, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Position before the first {@link module:engine/model/item~Item model item} to move.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/moveoperation~MoveOperation#sourcePosition\n\t\t */\n\t\tthis.sourcePosition = sourcePosition.clone();\n\t\t// `'toNext'` because `sourcePosition` is a bit like a start of the moved range.\n\t\tthis.sourcePosition.stickiness = 'toNext';\n\n\t\t/**\n\t\t * Offset size of moved range.\n\t\t *\n\t\t * @member {Number} module:engine/model/operation/moveoperation~MoveOperation#howMany\n\t\t */\n\t\tthis.howMany = howMany;\n\n\t\t/**\n\t\t * Position at which moved nodes will be inserted.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/moveoperation~MoveOperation#targetPosition\n\t\t */\n\t\tthis.targetPosition = targetPosition.clone();\n\t\tthis.targetPosition.stickiness = 'toNone';\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\tif ( this.targetPosition.root.rootName == '$graveyard' ) {\n\t\t\treturn 'remove';\n\t\t} else if ( this.sourcePosition.root.rootName == '$graveyard' ) {\n\t\t\treturn 'reinsert';\n\t\t}\n\n\t\treturn 'move';\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/moveoperation~MoveOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion );\n\t}\n\n\t/**\n\t * Returns the start position of the moved range after it got moved. This may be different than\n\t * {@link module:engine/model/operation/moveoperation~MoveOperation#targetPosition} in some cases, i.e. when a range is moved\n\t * inside the same parent but {@link module:engine/model/operation/moveoperation~MoveOperation#targetPosition targetPosition}\n\t * is after {@link module:engine/model/operation/moveoperation~MoveOperation#sourcePosition sourcePosition}.\n\t *\n\t *\t\t vv vv\n\t *\t\tabcdefg ===> adefbcg\n\t *\t\t ^ ^\n\t *\t\t targetPos\tmovedRangeStart\n\t *\t\t offset 6\toffset 4\n\t *\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tgetMovedRangeStart() {\n\t\treturn this.targetPosition._getTransformedByDeletion( this.sourcePosition, this.howMany );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/moveoperation~MoveOperation}\n\t */\n\tgetReversed() {\n\t\tconst newTargetPosition = this.sourcePosition._getTransformedByInsertion( this.targetPosition, this.howMany );\n\n\t\treturn new this.constructor( this.getMovedRangeStart(), this.howMany, newTargetPosition, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tconst sourceElement = this.sourcePosition.parent;\n\t\tconst targetElement = this.targetPosition.parent;\n\t\tconst sourceOffset = this.sourcePosition.offset;\n\t\tconst targetOffset = this.targetPosition.offset;\n\n\t\t// Validate whether move operation has correct parameters.\n\t\t// Validation is pretty complex but move operation is one of the core ways to manipulate the document state.\n\t\t// We expect that many errors might be connected with one of scenarios described below.\n\t\tif ( sourceOffset + this.howMany > sourceElement.maxOffset ) {\n\t\t\t/**\n\t\t\t * The nodes which should be moved do not exist.\n\t\t\t *\n\t\t\t * @error move-operation-nodes-do-not-exist\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'move-operation-nodes-do-not-exist: The nodes which should be moved do not exist.', this\n\t\t\t);\n\t\t} else if ( sourceElement === targetElement && sourceOffset < targetOffset && targetOffset < sourceOffset + this.howMany ) {\n\t\t\t/**\n\t\t\t * Trying to move a range of nodes into the middle of that range.\n\t\t\t *\n\t\t\t * @error move-operation-range-into-itself\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'move-operation-range-into-itself: Trying to move a range of nodes to the inside of that range.', this\n\t\t\t);\n\t\t} else if ( this.sourcePosition.root == this.targetPosition.root ) {\n\t\t\tif ( compareArrays( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == 'prefix' ) {\n\t\t\t\tconst i = this.sourcePosition.path.length - 1;\n\n\t\t\t\tif ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {\n\t\t\t\t\t/**\n\t\t\t\t\t * Trying to move a range of nodes into one of nodes from that range.\n\t\t\t\t\t *\n\t\t\t\t\t * @error move-operation-node-into-itself\n\t\t\t\t\t */\n\t\t\t\t\tthrow new CKEditorError(\n\t\t\t\t\t\t'move-operation-node-into-itself: Trying to move a range of nodes into one of nodes from that range.', this\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\t_move( Range._createFromPositionAndShift( this.sourcePosition, this.howMany ), this.targetPosition );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.sourcePosition = this.sourcePosition.toJSON();\n\t\tjson.targetPosition = this.targetPosition.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'MoveOperation';\n\t}\n\n\t/**\n\t * Creates `MoveOperation` object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/moveoperation~MoveOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\tconst sourcePosition = Position.fromJSON( json.sourcePosition, document );\n\t\tconst targetPosition = Position.fromJSON( json.targetPosition, document );\n\n\t\treturn new this( sourcePosition, json.howMany, targetPosition, json.baseVersion );\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \tconst range = ModelRange._createFromPositionAndShift( this.sourcePosition, this.howMany );\n\n\t// @if CK_DEBUG_ENGINE //\treturn `MoveOperation( ${ this.baseVersion } ): ${ range } -> ${ this.targetPosition }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/insertoperation\n */\n\nimport Operation from './operation';\nimport Position from '../position';\nimport NodeList from '../nodelist';\nimport MoveOperation from './moveoperation';\nimport { _insert, _normalizeNodes } from './utils';\nimport Text from '../text';\nimport Element from '../element';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * Operation to insert one or more nodes at given position in the model.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class InsertOperation extends Operation {\n\t/**\n\t * Creates an insert operation.\n\t *\n\t * @param {module:engine/model/position~Position} position Position of insertion.\n\t * @param {module:engine/model/node~NodeSet} nodes The list of nodes to be inserted.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( position, nodes, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Position of insertion.\n\t\t *\n\t\t * @readonly\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/insertoperation~InsertOperation#position\n\t\t */\n\t\tthis.position = position.clone();\n\t\tthis.position.stickiness = 'toNone';\n\n\t\t/**\n\t\t * List of nodes to insert.\n\t\t *\n\t\t * @readonly\n\t\t * @member {module:engine/model/nodelist~NodeList} module:engine/model/operation/insertoperation~InsertOperation#nodeList\n\t\t */\n\t\tthis.nodes = new NodeList( _normalizeNodes( nodes ) );\n\n\t\t/**\n\t\t * Flag deciding how the operation should be transformed. If set to `true`, nodes might get additional attributes\n\t\t * during operational transformation. This happens when the operation insertion position is inside of a range\n\t\t * where attributes have changed.\n\t\t *\n\t\t * @member {Boolean} module:engine/model/operation/insertoperation~InsertOperation#shouldReceiveAttributes\n\t\t */\n\t\tthis.shouldReceiveAttributes = false;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\treturn 'insert';\n\t}\n\n\t/**\n\t * Total offset size of inserted nodes.\n\t *\n\t * @returns {Number}\n\t */\n\tget howMany() {\n\t\treturn this.nodes.maxOffset;\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/insertoperation~InsertOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\tconst nodes = new NodeList( [ ...this.nodes ].map( node => node._clone( true ) ) );\n\t\tconst insert = new InsertOperation( this.position, nodes, this.baseVersion );\n\n\t\tinsert.shouldReceiveAttributes = this.shouldReceiveAttributes;\n\n\t\treturn insert;\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/moveoperation~MoveOperation}\n\t */\n\tgetReversed() {\n\t\tconst graveyard = this.position.root.document.graveyard;\n\t\tconst gyPosition = new Position( graveyard, [ 0 ] );\n\n\t\treturn new MoveOperation( this.position, this.nodes.maxOffset, gyPosition, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tconst targetElement = this.position.parent;\n\n\t\tif ( !targetElement || targetElement.maxOffset < this.position.offset ) {\n\t\t\t/**\n\t\t\t * Insertion position is invalid.\n\t\t\t *\n\t\t\t * @error insert-operation-position-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'insert-operation-position-invalid: Insertion position is invalid.',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\t// What happens here is that we want original nodes be passed to writer because we want original nodes\n\t\t// to be inserted to the model. But in InsertOperation, we want to keep those nodes as they were added\n\t\t// to the operation, not modified. For example, text nodes can get merged or cropped while Elements can\n\t\t// get children. It is important that InsertOperation has the copy of original nodes in intact state.\n\t\tconst originalNodes = this.nodes;\n\t\tthis.nodes = new NodeList( [ ...originalNodes ].map( node => node._clone( true ) ) );\n\n\t\t_insert( this.position, originalNodes );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.position = this.position.toJSON();\n\t\tjson.nodes = this.nodes.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'InsertOperation';\n\t}\n\n\t/**\n\t * Creates `InsertOperation` object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/insertoperation~InsertOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\tconst children = [];\n\n\t\tfor ( const child of json.nodes ) {\n\t\t\tif ( child.name ) {\n\t\t\t\t// If child has name property, it is an Element.\n\t\t\t\tchildren.push( Element.fromJSON( child ) );\n\t\t\t} else {\n\t\t\t\t// Otherwise, it is a Text node.\n\t\t\t\tchildren.push( Text.fromJSON( child ) );\n\t\t\t}\n\t\t}\n\n\t\tconst insert = new InsertOperation( Position.fromJSON( json.position, document ), children, json.baseVersion );\n\t\tinsert.shouldReceiveAttributes = json.shouldReceiveAttributes;\n\n\t\treturn insert;\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \tconst nodeString = this.nodes.length > 1 ? `[ ${ this.nodes.length } ]` : this.nodes.getNode( 0 );\n\n\t// @if CK_DEBUG_ENGINE //\treturn `InsertOperation( ${ this.baseVersion } ): ${ nodeString } -> ${ this.position }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/markeroperation\n */\n\nimport Operation from './operation';\nimport Range from '../range';\n\n/**\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class MarkerOperation extends Operation {\n\t/**\n\t * @param {String} name Marker name.\n\t * @param {module:engine/model/range~Range} oldRange Marker range before the change.\n\t * @param {module:engine/model/range~Range} newRange Marker range after the change.\n\t * @param {module:engine/model/markercollection~MarkerCollection} markers Marker collection on which change should be executed.\n\t * @param {Boolean} affectsData Specifies whether the marker operation affects the data produced by the data pipeline\n\t * (is persisted in the editor's data).\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( name, oldRange, newRange, markers, affectsData, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Marker name.\n\t\t *\n\t\t * @readonly\n\t\t * @member {String}\n\t\t */\n\t\tthis.name = name;\n\n\t\t/**\n\t\t * Marker range before the change.\n\t\t *\n\t\t * @readonly\n\t\t * @member {module:engine/model/range~Range}\n\t\t */\n\t\tthis.oldRange = oldRange ? oldRange.clone() : null;\n\n\t\t/**\n\t\t * Marker range after the change.\n\t\t *\n\t\t * @readonly\n\t\t * @member {module:engine/model/range~Range}\n\t\t */\n\t\tthis.newRange = newRange ? newRange.clone() : null;\n\n\t\t/**\n\t\t * Specifies whether the marker operation affects the data produced by the data pipeline\n\t\t * (is persisted in the editor's data).\n\t\t *\n\t\t * @readonly\n\t\t * @member {Boolean}\n\t\t */\n\t\tthis.affectsData = affectsData;\n\n\t\t/**\n\t\t * Marker collection on which change should be executed.\n\t\t *\n\t\t * @private\n\t\t * @member {module:engine/model/markercollection~MarkerCollection}\n\t\t */\n\t\tthis._markers = markers;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\treturn 'marker';\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/markeroperation~MarkerOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new MarkerOperation( this.name, this.oldRange, this.newRange, this._markers, this.affectsData, this.baseVersion );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/markeroperation~MarkerOperation}\n\t */\n\tgetReversed() {\n\t\treturn new MarkerOperation( this.name, this.newRange, this.oldRange, this._markers, this.affectsData, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\tconst type = this.newRange ? '_set' : '_remove';\n\n\t\tthis._markers[ type ]( this.name, this.newRange, true, this.affectsData );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tif ( this.oldRange ) {\n\t\t\tjson.oldRange = this.oldRange.toJSON();\n\t\t}\n\n\t\tif ( this.newRange ) {\n\t\t\tjson.newRange = this.newRange.toJSON();\n\t\t}\n\n\t\tdelete json._markers;\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'MarkerOperation';\n\t}\n\n\t/**\n\t * Creates `MarkerOperation` object from deserialized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/markeroperation~MarkerOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\treturn new MarkerOperation(\n\t\t\tjson.name,\n\t\t\tjson.oldRange ? Range.fromJSON( json.oldRange, document ) : null,\n\t\t\tjson.newRange ? Range.fromJSON( json.newRange, document ) : null,\n\t\t\tdocument.model.markers,\n\t\t\tjson.affectsData,\n\t\t\tjson.baseVersion\n\t\t);\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `MarkerOperation( ${ this.baseVersion } ): ` +\n\t// @if CK_DEBUG_ENGINE //\t\t`\"${ this.name }\": ${ this.oldRange } -> ${ this.newRange }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/renameoperation\n */\n\nimport Operation from './operation';\nimport Element from '../element';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\nimport Position from '../position';\n\n/**\n * Operation to change element's name.\n *\n * Using this class you can change element's name.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class RenameOperation extends Operation {\n\t/**\n\t * Creates an operation that changes element's name.\n\t *\n\t * @param {module:engine/model/position~Position} position Position before an element to change.\n\t * @param {String} oldName Current name of the element.\n\t * @param {String} newName New name for the element.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( position, oldName, newName, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Position before an element to change.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/renameoperation~RenameOperation#position\n\t\t */\n\t\tthis.position = position;\n\t\t// This position sticks to the next node because it is a position before the node that we want to change.\n\t\tthis.position.stickiness = 'toNext';\n\n\t\t/**\n\t\t * Current name of the element.\n\t\t *\n\t\t * @member {String} module:engine/model/operation/renameoperation~RenameOperation#oldName\n\t\t */\n\t\tthis.oldName = oldName;\n\n\t\t/**\n\t\t * New name for the element.\n\t\t *\n\t\t * @member {String} module:engine/model/operation/renameoperation~RenameOperation#newName\n\t\t */\n\t\tthis.newName = newName;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\treturn 'rename';\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/renameoperation~RenameOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new RenameOperation( this.position.clone(), this.oldName, this.newName, this.baseVersion );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/renameoperation~RenameOperation}\n\t */\n\tgetReversed() {\n\t\treturn new RenameOperation( this.position.clone(), this.newName, this.oldName, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tconst element = this.position.nodeAfter;\n\n\t\tif ( !( element instanceof Element ) ) {\n\t\t\t/**\n\t\t\t * Given position is invalid or node after it is not instance of Element.\n\t\t\t *\n\t\t\t * @error rename-operation-wrong-position\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'rename-operation-wrong-position: Given position is invalid or node after it is not an instance of Element.',\n\t\t\t\tthis\n\t\t\t);\n\t\t} else if ( element.name !== this.oldName ) {\n\t\t\t/**\n\t\t\t * Element to change has different name than operation's old name.\n\t\t\t *\n\t\t\t * @error rename-operation-wrong-name\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'rename-operation-wrong-name: Element to change has different name than operation\\'s old name.',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\tconst element = this.position.nodeAfter;\n\n\t\telement.name = this.newName;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.position = this.position.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'RenameOperation';\n\t}\n\n\t/**\n\t * Creates `RenameOperation` object from deserialized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\treturn new RenameOperation( Position.fromJSON( json.position, document ), json.oldName, json.newName, json.baseVersion );\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `RenameOperation( ${ this.baseVersion } ): ` +\n\t// @if CK_DEBUG_ENGINE //\t\t`${ this.position }: \"${ this.oldName }\" -> \"${ this.newName }\"`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/rootattributeoperation\n */\n\nimport Operation from './operation';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * Operation to change root element's attribute. Using this class you can add, remove or change value of the attribute.\n *\n * This operation is needed, because root elements can't be changed through\n * @link module:engine/model/operation/attributeoperation~AttributeOperation}.\n * It is because {@link module:engine/model/operation/attributeoperation~AttributeOperation}\n * requires a range to change and root element can't\n * be a part of range because every {@link module:engine/model/position~Position} has to be inside a root.\n * {@link module:engine/model/position~Position} can't be created before a root element.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class RootAttributeOperation extends Operation {\n\t/**\n\t * Creates an operation that changes, removes or adds attributes on root element.\n\t *\n\t * @see module:engine/model/operation/attributeoperation~AttributeOperation\n\t * @param {module:engine/model/rootelement~RootElement} root Root element to change.\n\t * @param {String} key Key of an attribute to change or remove.\n\t * @param {*} oldValue Old value of the attribute with given key or `null` if adding a new attribute.\n\t * @param {*} newValue New value to set for the attribute. If `null`, then the operation just removes the attribute.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( root, key, oldValue, newValue, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Root element to change.\n\t\t *\n\t\t * @readonly\n\t\t * @member {module:engine/model/rootelement~RootElement}\n\t\t */\n\t\tthis.root = root;\n\n\t\t/**\n\t\t * Key of an attribute to change or remove.\n\t\t *\n\t\t * @readonly\n\t\t * @member {String}\n\t\t */\n\t\tthis.key = key;\n\n\t\t/**\n\t\t * Old value of the attribute with given key or `null` if adding a new attribute.\n\t\t *\n\t\t * @readonly\n\t\t * @member {*}\n\t\t */\n\t\tthis.oldValue = oldValue;\n\n\t\t/**\n\t\t * New value to set for the attribute. If `null`, then the operation just removes the attribute.\n\t\t *\n\t\t * @readonly\n\t\t * @member {*}\n\t\t */\n\t\tthis.newValue = newValue;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\tif ( this.oldValue === null ) {\n\t\t\treturn 'addRootAttribute';\n\t\t} else if ( this.newValue === null ) {\n\t\t\treturn 'removeRootAttribute';\n\t\t} else {\n\t\t\treturn 'changeRootAttribute';\n\t\t}\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new RootAttributeOperation( this.root, this.key, this.oldValue, this.newValue, this.baseVersion );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}\n\t */\n\tgetReversed() {\n\t\treturn new RootAttributeOperation( this.root, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tif ( this.root != this.root.root || this.root.is( 'documentFragment' ) ) {\n\t\t\t/**\n\t\t\t * The element to change is not a root element.\n\t\t\t *\n\t\t\t * @error rootattribute-operation-not-a-root\n\t\t\t * @param {module:engine/model/rootelement~RootElement} root\n\t\t\t * @param {String} key\n\t\t\t * @param {*} value\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'rootattribute-operation-not-a-root: The element to change is not a root element.',\n\t\t\t\tthis,\n\t\t\t\t{ root: this.root, key: this.key }\n\t\t\t);\n\t\t}\n\n\t\tif ( this.oldValue !== null && this.root.getAttribute( this.key ) !== this.oldValue ) {\n\t\t\t/**\n\t\t\t * The attribute which should be removed does not exists for the given node.\n\t\t\t *\n\t\t\t * @error rootattribute-operation-wrong-old-value\n\t\t\t * @param {module:engine/model/rootelement~RootElement} root\n\t\t\t * @param {String} key\n\t\t\t * @param {*} value\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'rootattribute-operation-wrong-old-value: Changed node has different attribute value than operation\\'s ' +\n\t\t\t\t'old attribute value.',\n\t\t\t\tthis,\n\t\t\t\t{ root: this.root, key: this.key }\n\t\t\t);\n\t\t}\n\n\t\tif ( this.oldValue === null && this.newValue !== null && this.root.hasAttribute( this.key ) ) {\n\t\t\t/**\n\t\t\t * The attribute with given key already exists for the given node.\n\t\t\t *\n\t\t\t * @error rootattribute-operation-attribute-exists\n\t\t\t * @param {module:engine/model/rootelement~RootElement} root\n\t\t\t * @param {String} key\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'rootattribute-operation-attribute-exists: The attribute with given key already exists.',\n\t\t\t\tthis,\n\t\t\t\t{ root: this.root, key: this.key }\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\tif ( this.newValue !== null ) {\n\t\t\tthis.root._setAttribute( this.key, this.newValue );\n\t\t} else {\n\t\t\tthis.root._removeAttribute( this.key );\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.root = this.root.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'RootAttributeOperation';\n\t}\n\n\t/**\n\t * Creates RootAttributeOperation object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\tif ( !document.getRoot( json.root ) ) {\n\t\t\t/**\n\t\t\t * Cannot create RootAttributeOperation for document. Root with specified name does not exist.\n\t\t\t *\n\t\t\t * @error rootattributeoperation-fromjson-no-root\n\t\t\t * @param {String} rootName\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'rootattribute-operation-fromjson-no-root: Cannot create RootAttributeOperation. Root with specified name does not exist.',\n\t\t\t\tthis,\n\t\t\t\t{ rootName: json.root }\n\t\t\t);\n\t\t}\n\n\t\treturn new RootAttributeOperation( document.getRoot( json.root ), json.key, json.oldValue, json.newValue, json.baseVersion );\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `RootAttributeOperation( ${ this.baseVersion } ): ` +\n\t// @if CK_DEBUG_ENGINE //\t\t`\"${ this.key }\": ${ JSON.stringify( this.oldValue ) }` +\n\t// @if CK_DEBUG_ENGINE //\t\t` -> ${ JSON.stringify( this.newValue ) }, ${ this.root.rootName }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/mergeoperation\n */\n\nimport Operation from './operation';\nimport SplitOperation from './splitoperation';\nimport Position from '../position';\nimport Range from '../range';\nimport { _move } from './utils';\n\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * Operation to merge two {@link module:engine/model/element~Element elements}.\n *\n * The merged element is the parent of {@link ~MergeOperation#sourcePosition} and it is merged into the parent of\n * {@link ~MergeOperation#targetPosition}. All nodes from the merged element are moved to {@link ~MergeOperation#targetPosition}.\n *\n * The merged element is moved to the graveyard at {@link ~MergeOperation#graveyardPosition}.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class MergeOperation extends Operation {\n\t/**\n\t * Creates a merge operation.\n\t *\n\t * @param {module:engine/model/position~Position} sourcePosition Position inside the merged element. All nodes from that\n\t * element after that position will be moved to {@link ~#targetPosition}.\n\t * @param {Number} howMany Summary offset size of nodes which will be moved from the merged element to the new parent.\n\t * @param {module:engine/model/position~Position} targetPosition Position which the nodes from the merged elements will be moved to.\n\t * @param {module:engine/model/position~Position} graveyardPosition Position in graveyard to which the merged element will be moved.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( sourcePosition, howMany, targetPosition, graveyardPosition, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Position inside the merged element. All nodes from that element after that position will be moved to {@link ~#targetPosition}.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#sourcePosition\n\t\t */\n\t\tthis.sourcePosition = sourcePosition.clone();\n\t\t// This is, and should always remain, the first position in its parent.\n\t\tthis.sourcePosition.stickiness = 'toPrevious';\n\n\t\t/**\n\t\t * Summary offset size of nodes which will be moved from the merged element to the new parent.\n\t\t *\n\t\t * @member {Number} module:engine/model/operation/mergeoperation~MergeOperation#howMany\n\t\t */\n\t\tthis.howMany = howMany;\n\n\t\t/**\n\t\t * Position which the nodes from the merged elements will be moved to.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#targetPosition\n\t\t */\n\t\tthis.targetPosition = targetPosition.clone();\n\t\t// Except of a rare scenario in `MergeOperation` x `MergeOperation` transformation,\n\t\t// this is, and should always remain, the last position in its parent.\n\t\tthis.targetPosition.stickiness = 'toNext';\n\n\t\t/**\n\t\t * Position in graveyard to which the merged element will be moved.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#graveyardPosition\n\t\t */\n\t\tthis.graveyardPosition = graveyardPosition.clone();\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\treturn 'merge';\n\t}\n\n\t/**\n\t * Position before the merged element (which will be deleted).\n\t *\n\t * @readonly\n\t * @type {module:engine/model/position~Position}\n\t */\n\tget deletionPosition() {\n\t\treturn new Position( this.sourcePosition.root, this.sourcePosition.path.slice( 0, -1 ) );\n\t}\n\n\t/**\n\t * Artificial range that contains all the nodes from the merged element that will be moved to {@link ~MergeOperation#sourcePosition}.\n\t * The range starts at {@link ~MergeOperation#sourcePosition} and ends in the same parent, at `POSITIVE_INFINITY` offset.\n\t *\n\t * @readonly\n\t * @type {module:engine/model/range~Range}\n\t */\n\tget movedRange() {\n\t\tconst end = this.sourcePosition.getShiftedBy( Number.POSITIVE_INFINITY );\n\n\t\treturn new Range( this.sourcePosition, end );\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/mergeoperation~MergeOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.graveyardPosition, this.baseVersion );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/splitoperation~SplitOperation}\n\t */\n\tgetReversed() {\n\t\t// Positions in this method are transformed by this merge operation because the split operation bases on\n\t\t// the context after this merge operation happened (because split operation reverses it).\n\t\t// So we need to acknowledge that the merge operation happened and those positions changed a little.\n\t\tconst targetPosition = this.targetPosition._getTransformedByMergeOperation( this );\n\n\t\tconst path = this.sourcePosition.path.slice( 0, -1 );\n\t\tconst insertionPosition = new Position( this.sourcePosition.root, path )._getTransformedByMergeOperation( this );\n\n\t\tconst split = new SplitOperation( targetPosition, this.howMany, this.graveyardPosition, this.baseVersion + 1 );\n\t\tsplit.insertionPosition = insertionPosition;\n\n\t\treturn split;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tconst sourceElement = this.sourcePosition.parent;\n\t\tconst targetElement = this.targetPosition.parent;\n\n\t\t// Validate whether merge operation has correct parameters.\n\t\tif ( !sourceElement.parent ) {\n\t\t\t/**\n\t\t\t * Merge source position is invalid. The element to be merged must have a parent node.\n\t\t\t *\n\t\t\t * @error merge-operation-source-position-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'merge-operation-source-position-invalid: Merge source position is invalid.', this );\n\t\t} else if ( !targetElement.parent ) {\n\t\t\t/**\n\t\t\t * Merge target position is invalid. The element to be merged must have a parent node.\n\t\t\t *\n\t\t\t * @error merge-operation-target-position-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'merge-operation-target-position-invalid: Merge target position is invalid.', this );\n\t\t} else if ( this.howMany != sourceElement.maxOffset ) {\n\t\t\t/**\n\t\t\t * Merge operation specifies wrong number of nodes to move.\n\t\t\t *\n\t\t\t * @error merge-operation-how-many-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'merge-operation-how-many-invalid: Merge operation specifies wrong number of nodes to move.', this );\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\tconst mergedElement = this.sourcePosition.parent;\n\t\tconst sourceRange = Range._createIn( mergedElement );\n\n\t\t_move( sourceRange, this.targetPosition );\n\t\t_move( Range._createOn( mergedElement ), this.graveyardPosition );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.sourcePosition = json.sourcePosition.toJSON();\n\t\tjson.targetPosition = json.targetPosition.toJSON();\n\t\tjson.graveyardPosition = json.graveyardPosition.toJSON();\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'MergeOperation';\n\t}\n\n\t/**\n\t * Creates `MergeOperation` object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/mergeoperation~MergeOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\tconst sourcePosition = Position.fromJSON( json.sourcePosition, document );\n\t\tconst targetPosition = Position.fromJSON( json.targetPosition, document );\n\t\tconst graveyardPosition = Position.fromJSON( json.graveyardPosition, document );\n\n\t\treturn new this( sourcePosition, json.howMany, targetPosition, graveyardPosition, json.baseVersion );\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `MergeOperation( ${ this.baseVersion } ): ` +\n\t// @if CK_DEBUG_ENGINE //\t\t`${ this.sourcePosition } -> ${ this.targetPosition }` +\n\t// @if CK_DEBUG_ENGINE //\t\t` ( ${ this.howMany } ), ${ this.graveyardPosition }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/splitoperation\n */\n\nimport Operation from './operation';\nimport MergeOperation from './mergeoperation';\nimport Position from '../position';\nimport Range from '../range';\nimport { _insert, _move } from './utils';\n\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * Operation to split {@link module:engine/model/element~Element an element} at given\n * {@link module:engine/model/operation/splitoperation~SplitOperation#splitPosition split position} into two elements,\n * both containing a part of the element's original content.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class SplitOperation extends Operation {\n\t/**\n\t * Creates a split operation.\n\t *\n\t * @param {module:engine/model/position~Position} splitPosition Position at which an element should be split.\n\t * @param {Number} howMany Total offset size of elements that are in the split element after `position`.\n\t * @param {module:engine/model/position~Position|null} graveyardPosition Position in the graveyard root before the element which\n\t * should be used as a parent of the nodes after `position`. If it is not set, a copy of the the `position` parent will be used.\n\t * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation\n\t * can be applied or `null` if the operation operates on detached (non-document) tree.\n\t */\n\tconstructor( splitPosition, howMany, graveyardPosition, baseVersion ) {\n\t\tsuper( baseVersion );\n\n\t\t/**\n\t\t * Position at which an element should be split.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/splitoperation~SplitOperation#splitPosition\n\t\t */\n\t\tthis.splitPosition = splitPosition.clone();\n\t\t// Keep position sticking to the next node. This way any new content added at the place where the element is split\n\t\t// will be left in the original element.\n\t\tthis.splitPosition.stickiness = 'toNext';\n\n\t\t/**\n\t\t * Total offset size of elements that are in the split element after `position`.\n\t\t *\n\t\t * @member {Number} module:engine/model/operation/splitoperation~SplitOperation#howMany\n\t\t */\n\t\tthis.howMany = howMany;\n\n\t\t/**\n\t\t * Position at which the clone of split element (or element from graveyard) will be inserted.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} module:engine/model/operation/splitoperation~SplitOperation#insertionPosition\n\t\t */\n\t\tthis.insertionPosition = SplitOperation.getInsertionPosition( splitPosition );\n\t\tthis.insertionPosition.stickiness = 'toNone';\n\n\t\t/**\n\t\t * Position in the graveyard root before the element which should be used as a parent of the nodes after `position`.\n\t\t * If it is not set, a copy of the the `position` parent will be used.\n\t\t *\n\t\t * The default behavior is to clone the split element. Element from graveyard is used during undo.\n\t\t *\n\t\t * @member {module:engine/model/position~Position|null} #graveyardPosition\n\t\t */\n\t\tthis.graveyardPosition = graveyardPosition ? graveyardPosition.clone() : null;\n\n\t\tif ( this.graveyardPosition ) {\n\t\t\tthis.graveyardPosition.stickiness = 'toNext';\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tget type() {\n\t\treturn 'split';\n\t}\n\n\t/**\n\t * Position inside the new clone of a split element.\n\t *\n\t * This is a position where nodes that are after the split position will be moved to.\n\t *\n\t * @readonly\n\t * @type {module:engine/model/position~Position}\n\t */\n\tget moveTargetPosition() {\n\t\tconst path = this.insertionPosition.path.slice();\n\t\tpath.push( 0 );\n\n\t\treturn new Position( this.insertionPosition.root, path );\n\t}\n\n\t/**\n\t * Artificial range that contains all the nodes from the split element that will be moved to the new element.\n\t * The range starts at {@link ~#splitPosition} and ends in the same parent, at `POSITIVE_INFINITY` offset.\n\t *\n\t * @readonly\n\t * @type {module:engine/model/range~Range}\n\t */\n\tget movedRange() {\n\t\tconst end = this.splitPosition.getShiftedBy( Number.POSITIVE_INFINITY );\n\n\t\treturn new Range( this.splitPosition, end );\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/splitoperation~SplitOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\tconst split = new this.constructor( this.splitPosition, this.howMany, this.graveyardPosition, this.baseVersion );\n\t\tsplit.insertionPosition = this.insertionPosition;\n\n\t\treturn split;\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/mergeoperation~MergeOperation}\n\t */\n\tgetReversed() {\n\t\tconst graveyard = this.splitPosition.root.document.graveyard;\n\t\tconst graveyardPosition = new Position( graveyard, [ 0 ] );\n\n\t\treturn new MergeOperation( this.moveTargetPosition, this.howMany, this.splitPosition, graveyardPosition, this.baseVersion + 1 );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_validate() {\n\t\tconst element = this.splitPosition.parent;\n\t\tconst offset = this.splitPosition.offset;\n\n\t\t// Validate whether split operation has correct parameters.\n\t\tif ( !element || element.maxOffset < offset ) {\n\t\t\t/**\n\t\t\t * Split position is invalid.\n\t\t\t *\n\t\t\t * @error split-operation-position-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'split-operation-position-invalid: Split position is invalid.', this );\n\t\t} else if ( !element.parent ) {\n\t\t\t/**\n\t\t\t * Cannot split root element.\n\t\t\t *\n\t\t\t * @error split-operation-split-in-root\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'split-operation-split-in-root: Cannot split root element.', this );\n\t\t} else if ( this.howMany != element.maxOffset - this.splitPosition.offset ) {\n\t\t\t/**\n\t\t\t * Split operation specifies wrong number of nodes to move.\n\t\t\t *\n\t\t\t * @error split-operation-how-many-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'split-operation-how-many-invalid: Split operation specifies wrong number of nodes to move.', this );\n\t\t} else if ( this.graveyardPosition && !this.graveyardPosition.nodeAfter ) {\n\t\t\t/**\n\t\t\t * Graveyard position invalid.\n\t\t\t *\n\t\t\t * @error split-operation-graveyard-position-invalid\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'split-operation-graveyard-position-invalid: Graveyard position invalid.', this );\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\t_execute() {\n\t\tconst splitElement = this.splitPosition.parent;\n\n\t\tif ( this.graveyardPosition ) {\n\t\t\t_move( Range._createFromPositionAndShift( this.graveyardPosition, 1 ), this.insertionPosition );\n\t\t} else {\n\t\t\tconst newElement = splitElement._clone();\n\n\t\t\t_insert( this.insertionPosition, newElement );\n\t\t}\n\n\t\tconst sourceRange = new Range(\n\t\t\tPosition._createAt( splitElement, this.splitPosition.offset ),\n\t\t\tPosition._createAt( splitElement, splitElement.maxOffset )\n\t\t);\n\n\t\t_move( sourceRange, this.moveTargetPosition );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\ttoJSON() {\n\t\tconst json = super.toJSON();\n\n\t\tjson.splitPosition = this.splitPosition.toJSON();\n\t\tjson.insertionPosition = this.insertionPosition.toJSON();\n\n\t\tif ( this.graveyardPosition ) {\n\t\t\tjson.graveyardPosition = this.graveyardPosition.toJSON();\n\t\t}\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'SplitOperation';\n\t}\n\n\t/**\n\t * Helper function that returns a default insertion position basing on given `splitPosition`. The default insertion\n\t * position is after the split element.\n\t *\n\t * @param {module:engine/model/position~Position} splitPosition\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tstatic getInsertionPosition( splitPosition ) {\n\t\tconst path = splitPosition.path.slice( 0, -1 );\n\t\tpath[ path.length - 1 ]++;\n\n\t\treturn new Position( splitPosition.root, path );\n\t}\n\n\t/**\n\t * Creates `SplitOperation` object from deserilized object, i.e. from parsed JSON string.\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/splitoperation~SplitOperation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\tconst splitPosition = Position.fromJSON( json.splitPosition, document );\n\t\tconst insertionPosition = Position.fromJSON( json.insertionPosition, document );\n\t\tconst graveyardPosition = json.graveyardPosition ? Position.fromJSON( json.graveyardPosition, document ) : null;\n\n\t\tconst split = new this( splitPosition, json.howMany, graveyardPosition, json.baseVersion );\n\t\tsplit.insertionPosition = insertionPosition;\n\n\t\treturn split;\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `SplitOperation( ${ this.baseVersion } ): ${ this.splitPosition } ` +\n\t// @if CK_DEBUG_ENGINE //\t\t`( ${ this.howMany } ) -> ${ this.insertionPosition }` +\n\t// @if CK_DEBUG_ENGINE //\t\t`${ this.graveyardPosition ? ' with ' + this.graveyardPosition : '' }`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/rootelement\n */\n\nimport Element from './element';\n\n/**\n * Type of {@link module:engine/model/element~Element} that is a root of a model tree.\n * @extends module:engine/model/element~Element\n */\nexport default class RootElement extends Element {\n\t/**\n\t * Creates root element.\n\t *\n\t * @param {module:engine/model/document~Document} doc Document that is an owner of this root.\n\t * @param {String} name Node name.\n\t * @param {String} [rootName='main'] Unique root name used to identify this root\n\t * element by {@link module:engine/model/document~Document}.\n\t */\n\tconstructor( doc, name, rootName = 'main' ) {\n\t\tsuper( name );\n\n\t\t/**\n\t\t * Document that is an owner of this root.\n\t\t *\n\t\t * @private\n\t\t * @member {module:engine/model/document~Document}\n\t\t */\n\t\tthis._doc = doc;\n\n\t\t/**\n\t\t * Unique root name used to identify this root element by {@link module:engine/model/document~Document}.\n\t\t *\n\t\t * @readonly\n\t\t * @member {String}\n\t\t */\n\t\tthis.rootName = rootName;\n\t}\n\n\t/**\n\t * {@link module:engine/model/document~Document Document} that owns this root element.\n\t *\n\t * In contrary, to {@link module:engine/model/node~Node node}, root element always have a `document`.\n\t *\n\t * @readonly\n\t * @type {module:engine/model/document~Document|null}\n\t */\n\tget document() {\n\t\treturn this._doc;\n\t}\n\n\t/**\n\t * Checks whether this object is of the given.\n\t *\n\t *\t\trootElement.is( 'rootElement' ); // -> true\n\t *\t\trootElement.is( 'element' ); // -> true\n\t *\t\trootElement.is( 'node' ); // -> true\n\t *\t\trootElement.is( 'model:rootElement' ); // -> true\n\t *\t\trootElement.is( 'model:element' ); // -> true\n\t *\t\trootElement.is( 'model:node' ); // -> true\n\t *\n\t *\t\trootElement.is( 'view:element' ); // -> false\n\t *\t\trootElement.is( 'documentFragment' ); // -> false\n\t *\n\t * Assuming that the object being checked is an element, you can also check its\n\t * {@link module:engine/model/element~Element#name name}:\n\t *\n\t *\t\trootElement.is( '$root' ); // -> true if this is a $root element\n\t *\t\trootElement.is( 'rootElement', '$root' ); // -> same as above\n\t *\t\ttext.is( '$root' ); -> false\n\t *\n\t * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.\n\t *\n\t * @param {String} type Type to check when `name` parameter is present.\n\t * Otherwise, it acts like the `name` parameter.\n\t * @param {String} [name] Element name.\n\t * @returns {Boolean}\n\t */\n\tis( type, name ) {\n\t\tconst cutType = type.replace( 'model:', '' );\n\t\tif ( !name ) {\n\t\t\treturn cutType == 'rootElement' || super.is( type );\n\t\t} else {\n\t\t\treturn ( cutType == 'rootElement' && name == this.name ) || super.is( type, name );\n\t\t}\n\t}\n\n\t/**\n\t * Converts `RootElement` instance to `String` containing it's name.\n\t *\n\t * @returns {String} `RootElement` instance converted to `String`.\n\t */\n\ttoJSON() {\n\t\treturn this.rootName;\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn this.rootName;\n\t// @if CK_DEBUG_ENGINE // }\n\n\t// @if CK_DEBUG_ENGINE // log() {\n\t// @if CK_DEBUG_ENGINE // \tconsole.log( 'ModelRootElement: ' + this );\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/writer\n */\n\nimport AttributeOperation from './operation/attributeoperation';\nimport DetachOperation from './operation/detachoperation';\nimport InsertOperation from './operation/insertoperation';\nimport MarkerOperation from './operation/markeroperation';\nimport MoveOperation from './operation/moveoperation';\nimport RenameOperation from './operation/renameoperation';\nimport RootAttributeOperation from './operation/rootattributeoperation';\nimport SplitOperation from './operation/splitoperation';\nimport MergeOperation from './operation/mergeoperation';\n\nimport DocumentFragment from './documentfragment';\nimport Text from './text';\nimport Element from './element';\nimport RootElement from './rootelement';\nimport Position from './position';\nimport Range from './range.js';\nimport DocumentSelection from './documentselection';\n\nimport toMap from '@ckeditor/ckeditor5-utils/src/tomap';\n\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * The model can only be modified by using the writer. It should be used whenever you want to create a node, modify\n * child nodes, attributes or text, set the selection's position and its attributes.\n *\n * The instance of the writer is only available in the {@link module:engine/model/model~Model#change `change()`} or\n * {@link module:engine/model/model~Model#enqueueChange `enqueueChange()`}.\n *\n *\t\tmodel.change( writer => {\n *\t\t\twriter.insertText( 'foo', paragraph, 'end' );\n *\t\t} );\n *\n * Note that the writer should never be stored and used outside of the `change()` and\n * `enqueueChange()` blocks.\n *\n * Note that writer's methods do not check the {@link module:engine/model/schema~Schema}. It is possible\n * to create incorrect model structures by using the writer. Read more about in\n * {@glink framework/guides/deep-dive/schema#who-checks-the-schema \"Who checks the schema?\"}.\n *\n * @see module:engine/model/model~Model#change\n * @see module:engine/model/model~Model#enqueueChange\n */\nexport default class Writer {\n\t/**\n\t * Creates a writer instance.\n\t *\n\t * **Note:** It is not recommended to use it directly. Use {@link module:engine/model/model~Model#change `Model#change()`} or\n\t * {@link module:engine/model/model~Model#enqueueChange `Model#enqueueChange()`} instead.\n\t *\n\t * @protected\n\t * @param {module:engine/model/model~Model} model\n\t * @param {module:engine/model/batch~Batch} batch\n\t */\n\tconstructor( model, batch ) {\n\t\t/**\n\t\t * Instance of the model on which this writer operates.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:engine/model/model~Model}\n\t\t */\n\t\tthis.model = model;\n\n\t\t/**\n\t\t * The batch to which this writer will add changes.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:engine/model/batch~Batch}\n\t\t */\n\t\tthis.batch = batch;\n\t}\n\n\t/**\n\t * Creates a new {@link module:engine/model/text~Text text node}.\n\t *\n\t *\t\twriter.createText( 'foo' );\n\t *\t\twriter.createText( 'foo', { bold: true } );\n\t *\n\t * @param {String} data Text data.\n\t * @param {Object} [attributes] Text attributes.\n\t * @returns {module:engine/model/text~Text} Created text node.\n\t */\n\tcreateText( data, attributes ) {\n\t\treturn new Text( data, attributes );\n\t}\n\n\t/**\n\t * Creates a new {@link module:engine/model/element~Element element}.\n\t *\n\t *\t\twriter.createElement( 'paragraph' );\n\t *\t\twriter.createElement( 'paragraph', { alignment: 'center' } );\n\t *\n\t * @param {String} name Name of the element.\n\t * @param {Object} [attributes] Elements attributes.\n\t * @returns {module:engine/model/element~Element} Created element.\n\t */\n\tcreateElement( name, attributes ) {\n\t\treturn new Element( name, attributes );\n\t}\n\n\t/**\n\t * Creates a new {@link module:engine/model/documentfragment~DocumentFragment document fragment}.\n\t *\n\t * @returns {module:engine/model/documentfragment~DocumentFragment} Created document fragment.\n\t */\n\tcreateDocumentFragment() {\n\t\treturn new DocumentFragment();\n\t}\n\n\t/**\n\t * Inserts item on given position.\n\t *\n\t *\t\tconst paragraph = writer.createElement( 'paragraph' );\n\t *\t\twriter.insert( paragraph, position );\n\t *\n\t * Instead of using position you can use parent and offset:\n\t *\n\t *\t\tconst text = writer.createText( 'foo' );\n\t *\t\twriter.insert( text, paragraph, 5 );\n\t *\n\t * You can also use `end` instead of the offset to insert at the end:\n\t *\n\t *\t\tconst text = writer.createText( 'foo' );\n\t *\t\twriter.insert( text, paragraph, 'end' );\n\t *\n\t * Or insert before or after another element:\n\t *\n\t *\t\tconst paragraph = writer.createElement( 'paragraph' );\n\t *\t\twriter.insert( paragraph, anotherParagraph, 'after' );\n\t *\n\t * These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.\n\t *\n\t * Note that if the item already has parent it will be removed from the previous parent.\n\t *\n\t * Note that you cannot re-insert a node from a document to a different document or a document fragment. In this case,\n\t * `model-writer-insert-forbidden-move` is thrown.\n\t *\n\t * If you want to move {@link module:engine/model/range~Range range} instead of an\n\t * {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.\n\t *\n\t * **Note:** For a paste-like content insertion mechanism see\n\t * {@link module:engine/model/model~Model#insertContent `model.insertContent()`}.\n\t *\n\t * @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment} item Item or document\n\t * fragment to insert.\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when\n\t * second parameter is a {@link module:engine/model/item~Item model item}.\n\t */\n\tinsert( item, itemOrPosition, offset = 0 ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( item instanceof Text && item.data == '' ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst position = Position._createAt( itemOrPosition, offset );\n\n\t\t// If item has a parent already.\n\t\tif ( item.parent ) {\n\t\t\t// We need to check if item is going to be inserted within the same document.\n\t\t\tif ( isSameTree( item.root, position.root ) ) {\n\t\t\t\t// If it's we just need to move it.\n\t\t\t\tthis.move( Range._createOn( item ), position );\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// If it isn't the same root.\n\t\t\telse {\n\t\t\t\tif ( item.root.document ) {\n\t\t\t\t\t/**\n\t\t\t\t\t * Cannot move a node from a document to a different tree.\n\t\t\t\t\t * It is forbidden to move a node that was already in a document outside of it.\n\t\t\t\t\t *\n\t\t\t\t\t * @error model-writer-insert-forbidden-move\n\t\t\t\t\t */\n\t\t\t\t\tthrow new CKEditorError(\n\t\t\t\t\t\t'model-writer-insert-forbidden-move: ' +\n\t\t\t\t\t\t'Cannot move a node from a document to a different tree. ' +\n\t\t\t\t\t\t'It is forbidden to move a node that was already in a document outside of it.',\n\t\t\t\t\t\tthis\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\t// Move between two different document fragments or from document fragment to a document is possible.\n\t\t\t\t\t// In that case, remove the item from it's original parent.\n\t\t\t\t\tthis.remove( item );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst version = position.root.document ? position.root.document.version : null;\n\n\t\tconst insert = new InsertOperation( position, item, version );\n\n\t\tif ( item instanceof Text ) {\n\t\t\tinsert.shouldReceiveAttributes = true;\n\t\t}\n\n\t\tthis.batch.addOperation( insert );\n\t\tthis.model.applyOperation( insert );\n\n\t\t// When element is a DocumentFragment we need to move its markers to Document#markers.\n\t\tif ( item instanceof DocumentFragment ) {\n\t\t\tfor ( const [ markerName, markerRange ] of item.markers ) {\n\t\t\t\t// We need to migrate marker range from DocumentFragment to Document.\n\t\t\t\tconst rangeRootPosition = Position._createAt( markerRange.root, 0 );\n\t\t\t\tconst range = new Range(\n\t\t\t\t\tmarkerRange.start._getCombined( rangeRootPosition, position ),\n\t\t\t\t\tmarkerRange.end._getCombined( rangeRootPosition, position )\n\t\t\t\t);\n\n\t\t\t\tconst options = { range, usingOperation: true, affectsData: true };\n\n\t\t\t\tif ( this.model.markers.has( markerName ) ) {\n\t\t\t\t\tthis.updateMarker( markerName, options );\n\t\t\t\t} else {\n\t\t\t\t\tthis.addMarker( markerName, options );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Creates and inserts text on given position. You can optionally set text attributes:\n\t *\n\t *\t\twriter.insertText( 'foo', position );\n\t *\t\twriter.insertText( 'foo', { bold: true }, position );\n\t *\n\t * Instead of using position you can use parent and offset or define that text should be inserted at the end\n\t * or before or after other node:\n\t *\n\t *\t\t// Inserts 'foo' in paragraph, at offset 5:\n\t *\t\twriter.insertText( 'foo', paragraph, 5 );\n\t *\t\t// Inserts 'foo' at the end of a paragraph:\n\t *\t\twriter.insertText( 'foo', paragraph, 'end' );\n\t *\t\t// Inserts 'foo' after an image:\n\t *\t\twriter.insertText( 'foo', image, 'after' );\n\t *\n\t * These parameters work in the same way as {@link #createPositionAt `writer.createPositionAt()`}.\n\t *\n\t * @param {String} data Text data.\n\t * @param {Object} [attributes] Text attributes.\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when\n\t * third parameter is a {@link module:engine/model/item~Item model item}.\n\t */\n\tinsertText( text, attributes, itemOrPosition, offset ) {\n\t\tif ( attributes instanceof DocumentFragment || attributes instanceof Element || attributes instanceof Position ) {\n\t\t\tthis.insert( this.createText( text ), attributes, itemOrPosition );\n\t\t} else {\n\t\t\tthis.insert( this.createText( text, attributes ), itemOrPosition, offset );\n\t\t}\n\t}\n\n\t/**\n\t * Creates and inserts element on given position. You can optionally set attributes:\n\t *\n\t *\t\twriter.insertElement( 'paragraph', position );\n\t *\t\twriter.insertElement( 'paragraph', { alignment: 'center' }, position );\n\t *\n\t * Instead of using position you can use parent and offset or define that text should be inserted at the end\n\t * or before or after other node:\n\t *\n\t *\t\t// Inserts paragraph in the root at offset 5:\n\t *\t\twriter.insertElement( 'paragraph', root, 5 );\n\t *\t\t// Inserts paragraph at the end of a blockquote:\n\t *\t\twriter.insertElement( 'paragraph', blockquote, 'end' );\n\t *\t\t// Inserts after an image:\n\t *\t\twriter.insertElement( 'paragraph', image, 'after' );\n\t *\n\t * These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.\n\t *\n\t * @param {String} name Name of the element.\n\t * @param {Object} [attributes] Elements attributes.\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when\n\t * third parameter is a {@link module:engine/model/item~Item model item}.\n\t */\n\tinsertElement( name, attributes, itemOrPosition, offset ) {\n\t\tif ( attributes instanceof DocumentFragment || attributes instanceof Element || attributes instanceof Position ) {\n\t\t\tthis.insert( this.createElement( name ), attributes, itemOrPosition );\n\t\t} else {\n\t\t\tthis.insert( this.createElement( name, attributes ), itemOrPosition, offset );\n\t\t}\n\t}\n\n\t/**\n\t * Inserts item at the end of the given parent.\n\t *\n\t *\t\tconst paragraph = writer.createElement( 'paragraph' );\n\t *\t\twriter.append( paragraph, root );\n\t *\n\t * Note that if the item already has parent it will be removed from the previous parent.\n\t *\n\t * If you want to move {@link module:engine/model/range~Range range} instead of an\n\t * {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.\n\t *\n\t * @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment}\n\t * item Item or document fragment to insert.\n\t * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent\n\t */\n\tappend( item, parent ) {\n\t\tthis.insert( item, parent, 'end' );\n\t}\n\n\t/**\n\t * Creates text node and inserts it at the end of the parent. You can optionally set text attributes:\n\t *\n\t *\t\twriter.appendText( 'foo', paragraph );\n\t *\t\twriter.appendText( 'foo', { bold: true }, paragraph );\n\t *\n\t * @param {String} text Text data.\n\t * @param {Object} [attributes] Text attributes.\n\t * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent\n\t */\n\tappendText( text, attributes, parent ) {\n\t\tif ( attributes instanceof DocumentFragment || attributes instanceof Element ) {\n\t\t\tthis.insert( this.createText( text ), attributes, 'end' );\n\t\t} else {\n\t\t\tthis.insert( this.createText( text, attributes ), parent, 'end' );\n\t\t}\n\t}\n\n\t/**\n\t * Creates element and inserts it at the end of the parent. You can optionally set attributes:\n\t *\n\t *\t\twriter.appendElement( 'paragraph', root );\n\t *\t\twriter.appendElement( 'paragraph', { alignment: 'center' }, root );\n\t *\n\t * @param {String} name Name of the element.\n\t * @param {Object} [attributes] Elements attributes.\n\t * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent\n\t */\n\tappendElement( name, attributes, parent ) {\n\t\tif ( attributes instanceof DocumentFragment || attributes instanceof Element ) {\n\t\t\tthis.insert( this.createElement( name ), attributes, 'end' );\n\t\t} else {\n\t\t\tthis.insert( this.createElement( name, attributes ), parent, 'end' );\n\t\t}\n\t}\n\n\t/**\n\t * Sets value of the attribute with given key on a {@link module:engine/model/item~Item model item}\n\t * or on a {@link module:engine/model/range~Range range}.\n\t *\n\t * @param {String} key Attribute key.\n\t * @param {*} value Attribute new value.\n\t * @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange\n\t * Model item or range on which the attribute will be set.\n\t */\n\tsetAttribute( key, value, itemOrRange ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( itemOrRange instanceof Range ) {\n\t\t\tconst ranges = itemOrRange.getMinimalFlatRanges();\n\n\t\t\tfor ( const range of ranges ) {\n\t\t\t\tsetAttributeOnRange( this, key, value, range );\n\t\t\t}\n\t\t} else {\n\t\t\tsetAttributeOnItem( this, key, value, itemOrRange );\n\t\t}\n\t}\n\n\t/**\n\t * Sets values of attributes on a {@link module:engine/model/item~Item model item}\n\t * or on a {@link module:engine/model/range~Range range}.\n\t *\n\t *\t\twriter.setAttributes( {\n\t *\t\t\tbold: true,\n\t *\t\t\titalic: true\n\t *\t\t}, range );\n\t *\n\t * @param {Object} attributes Attributes keys and values.\n\t * @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange\n\t * Model item or range on which the attributes will be set.\n\t */\n\tsetAttributes( attributes, itemOrRange ) {\n\t\tfor ( const [ key, val ] of toMap( attributes ) ) {\n\t\t\tthis.setAttribute( key, val, itemOrRange );\n\t\t}\n\t}\n\n\t/**\n\t * Removes an attribute with given key from a {@link module:engine/model/item~Item model item}\n\t * or from a {@link module:engine/model/range~Range range}.\n\t *\n\t * @param {String} key Attribute key.\n\t * @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange\n\t * Model item or range from which the attribute will be removed.\n\t */\n\tremoveAttribute( key, itemOrRange ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( itemOrRange instanceof Range ) {\n\t\t\tconst ranges = itemOrRange.getMinimalFlatRanges();\n\n\t\t\tfor ( const range of ranges ) {\n\t\t\t\tsetAttributeOnRange( this, key, null, range );\n\t\t\t}\n\t\t} else {\n\t\t\tsetAttributeOnItem( this, key, null, itemOrRange );\n\t\t}\n\t}\n\n\t/**\n\t * Removes all attributes from all elements in the range or from the given item.\n\t *\n\t * @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange\n\t * Model item or range from which all attributes will be removed.\n\t */\n\tclearAttributes( itemOrRange ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tconst removeAttributesFromItem = item => {\n\t\t\tfor ( const attribute of item.getAttributeKeys() ) {\n\t\t\t\tthis.removeAttribute( attribute, item );\n\t\t\t}\n\t\t};\n\n\t\tif ( !( itemOrRange instanceof Range ) ) {\n\t\t\tremoveAttributesFromItem( itemOrRange );\n\t\t} else {\n\t\t\tfor ( const item of itemOrRange.getItems() ) {\n\t\t\t\tremoveAttributesFromItem( item );\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Moves all items in the source range to the target position.\n\t *\n\t *\t\twriter.move( sourceRange, targetPosition );\n\t *\n\t * Instead of the target position you can use parent and offset or define that range should be moved to the end\n\t * or before or after chosen item:\n\t *\n\t *\t\t// Moves all items in the range to the paragraph at offset 5:\n\t *\t\twriter.move( sourceRange, paragraph, 5 );\n\t *\t\t// Moves all items in the range to the end of a blockquote:\n\t *\t\twriter.move( sourceRange, blockquote, 'end' );\n\t *\t\t// Moves all items in the range to a position after an image:\n\t *\t\twriter.move( sourceRange, image, 'after' );\n\t *\n\t * These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.\n\t *\n\t * Note that items can be moved only within the same tree. It means that you can move items within the same root\n\t * (element or document fragment) or between {@link module:engine/model/document~Document#roots documents roots},\n\t * but you can not move items from document fragment to the document or from one detached element to another. Use\n\t * {@link module:engine/model/writer~Writer#insert} in such cases.\n\t *\n\t * @param {module:engine/model/range~Range} range Source range.\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when\n\t * second parameter is a {@link module:engine/model/item~Item model item}.\n\t */\n\tmove( range, itemOrPosition, offset ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( !( range instanceof Range ) ) {\n\t\t\t/**\n\t\t\t * Invalid range to move.\n\t\t\t *\n\t\t\t * @error writer-move-invalid-range\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-move-invalid-range: Invalid range to move.', this );\n\t\t}\n\n\t\tif ( !range.isFlat ) {\n\t\t\t/**\n\t\t\t * Range to move is not flat.\n\t\t\t *\n\t\t\t * @error writer-move-range-not-flat\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-move-range-not-flat: Range to move is not flat.', this );\n\t\t}\n\n\t\tconst position = Position._createAt( itemOrPosition, offset );\n\n\t\t// Do not move anything if the move target is same as moved range start.\n\t\tif ( position.isEqual( range.start ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If part of the marker is removed, create additional marker operation for undo purposes.\n\t\tthis._addOperationForAffectedMarkers( 'move', range );\n\n\t\tif ( !isSameTree( range.root, position.root ) ) {\n\t\t\t/**\n\t\t\t * Range is going to be moved within not the same document. Please use\n\t\t\t * {@link module:engine/model/writer~Writer#insert insert} instead.\n\t\t\t *\n\t\t\t * @error writer-move-different-document\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-move-different-document: Range is going to be moved between different documents.', this );\n\t\t}\n\n\t\tconst version = range.root.document ? range.root.document.version : null;\n\t\tconst operation = new MoveOperation( range.start, range.end.offset - range.start.offset, position, version );\n\n\t\tthis.batch.addOperation( operation );\n\t\tthis.model.applyOperation( operation );\n\t}\n\n\t/**\n\t * Removes given model {@link module:engine/model/item~Item item} or {@link module:engine/model/range~Range range}.\n\t *\n\t * @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange Model item or range to remove.\n\t */\n\tremove( itemOrRange ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tconst rangeToRemove = itemOrRange instanceof Range ? itemOrRange : Range._createOn( itemOrRange );\n\t\tconst ranges = rangeToRemove.getMinimalFlatRanges().reverse();\n\n\t\tfor ( const flat of ranges ) {\n\t\t\t// If part of the marker is removed, create additional marker operation for undo purposes.\n\t\t\tthis._addOperationForAffectedMarkers( 'move', flat );\n\n\t\t\tapplyRemoveOperation( flat.start, flat.end.offset - flat.start.offset, this.batch, this.model );\n\t\t}\n\t}\n\n\t/**\n\t * Merges two siblings at the given position.\n\t *\n\t * Node before and after the position have to be an element. Otherwise `writer-merge-no-element-before` or\n\t * `writer-merge-no-element-after` error will be thrown.\n\t *\n\t * @param {module:engine/model/position~Position} position Position between merged elements.\n\t */\n\tmerge( position ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tconst nodeBefore = position.nodeBefore;\n\t\tconst nodeAfter = position.nodeAfter;\n\n\t\t// If part of the marker is removed, create additional marker operation for undo purposes.\n\t\tthis._addOperationForAffectedMarkers( 'merge', position );\n\n\t\tif ( !( nodeBefore instanceof Element ) ) {\n\t\t\t/**\n\t\t\t * Node before merge position must be an element.\n\t\t\t *\n\t\t\t * @error writer-merge-no-element-before\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-merge-no-element-before: Node before merge position must be an element.', this );\n\t\t}\n\n\t\tif ( !( nodeAfter instanceof Element ) ) {\n\t\t\t/**\n\t\t\t * Node after merge position must be an element.\n\t\t\t *\n\t\t\t * @error writer-merge-no-element-after\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-merge-no-element-after: Node after merge position must be an element.', this );\n\t\t}\n\n\t\tif ( !position.root.document ) {\n\t\t\tthis._mergeDetached( position );\n\t\t} else {\n\t\t\tthis._merge( position );\n\t\t}\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createPositionFromPath `Model#createPositionFromPath()`}.\n\t *\n\t * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.\n\t * @param {Array.} path Position path. See {@link module:engine/model/position~Position#path}.\n\t * @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.\n\t * See {@link module:engine/model/position~PositionStickiness}.\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tcreatePositionFromPath( root, path, stickiness ) {\n\t\treturn this.model.createPositionFromPath( root, path, stickiness );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createPositionAt `Model#createPositionAt()`}.\n\t *\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when\n\t * first parameter is a {@link module:engine/model/item~Item model item}.\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tcreatePositionAt( itemOrPosition, offset ) {\n\t\treturn this.model.createPositionAt( itemOrPosition, offset );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createPositionAfter `Model#createPositionAfter()`}.\n\t *\n\t * @param {module:engine/model/item~Item} item Item after which the position should be placed.\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tcreatePositionAfter( item ) {\n\t\treturn this.model.createPositionAfter( item );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createPositionBefore `Model#createPositionBefore()`}.\n\t *\n\t * @param {module:engine/model/item~Item} item Item after which the position should be placed.\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tcreatePositionBefore( item ) {\n\t\treturn this.model.createPositionBefore( item );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createRange `Model#createRange()`}.\n\t *\n\t * @param {module:engine/model/position~Position} start Start position.\n\t * @param {module:engine/model/position~Position} [end] End position. If not set, range will be collapsed at `start` position.\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tcreateRange( start, end ) {\n\t\treturn this.model.createRange( start, end );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createRangeIn `Model#createRangeIn()`}.\n\t *\n\t * @param {module:engine/model/element~Element} element Element which is a parent for the range.\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tcreateRangeIn( element ) {\n\t\treturn this.model.createRangeIn( element );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createRangeOn `Model#createRangeOn()`}.\n\t *\n\t * @param {module:engine/model/element~Element} element Element which is a parent for the range.\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tcreateRangeOn( element ) {\n\t\treturn this.model.createRangeOn( element );\n\t}\n\n\t/**\n\t * Shortcut for {@link module:engine/model/model~Model#createSelection `Model#createSelection()`}.\n\t *\n\t * @param {module:engine/model/selection~Selectable} selectable\n\t * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.\n\t * @param {Object} [options]\n\t * @param {Boolean} [options.backward] Sets this selection instance to be backward.\n\t * @returns {module:engine/model/selection~Selection}\n\t */\n\tcreateSelection( selectable, placeOrOffset, options ) {\n\t\treturn this.model.createSelection( selectable, placeOrOffset, options );\n\t}\n\n\t/**\n\t * Performs merge action in a detached tree.\n\t *\n\t * @private\n\t * @param {module:engine/model/position~Position} position Position between merged elements.\n\t */\n\t_mergeDetached( position ) {\n\t\tconst nodeBefore = position.nodeBefore;\n\t\tconst nodeAfter = position.nodeAfter;\n\n\t\tthis.move( Range._createIn( nodeAfter ), Position._createAt( nodeBefore, 'end' ) );\n\t\tthis.remove( nodeAfter );\n\t}\n\n\t/**\n\t * Performs merge action in a non-detached tree.\n\t *\n\t * @private\n\t * @param {module:engine/model/position~Position} position Position between merged elements.\n\t */\n\t_merge( position ) {\n\t\tconst targetPosition = Position._createAt( position.nodeBefore, 'end' );\n\t\tconst sourcePosition = Position._createAt( position.nodeAfter, 0 );\n\n\t\tconst graveyard = position.root.document.graveyard;\n\t\tconst graveyardPosition = new Position( graveyard, [ 0 ] );\n\n\t\tconst version = position.root.document.version;\n\n\t\tconst merge = new MergeOperation( sourcePosition, position.nodeAfter.maxOffset, targetPosition, graveyardPosition, version );\n\n\t\tthis.batch.addOperation( merge );\n\t\tthis.model.applyOperation( merge );\n\t}\n\n\t/**\n\t * Renames the given element.\n\t *\n\t * @param {module:engine/model/element~Element} element The element to rename.\n\t * @param {String} newName New element name.\n\t */\n\trename( element, newName ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( !( element instanceof Element ) ) {\n\t\t\t/**\n\t\t\t * Trying to rename an object which is not an instance of Element.\n\t\t\t *\n\t\t\t * @error writer-rename-not-element-instance\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'writer-rename-not-element-instance: Trying to rename an object which is not an instance of Element.',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\n\t\tconst version = element.root.document ? element.root.document.version : null;\n\t\tconst renameOperation = new RenameOperation( Position._createBefore( element ), element.name, newName, version );\n\n\t\tthis.batch.addOperation( renameOperation );\n\t\tthis.model.applyOperation( renameOperation );\n\t}\n\n\t/**\n\t * Splits elements starting from the given position and going to the top of the model tree as long as given\n\t * `limitElement` is reached. When `limitElement` is not defined then only the parent of the given position will be split.\n\t *\n\t * The element needs to have a parent. It cannot be a root element nor a document fragment.\n\t * The `writer-split-element-no-parent` error will be thrown if you try to split an element with no parent.\n\t *\n\t * @param {module:engine/model/position~Position} position Position of split.\n\t * @param {module:engine/model/node~Node} [limitElement] Stop splitting when this element will be reached.\n\t * @returns {Object} result Split result.\n\t * @returns {module:engine/model/position~Position} result.position Position between split elements.\n\t * @returns {module:engine/model/range~Range} result.range Range that stars from the end of the first split element and ends\n\t * at the beginning of the first copy element.\n\t */\n\tsplit( position, limitElement ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tlet splitElement = position.parent;\n\n\t\tif ( !splitElement.parent ) {\n\t\t\t/**\n\t\t\t * Element with no parent can not be split.\n\t\t\t *\n\t\t\t * @error writer-split-element-no-parent\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-split-element-no-parent: Element with no parent can not be split.', this );\n\t\t}\n\n\t\t// When limit element is not defined lets set splitElement parent as limit.\n\t\tif ( !limitElement ) {\n\t\t\tlimitElement = splitElement.parent;\n\t\t}\n\n\t\tif ( !position.parent.getAncestors( { includeSelf: true } ).includes( limitElement ) ) {\n\t\t\tthrow new CKEditorError( 'writer-split-invalid-limit-element: Limit element is not a position ancestor.', this );\n\t\t}\n\n\t\t// We need to cache elements that will be created as a result of the first split because\n\t\t// we need to create a range from the end of the first split element to the beginning of the\n\t\t// first copy element. This should be handled by LiveRange but it doesn't work on detached nodes.\n\t\tlet firstSplitElement, firstCopyElement;\n\n\t\tdo {\n\t\t\tconst version = splitElement.root.document ? splitElement.root.document.version : null;\n\t\t\tconst howMany = splitElement.maxOffset - position.offset;\n\t\t\tconst split = new SplitOperation( position, howMany, null, version );\n\n\t\t\tthis.batch.addOperation( split );\n\t\t\tthis.model.applyOperation( split );\n\n\t\t\t// Cache result of the first split.\n\t\t\tif ( !firstSplitElement && !firstCopyElement ) {\n\t\t\t\tfirstSplitElement = splitElement;\n\t\t\t\tfirstCopyElement = position.parent.nextSibling;\n\t\t\t}\n\n\t\t\tposition = this.createPositionAfter( position.parent );\n\t\t\tsplitElement = position.parent;\n\t\t} while ( splitElement !== limitElement );\n\n\t\treturn {\n\t\t\tposition,\n\t\t\trange: new Range( Position._createAt( firstSplitElement, 'end' ), Position._createAt( firstCopyElement, 0 ) )\n\t\t};\n\t}\n\n\t/**\n\t * Wraps the given range with the given element or with a new element (if a string was passed).\n\t *\n\t * **Note:** range to wrap should be a \"flat range\" (see {@link module:engine/model/range~Range#isFlat `Range#isFlat`}).\n\t * If not, an error will be thrown.\n\t *\n\t * @param {module:engine/model/range~Range} range Range to wrap.\n\t * @param {module:engine/model/element~Element|String} elementOrString Element or name of element to wrap the range with.\n\t */\n\twrap( range, elementOrString ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( !range.isFlat ) {\n\t\t\t/**\n\t\t\t * Range to wrap is not flat.\n\t\t\t *\n\t\t\t * @error writer-wrap-range-not-flat\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-wrap-range-not-flat: Range to wrap is not flat.', this );\n\t\t}\n\n\t\tconst element = elementOrString instanceof Element ? elementOrString : new Element( elementOrString );\n\n\t\tif ( element.childCount > 0 ) {\n\t\t\t/**\n\t\t\t * Element to wrap with is not empty.\n\t\t\t *\n\t\t\t * @error writer-wrap-element-not-empty\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-wrap-element-not-empty: Element to wrap with is not empty.', this );\n\t\t}\n\n\t\tif ( element.parent !== null ) {\n\t\t\t/**\n\t\t\t * Element to wrap with is already attached to a tree model.\n\t\t\t *\n\t\t\t * @error writer-wrap-element-attached\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-wrap-element-attached: Element to wrap with is already attached to tree model.', this );\n\t\t}\n\n\t\tthis.insert( element, range.start );\n\n\t\t// Shift the range-to-wrap because we just inserted an element before that range.\n\t\tconst shiftedRange = new Range( range.start.getShiftedBy( 1 ), range.end.getShiftedBy( 1 ) );\n\n\t\tthis.move( shiftedRange, Position._createAt( element, 0 ) );\n\t}\n\n\t/**\n\t * Unwraps children of the given element – all its children are moved before it and then the element is removed.\n\t * Throws error if you try to unwrap an element which does not have a parent.\n\t *\n\t * @param {module:engine/model/element~Element} element Element to unwrap.\n\t */\n\tunwrap( element ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( element.parent === null ) {\n\t\t\t/**\n\t\t\t * Trying to unwrap an element which has no parent.\n\t\t\t *\n\t\t\t * @error writer-unwrap-element-no-parent\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-unwrap-element-no-parent: Trying to unwrap an element which has no parent.', this );\n\t\t}\n\n\t\tthis.move( Range._createIn( element ), this.createPositionAfter( element ) );\n\t\tthis.remove( element );\n\t}\n\n\t/**\n\t * Adds a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks\n\t * changes in the document and updates its range automatically, when model tree changes.\n\t *\n\t * As the first parameter you can set marker name.\n\t *\n\t * The required `options.usingOperation` parameter lets you decide if the marker should be managed by operations or not. See\n\t * {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between\n\t * markers managed by operations and not-managed by operations.\n\t *\n\t * The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be\n\t * `true` when the marker change changes the data returned by the\n\t * {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.\n\t * When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.\n\t * When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.\n\t *\n\t * Create marker directly base on marker's name:\n\t *\n\t *\t\taddMarker( markerName, { range, usingOperation: false } );\n\t *\n\t * Create marker using operation:\n\t *\n\t *\t\taddMarker( markerName, { range, usingOperation: true } );\n\t *\n\t * Create marker that affects the editor data:\n\t *\n\t *\t\taddMarker( markerName, { range, usingOperation: false, affectsData: true } );\n\t *\n\t * Note: For efficiency reasons, it's best to create and keep as little markers as possible.\n\t *\n\t * @see module:engine/model/markercollection~Marker\n\t * @param {String} name Name of a marker to create - must be unique.\n\t * @param {Object} options\n\t * @param {Boolean} options.usingOperation Flag indicating that the marker should be added by MarkerOperation.\n\t * See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.\n\t * @param {module:engine/model/range~Range} options.range Marker range.\n\t * @param {Boolean} [options.affectsData=false] Flag indicating that the marker changes the editor data.\n\t * @returns {module:engine/model/markercollection~Marker} Marker that was set.\n\t */\n\taddMarker( name, options ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( !options || typeof options.usingOperation != 'boolean' ) {\n\t\t\t/**\n\t\t\t * The `options.usingOperation` parameter is required when adding a new marker.\n\t\t\t *\n\t\t\t * @error writer-addMarker-no-usingOperation\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'writer-addMarker-no-usingOperation: The options.usingOperation parameter is required when adding a new marker.',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\n\t\tconst usingOperation = options.usingOperation;\n\t\tconst range = options.range;\n\t\tconst affectsData = options.affectsData === undefined ? false : options.affectsData;\n\n\t\tif ( this.model.markers.has( name ) ) {\n\t\t\t/**\n\t\t\t * Marker with provided name already exists.\n\t\t\t *\n\t\t\t * @error writer-addMarker-marker-exists\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-addMarker-marker-exists: Marker with provided name already exists.', this );\n\t\t}\n\n\t\tif ( !range ) {\n\t\t\t/**\n\t\t\t * Range parameter is required when adding a new marker.\n\t\t\t *\n\t\t\t * @error writer-addMarker-no-range\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'writer-addMarker-no-range: Range parameter is required when adding a new marker.',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\n\t\tif ( !usingOperation ) {\n\t\t\treturn this.model.markers._set( name, range, usingOperation, affectsData );\n\t\t}\n\n\t\tapplyMarkerOperation( this, name, null, range, affectsData );\n\n\t\treturn this.model.markers.get( name );\n\t}\n\n\t/**\n\t * Adds, updates or refreshes a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks\n\t * changes in the document and updates its range automatically, when model tree changes. Still, it is possible to change the\n\t * marker's range directly using this method.\n\t *\n\t * As the first parameter you can set marker name or instance. If none of them is provided, new marker, with a unique\n\t * name is created and returned.\n\t *\n\t * As the second parameter you can set the new marker data or leave this parameter as empty which will just refresh\n\t * the marker by triggering downcast conversion for it. Refreshing the marker is useful when you want to change\n\t * the marker {@link module:engine/view/element~Element view element} without changing any marker data.\n\t *\n\t * \t\tlet isCommentActive = false;\n\t *\n\t * \t\tmodel.conversion.markerToHighlight( {\n\t * \t\t\tmodel: 'comment',\n\t *\t\t\tview: data => {\n\t *\t\t\t\tconst classes = [ 'comment-marker' ];\n\t *\n\t *\t\t\t\tif ( isCommentActive ) {\n\t *\t\t\t\t\tclasses.push( 'comment-marker--active' );\n\t *\t\t\t\t}\n\t *\n\t *\t\t\t\treturn { classes };\n\t *\t\t\t}\n\t * \t\t} );\n\t *\n\t * \t\t// Change the property that indicates if marker is displayed as active or not.\n\t * \t\tisCommentActive = true;\n\t *\n\t * \t\t// And refresh the marker to convert it with additional class.\n\t * \t\tmodel.change( writer => writer.updateMarker( 'comment' ) );\n\t *\n\t * The `options.usingOperation` parameter lets you change if the marker should be managed by operations or not. See\n\t * {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between\n\t * markers managed by operations and not-managed by operations. It is possible to change this option for an existing marker.\n\t *\n\t * The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be\n\t * `true` when the marker change changes the data returned by\n\t * the {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.\n\t * When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.\n\t * When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.\n\t *\n\t * Update marker directly base on marker's name:\n\t *\n\t *\t\tupdateMarker( markerName, { range } );\n\t *\n\t * Update marker using operation:\n\t *\n\t *\t\tupdateMarker( marker, { range, usingOperation: true } );\n\t *\t\tupdateMarker( markerName, { range, usingOperation: true } );\n\t *\n\t * Change marker's option (start using operations to manage it):\n\t *\n\t *\t\tupdateMarker( marker, { usingOperation: true } );\n\t *\n\t * Change marker's option (inform the engine, that the marker does not affect the data anymore):\n\t *\n\t *\t\tupdateMarker( markerName, { affectsData: false } );\n\t *\n\t * @see module:engine/model/markercollection~Marker\n\t * @param {String|module:engine/model/markercollection~Marker} markerOrName Name of a marker to update, or a marker instance.\n\t * @param {Object} [options] If options object is not defined then marker will be refreshed by triggering\n\t * downcast conversion for this marker with the same data.\n\t * @param {module:engine/model/range~Range} [options.range] Marker range to update.\n\t * @param {Boolean} [options.usingOperation] Flag indicated whether the marker should be added by MarkerOperation.\n\t * See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.\n\t * @param {Boolean} [options.affectsData] Flag indicating that the marker changes the editor data.\n\t */\n\tupdateMarker( markerOrName, options ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tconst markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;\n\t\tconst currentMarker = this.model.markers.get( markerName );\n\n\t\tif ( !currentMarker ) {\n\t\t\t/**\n\t\t\t * Marker with provided name does not exists.\n\t\t\t *\n\t\t\t * @error writer-updateMarker-marker-not-exists\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-updateMarker-marker-not-exists: Marker with provided name does not exists.', this );\n\t\t}\n\n\t\tif ( !options ) {\n\t\t\tthis.model.markers._refresh( currentMarker );\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst hasUsingOperationDefined = typeof options.usingOperation == 'boolean';\n\t\tconst affectsDataDefined = typeof options.affectsData == 'boolean';\n\n\t\t// Use previously defined marker's affectsData if the property is not provided.\n\t\tconst affectsData = affectsDataDefined ? options.affectsData : currentMarker.affectsData;\n\n\t\tif ( !hasUsingOperationDefined && !options.range && !affectsDataDefined ) {\n\t\t\t/**\n\t\t\t * One of the options is required - provide range, usingOperations or affectsData.\n\t\t\t *\n\t\t\t * @error writer-updateMarker-wrong-options\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'writer-updateMarker-wrong-options: One of the options is required - provide range, usingOperations or affectsData.',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\n\t\tconst currentRange = currentMarker.getRange();\n\t\tconst updatedRange = options.range ? options.range : currentRange;\n\n\t\tif ( hasUsingOperationDefined && options.usingOperation !== currentMarker.managedUsingOperations ) {\n\t\t\t// The marker type is changed so it's necessary to create proper operations.\n\t\t\tif ( options.usingOperation ) {\n\t\t\t\t// If marker changes to a managed one treat this as synchronizing existing marker.\n\t\t\t\t// Create `MarkerOperation` with `oldRange` set to `null`, so reverse operation will remove the marker.\n\t\t\t\tapplyMarkerOperation( this, markerName, null, updatedRange, affectsData );\n\t\t\t} else {\n\t\t\t\t// If marker changes to a marker that do not use operations then we need to create additional operation\n\t\t\t\t// that removes that marker first.\n\t\t\t\tapplyMarkerOperation( this, markerName, currentRange, null, affectsData );\n\n\t\t\t\t// Although not managed the marker itself should stay in model and its range should be preserver or changed to passed range.\n\t\t\t\tthis.model.markers._set( markerName, updatedRange, undefined, affectsData );\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Marker's type doesn't change so update it accordingly.\n\t\tif ( currentMarker.managedUsingOperations ) {\n\t\t\tapplyMarkerOperation( this, markerName, currentRange, updatedRange, affectsData );\n\t\t} else {\n\t\t\tthis.model.markers._set( markerName, updatedRange, undefined, affectsData );\n\t\t}\n\t}\n\n\t/**\n\t * Removes given {@link module:engine/model/markercollection~Marker marker} or marker with given name.\n\t * The marker is removed accordingly to how it has been created, so if the marker was created using operation,\n\t * it will be destroyed using operation.\n\t *\n\t * @param {module:engine/model/markercollection~Marker|String} markerOrName Marker or marker name to remove.\n\t */\n\tremoveMarker( markerOrName ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tconst name = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;\n\n\t\tif ( !this.model.markers.has( name ) ) {\n\t\t\t/**\n\t\t\t * Trying to remove marker which does not exist.\n\t\t\t *\n\t\t\t * @error writer-removeMarker-no-marker\n\t\t\t */\n\t\t\tthrow new CKEditorError( 'writer-removeMarker-no-marker: Trying to remove marker which does not exist.', this );\n\t\t}\n\n\t\tconst marker = this.model.markers.get( name );\n\n\t\tif ( !marker.managedUsingOperations ) {\n\t\t\tthis.model.markers._remove( name );\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst oldRange = marker.getRange();\n\n\t\tapplyMarkerOperation( this, name, oldRange, null, marker.affectsData );\n\t}\n\n\t/**\n\t * Sets the document's selection (ranges and direction) to the specified location based on the given\n\t * {@link module:engine/model/selection~Selectable selectable} or creates an empty selection if no arguments were passed.\n\t *\n\t *\t\t// Sets selection to the given range.\n\t *\t\tconst range = writer.createRange( start, end );\n\t *\t\twriter.setSelection( range );\n\t *\n\t *\t\t// Sets selection to given ranges.\n\t *\t\tconst ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];\n\t *\t\twriter.setSelection( range );\n\t *\n\t *\t\t// Sets selection to other selection.\n\t *\t\tconst otherSelection = writer.createSelection();\n\t *\t\twriter.setSelection( otherSelection );\n\t *\n\t *\t\t// Sets selection to the given document selection.\n\t *\t\tconst documentSelection = model.document.selection;\n\t *\t\twriter.setSelection( documentSelection );\n\t *\n\t *\t\t// Sets collapsed selection at the given position.\n\t *\t\tconst position = writer.createPosition( root, path );\n\t *\t\twriter.setSelection( position );\n\t *\n\t *\t\t// Sets collapsed selection at the position of the given node and an offset.\n\t *\t\twriter.setSelection( paragraph, offset );\n\t *\n\t * Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of\n \t * that element and ends after the last child of that element.\n\t *\n\t *\t\twriter.setSelection( paragraph, 'in' );\n\t *\n\t * Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends just after the item.\n\t *\n\t *\t\twriter.setSelection( paragraph, 'on' );\n\t *\n\t *\t\t// Removes all selection's ranges.\n\t *\t\twriter.setSelection( null );\n\t *\n\t * `Writer#setSelection()` allow passing additional options (`backward`) as the last argument.\n\t *\n\t *\t\t// Sets selection as backward.\n\t *\t\twriter.setSelection( range, { backward: true } );\n\t *\n\t * Throws `writer-incorrect-use` error when the writer is used outside the `change()` block.\n\t *\n\t * @param {module:engine/model/selection~Selectable} selectable\n\t * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.\n\t * @param {Object} [options]\n\t * @param {Boolean} [options.backward] Sets this selection instance to be backward.\n\t */\n\tsetSelection( selectable, placeOrOffset, options ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tthis.model.document.selection._setTo( selectable, placeOrOffset, options );\n\t}\n\n\t/**\n\t * Moves {@link module:engine/model/documentselection~DocumentSelection#focus} to the specified location.\n\t *\n\t * The location can be specified in the same form as\n\t * {@link #createPositionAt `writer.createPositionAt()`} parameters.\n\t *\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when\n\t * first parameter is a {@link module:engine/model/item~Item model item}.\n\t */\n\tsetSelectionFocus( itemOrPosition, offset ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tthis.model.document.selection._setFocus( itemOrPosition, offset );\n\t}\n\n\t/**\n\t * Sets attribute(s) on the selection. If attribute with the same key already is set, it's value is overwritten.\n\t *\n\t * Using key and value pair:\n\t *\n\t * \twriter.setSelectionAttribute( 'italic', true );\n\t *\n\t * Using key-value object:\n\t *\n\t * \twriter.setSelectionAttribute( { italic: true, bold: false } );\n\t *\n\t * Using iterable object:\n\t *\n\t * \twriter.setSelectionAttribute( new Map( [ [ 'italic', true ] ] ) );\n\t *\n\t * @param {String|Object|Iterable.<*>} keyOrObjectOrIterable Key of the attribute to set\n\t * or object / iterable of key => value attribute pairs.\n\t * @param {*} [value] Attribute value.\n\t */\n\tsetSelectionAttribute( keyOrObjectOrIterable, value ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( typeof keyOrObjectOrIterable === 'string' ) {\n\t\t\tthis._setSelectionAttribute( keyOrObjectOrIterable, value );\n\t\t} else {\n\t\t\tfor ( const [ key, value ] of toMap( keyOrObjectOrIterable ) ) {\n\t\t\t\tthis._setSelectionAttribute( key, value );\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Removes attribute(s) with given key(s) from the selection.\n\t *\n\t * Remove one attribute:\n\t *\n\t *\t\twriter.removeSelectionAttribute( 'italic' );\n\t *\n\t * Remove multiple attributes:\n\t *\n\t *\t\twriter.removeSelectionAttribute( [ 'italic', 'bold' ] );\n\t *\n\t * @param {String|Iterable.} keyOrIterableOfKeys Key of the attribute to remove or an iterable of attribute keys to remove.\n\t */\n\tremoveSelectionAttribute( keyOrIterableOfKeys ) {\n\t\tthis._assertWriterUsedCorrectly();\n\n\t\tif ( typeof keyOrIterableOfKeys === 'string' ) {\n\t\t\tthis._removeSelectionAttribute( keyOrIterableOfKeys );\n\t\t} else {\n\t\t\tfor ( const key of keyOrIterableOfKeys ) {\n\t\t\t\tthis._removeSelectionAttribute( key );\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Temporarily changes the {@link module:engine/model/documentselection~DocumentSelection#isGravityOverridden gravity}\n\t * of the selection from left to right.\n\t *\n\t * The gravity defines from which direction the selection inherits its attributes. If it's the default left gravity,\n\t * then the selection (after being moved by the user) inherits attributes from its left-hand side.\n\t * This method allows to temporarily override this behavior by forcing the gravity to the right.\n\t *\n\t * For the following model fragment:\n\t *\n\t *\t\t<$text bold=\"true\" linkHref=\"url\">bar[]$text><$text bold=\"true\">biz$text>\n\t *\n\t * * Default gravity: selection will have the `bold` and `linkHref` attributes.\n\t * * Overridden gravity: selection will have `bold` attribute.\n\t *\n\t * **Note**: It returns an unique identifier which is required to restore the gravity. It guarantees the symmetry\n\t * of the process.\n\t *\n\t * @returns {String} The unique id which allows restoring the gravity.\n\t */\n\toverrideSelectionGravity() {\n\t\treturn this.model.document.selection._overrideGravity();\n\t}\n\n\t/**\n\t * Restores {@link ~Writer#overrideSelectionGravity} gravity to default.\n\t *\n\t * Restoring the gravity is only possible using the unique identifier returned by\n\t * {@link ~Writer#overrideSelectionGravity}. Note that the gravity remains overridden as long as won't be restored\n\t * the same number of times it was overridden.\n\t *\n\t * @param {String} uid The unique id returned by {@link ~Writer#overrideSelectionGravity}.\n\t */\n\trestoreSelectionGravity( uid ) {\n\t\tthis.model.document.selection._restoreGravity( uid );\n\t}\n\n\t/**\n\t * @private\n\t * @param {String} key Key of the attribute to remove.\n\t * @param {*} value Attribute value.\n\t */\n\t_setSelectionAttribute( key, value ) {\n\t\tconst selection = this.model.document.selection;\n\n\t\t// Store attribute in parent element if the selection is collapsed in an empty node.\n\t\tif ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {\n\t\t\tconst storeKey = DocumentSelection._getStoreAttributeKey( key );\n\n\t\t\tthis.setAttribute( storeKey, value, selection.anchor.parent );\n\t\t}\n\n\t\tselection._setAttribute( key, value );\n\t}\n\n\t/**\n\t * @private\n\t * @param {String} key Key of the attribute to remove.\n\t */\n\t_removeSelectionAttribute( key ) {\n\t\tconst selection = this.model.document.selection;\n\n\t\t// Remove stored attribute from parent element if the selection is collapsed in an empty node.\n\t\tif ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {\n\t\t\tconst storeKey = DocumentSelection._getStoreAttributeKey( key );\n\n\t\t\tthis.removeAttribute( storeKey, selection.anchor.parent );\n\t\t}\n\n\t\tselection._removeAttribute( key );\n\t}\n\n\t/**\n\t * Throws `writer-detached-writer-tries-to-modify-model` error when the writer is used outside of the `change()` block.\n\t *\n\t * @private\n\t */\n\t_assertWriterUsedCorrectly() {\n\t\t/**\n\t\t * Trying to use a writer outside a {@link module:engine/model/model~Model#change `change()`} or\n\t\t * {@link module:engine/model/model~Model#enqueueChange `enqueueChange()`} blocks.\n\t\t *\n\t\t * The writer can only be used inside these blocks which ensures that the model\n\t\t * can only be changed during such \"sessions\".\n\t\t *\n\t\t * @error writer-incorrect-use\n\t\t */\n\t\tif ( this.model._currentWriter !== this ) {\n\t\t\tthrow new CKEditorError( 'writer-incorrect-use: Trying to use a writer outside the change() block.', this );\n\t\t}\n\t}\n\n\t/**\n\t * For given action `type` and `positionOrRange` where the action happens, this function finds all affected markers\n\t * and applies a marker operation with the new marker range equal to the current range. Thanks to this, the marker range\n\t * can be later correctly processed during undo.\n\t *\n\t * @private\n\t * @param {'move'|'merge'} type Writer action type.\n\t * @param {module:engine/model/position~Position|module:engine/model/range~Range} positionOrRange Position or range\n\t * where the writer action happens.\n\t */\n\t_addOperationForAffectedMarkers( type, positionOrRange ) {\n\t\tfor ( const marker of this.model.markers ) {\n\t\t\tif ( !marker.managedUsingOperations ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst markerRange = marker.getRange();\n\t\t\tlet isAffected = false;\n\n\t\t\tif ( type == 'move' ) {\n\t\t\t\tisAffected =\n\t\t\t\t\tpositionOrRange.containsPosition( markerRange.start ) ||\n\t\t\t\t\tpositionOrRange.start.isEqual( markerRange.start ) ||\n\t\t\t\t\tpositionOrRange.containsPosition( markerRange.end ) ||\n\t\t\t\t\tpositionOrRange.end.isEqual( markerRange.end );\n\t\t\t} else {\n\t\t\t\t// if type == 'merge'.\n\t\t\t\tconst elementBefore = positionOrRange.nodeBefore;\n\t\t\t\tconst elementAfter = positionOrRange.nodeAfter;\n\n\t\t\t\t// Start: Foo[
Bar]
\n\t\t\t\t// After merge: Foo[Bar]
\n\t\t\t\t// After undoing split: Foo
[Bar]
<-- incorrect, needs remembering for undo.\n\t\t\t\t//\n\t\t\t\tconst affectedInLeftElement = markerRange.start.parent == elementBefore && markerRange.start.isAtEnd;\n\n\t\t\t\t// Start: [Foo
]Bar
\n\t\t\t\t// After merge: [Foo]Bar
\n\t\t\t\t// After undoing split: [Foo]
Bar
<-- incorrect, needs remembering for undo.\n\t\t\t\t//\n\t\t\t\tconst affectedInRightElement = markerRange.end.parent == elementAfter && markerRange.end.offset == 0;\n\n\t\t\t\t// Start: [Foo
]Bar
\n\t\t\t\t// After merge: [Foo]Bar
\n\t\t\t\t// After undoing split: [Foo]
Bar
<-- incorrect, needs remembering for undo.\n\t\t\t\t//\n\t\t\t\tconst affectedAfterLeftElement = markerRange.end.nodeAfter == elementAfter;\n\n\t\t\t\t// Start: Foo
[Bar]
\n\t\t\t\t// After merge: Foo[Bar]
\n\t\t\t\t// After undoing split: Foo
[Bar]
<-- incorrect, needs remembering for undo.\n\t\t\t\t//\n\t\t\t\tconst affectedBeforeRightElement = markerRange.start.nodeAfter == elementAfter;\n\n\t\t\t\tisAffected = affectedInLeftElement || affectedInRightElement || affectedAfterLeftElement || affectedBeforeRightElement;\n\t\t\t}\n\n\t\t\tif ( isAffected ) {\n\t\t\t\tthis.updateMarker( marker.name, { range: markerRange } );\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Sets given attribute to each node in given range. When attribute value is null then attribute will be removed.\n//\n// Because attribute operation needs to have the same attribute value on the whole range, this function splits\n// the range into smaller parts.\n//\n// Given `range` must be flat.\n//\n// @private\n// @param {module:engine/model/writer~Writer} writer\n// @param {String} key Attribute key.\n// @param {*} value Attribute new value.\n// @param {module:engine/model/range~Range} range Model range on which the attribute will be set.\nfunction setAttributeOnRange( writer, key, value, range ) {\n\tconst model = writer.model;\n\tconst doc = model.document;\n\n\t// Position of the last split, the beginning of the new range.\n\tlet lastSplitPosition = range.start;\n\n\t// Currently position in the scanning range. Because we need value after the position, it is not a current\n\t// position of the iterator but the previous one (we need to iterate one more time to get the value after).\n\tlet position;\n\n\t// Value before the currently position.\n\tlet valueBefore;\n\n\t// Value after the currently position.\n\tlet valueAfter;\n\n\tfor ( const val of range.getWalker( { shallow: true } ) ) {\n\t\tvalueAfter = val.item.getAttribute( key );\n\n\t\t// At the first run of the iterator the position in undefined. We also do not have a valueBefore, but\n\t\t// because valueAfter may be null, valueBefore may be equal valueAfter ( undefined == null ).\n\t\tif ( position && valueBefore != valueAfter ) {\n\t\t\t// if valueBefore == value there is nothing to change, so we add operation only if these values are different.\n\t\t\tif ( valueBefore != value ) {\n\t\t\t\taddOperation();\n\t\t\t}\n\n\t\t\tlastSplitPosition = position;\n\t\t}\n\n\t\tposition = val.nextPosition;\n\t\tvalueBefore = valueAfter;\n\t}\n\n\t// Because position in the loop is not the iterator position (see let position comment), the last position in\n\t// the while loop will be last but one position in the range. We need to check the last position manually.\n\tif ( position instanceof Position && position != lastSplitPosition && valueBefore != value ) {\n\t\taddOperation();\n\t}\n\n\tfunction addOperation() {\n\t\tconst range = new Range( lastSplitPosition, position );\n\t\tconst version = range.root.document ? doc.version : null;\n\t\tconst operation = new AttributeOperation( range, key, valueBefore, value, version );\n\n\t\twriter.batch.addOperation( operation );\n\t\tmodel.applyOperation( operation );\n\t}\n}\n\n// Sets given attribute to the given node. When attribute value is null then attribute will be removed.\n//\n// @private\n// @param {module:engine/model/writer~Writer} writer\n// @param {String} key Attribute key.\n// @param {*} value Attribute new value.\n// @param {module:engine/model/item~Item} item Model item on which the attribute will be set.\nfunction setAttributeOnItem( writer, key, value, item ) {\n\tconst model = writer.model;\n\tconst doc = model.document;\n\tconst previousValue = item.getAttribute( key );\n\tlet range, operation;\n\n\tif ( previousValue != value ) {\n\t\tconst isRootChanged = item.root === item;\n\n\t\tif ( isRootChanged ) {\n\t\t\t// If we change attributes of root element, we have to use `RootAttributeOperation`.\n\t\t\tconst version = item.document ? doc.version : null;\n\n\t\t\toperation = new RootAttributeOperation( item, key, previousValue, value, version );\n\t\t} else {\n\t\t\trange = new Range( Position._createBefore( item ), writer.createPositionAfter( item ) );\n\n\t\t\tconst version = range.root.document ? doc.version : null;\n\n\t\t\toperation = new AttributeOperation( range, key, previousValue, value, version );\n\t\t}\n\n\t\twriter.batch.addOperation( operation );\n\t\tmodel.applyOperation( operation );\n\t}\n}\n\n// Creates and applies marker operation to {@link module:engine/model/operation/operation~Operation operation}.\n//\n// @private\n// @param {module:engine/model/writer~Writer} writer\n// @param {String} name Marker name.\n// @param {module:engine/model/range~Range} oldRange Marker range before the change.\n// @param {module:engine/model/range~Range} newRange Marker range after the change.\n// @param {Boolean} affectsData\nfunction applyMarkerOperation( writer, name, oldRange, newRange, affectsData ) {\n\tconst model = writer.model;\n\tconst doc = model.document;\n\n\tconst operation = new MarkerOperation( name, oldRange, newRange, model.markers, affectsData, doc.version );\n\n\twriter.batch.addOperation( operation );\n\tmodel.applyOperation( operation );\n}\n\n// Creates `MoveOperation` or `DetachOperation` that removes `howMany` nodes starting from `position`.\n// The operation will be applied on given model instance and added to given operation instance.\n//\n// @private\n// @param {module:engine/model/position~Position} position Position from which nodes are removed.\n// @param {Number} howMany Number of nodes to remove.\n// @param {Batch} batch Batch to which the operation will be added.\n// @param {module:engine/model/model~Model} model Model instance on which operation will be applied.\nfunction applyRemoveOperation( position, howMany, batch, model ) {\n\tlet operation;\n\n\tif ( position.root.document ) {\n\t\tconst doc = model.document;\n\t\tconst graveyardPosition = new Position( doc.graveyard, [ 0 ] );\n\n\t\toperation = new MoveOperation( position, howMany, graveyardPosition, doc.version );\n\t} else {\n\t\toperation = new DetachOperation( position, howMany );\n\t}\n\n\tbatch.addOperation( operation );\n\tmodel.applyOperation( operation );\n}\n\n// Returns `true` if both root elements are the same element or both are documents root elements.\n//\n// Elements in the same tree can be moved (for instance you can move element form one documents root to another, or\n// within the same document fragment), but when element supposed to be moved from document fragment to the document, or\n// to another document it should be removed and inserted to avoid problems with OT. This is because features like undo or\n// collaboration may track changes on the document but ignore changes on detached fragments and should not get\n// unexpected `move` operation.\nfunction isSameTree( rootA, rootB ) {\n\t// If it is the same root this is the same tree.\n\tif ( rootA === rootB ) {\n\t\treturn true;\n\t}\n\n\t// If both roots are documents root it is operation within the document what we still treat as the same tree.\n\tif ( rootA instanceof RootElement && rootB instanceof RootElement ) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/differ\n */\n\nimport Position from './position';\nimport Range from './range';\n\n/**\n * Calculates the difference between two model states.\n *\n * Receives operations that are to be applied on the model document. Marks parts of the model document tree which\n * are changed and saves the state of these elements before the change. Then, it compares saved elements with the\n * changed elements, after all changes are applied on the model document. Calculates the diff between saved\n * elements and new ones and returns a change set.\n */\nexport default class Differ {\n\t/**\n\t * Creates a `Differ` instance.\n\t *\n\t * @param {module:engine/model/markercollection~MarkerCollection} markerCollection Model's marker collection.\n\t */\n\tconstructor( markerCollection ) {\n\t\t/**\n\t\t * Reference to the model's marker collection.\n\t\t *\n\t\t * @private\n\t\t * @type {module:engine/model/markercollection~MarkerCollection}\n\t\t */\n\t\tthis._markerCollection = markerCollection;\n\n\t\t/**\n\t\t * A map that stores changes that happened in a given element.\n\t\t *\n\t\t * The keys of the map are references to the model elements.\n\t\t * The values of the map are arrays with changes that were done on this element.\n\t\t *\n\t\t * @private\n\t\t * @type {Map}\n\t\t */\n\t\tthis._changesInElement = new Map();\n\n\t\t/**\n\t\t * A map that stores \"element's children snapshots\". A snapshot is representing children of a given element before\n\t\t * the first change was applied on that element. Snapshot items are objects with two properties: `name`,\n\t\t * containing the element name (or `'$text'` for a text node) and `attributes` which is a map of the node's attributes.\n\t\t *\n\t\t * @private\n\t\t * @type {Map}\n\t\t */\n\t\tthis._elementSnapshots = new Map();\n\n\t\t/**\n\t\t * A map that stores all changed markers.\n\t\t *\n\t\t * The keys of the map are marker names.\n\t\t * The values of the map are objects with the `oldRange` and `newRange` properties. They store the marker range\n\t\t * state before and after the change.\n\t\t *\n\t\t * @private\n\t\t * @type {Map}\n\t\t */\n\t\tthis._changedMarkers = new Map();\n\n\t\t/**\n\t\t * Stores the number of changes that were processed. Used to order the changes chronologically. It is important\n\t\t * when changes are sorted.\n\t\t *\n\t\t * @private\n\t\t * @type {Number}\n\t\t */\n\t\tthis._changeCount = 0;\n\n\t\t/**\n\t\t * For efficiency purposes, `Differ` stores the change set returned by the differ after {@link #getChanges} call.\n\t\t * Cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will\n\t\t * return the cached value instead of calculating it again.\n\t\t *\n\t\t * This property stores those changes that did not take place in graveyard root.\n\t\t *\n\t\t * @private\n\t\t * @type {Array.|null}\n\t\t */\n\t\tthis._cachedChanges = null;\n\n\t\t/**\n\t\t * For efficiency purposes, `Differ` stores the change set returned by the differ after the {@link #getChanges} call.\n\t\t * The cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will\n\t\t * return the cached value instead of calculating it again.\n\t\t *\n\t\t * This property stores all changes evaluated by `Differ`, including those that took place in the graveyard.\n\t\t *\n\t\t * @private\n\t\t * @type {Array.|null}\n\t\t */\n\t\tthis._cachedChangesWithGraveyard = null;\n\t}\n\n\t/**\n\t * Informs whether there are any changes buffered in `Differ`.\n\t *\n\t * @readonly\n\t * @type {Boolean}\n\t */\n\tget isEmpty() {\n\t\treturn this._changesInElement.size == 0 && this._changedMarkers.size == 0;\n\t}\n\n\t/**\n\t * Marks given `item` in differ to be \"refreshed\". It means that the item will be marked as removed and inserted in the differ changes\n\t * set, so it will be effectively re-converted when differ changes will be handled by a dispatcher.\n\t *\n\t * @param {module:engine/model/item~Item} item Item to refresh.\n\t */\n\trefreshItem( item ) {\n\t\tif ( this._isInInsertedElement( item.parent ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._markRemove( item.parent, item.startOffset, item.offsetSize );\n\t\tthis._markInsert( item.parent, item.startOffset, item.offsetSize );\n\n\t\tconst range = Range._createOn( item );\n\n\t\tfor ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {\n\t\t\tconst markerRange = marker.getRange();\n\n\t\t\tthis.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );\n\t\t}\n\n\t\t// Clear cache after each buffered operation as it is no longer valid.\n\t\tthis._cachedChanges = null;\n\t}\n\n\t/**\n\t * Buffers the given operation. An operation has to be buffered before it is executed.\n\t *\n\t * Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ`\n\t * in the state before the operation is executed.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} operation An operation to buffer.\n\t */\n\tbufferOperation( operation ) {\n\t\t// Below we take an operation, check its type, then use its parameters in marking (private) methods.\n\t\t// The general rule is to not mark elements inside inserted element. All inserted elements are re-rendered.\n\t\t// Marking changes in them would cause a \"double\" changing then.\n\t\t//\n\t\tswitch ( operation.type ) {\n\t\t\tcase 'insert': {\n\t\t\t\tif ( this._isInInsertedElement( operation.position.parent ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis._markInsert( operation.position.parent, operation.position.offset, operation.nodes.maxOffset );\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'addAttribute':\n\t\t\tcase 'removeAttribute':\n\t\t\tcase 'changeAttribute': {\n\t\t\t\tfor ( const item of operation.range.getItems( { shallow: true } ) ) {\n\t\t\t\t\tif ( this._isInInsertedElement( item.parent ) ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis._markAttribute( item );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'remove':\n\t\t\tcase 'move':\n\t\t\tcase 'reinsert': {\n\t\t\t\t// When range is moved to the same position then not mark it as a change.\n\t\t\t\t// See: https://github.com/ckeditor/ckeditor5-engine/issues/1664.\n\t\t\t\tif (\n\t\t\t\t\toperation.sourcePosition.isEqual( operation.targetPosition ) ||\n\t\t\t\t\toperation.sourcePosition.getShiftedBy( operation.howMany ).isEqual( operation.targetPosition )\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst sourceParentInserted = this._isInInsertedElement( operation.sourcePosition.parent );\n\t\t\t\tconst targetParentInserted = this._isInInsertedElement( operation.targetPosition.parent );\n\n\t\t\t\tif ( !sourceParentInserted ) {\n\t\t\t\t\tthis._markRemove( operation.sourcePosition.parent, operation.sourcePosition.offset, operation.howMany );\n\t\t\t\t}\n\n\t\t\t\tif ( !targetParentInserted ) {\n\t\t\t\t\tthis._markInsert( operation.targetPosition.parent, operation.getMovedRangeStart().offset, operation.howMany );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'rename': {\n\t\t\t\tif ( this._isInInsertedElement( operation.position.parent ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis._markRemove( operation.position.parent, operation.position.offset, 1 );\n\t\t\t\tthis._markInsert( operation.position.parent, operation.position.offset, 1 );\n\n\t\t\t\tconst range = Range._createFromPositionAndShift( operation.position, 1 );\n\n\t\t\t\tfor ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {\n\t\t\t\t\tconst markerRange = marker.getRange();\n\n\t\t\t\t\tthis.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'split': {\n\t\t\t\tconst splitElement = operation.splitPosition.parent;\n\n\t\t\t\t// Mark that children of the split element were removed.\n\t\t\t\tif ( !this._isInInsertedElement( splitElement ) ) {\n\t\t\t\t\tthis._markRemove( splitElement, operation.splitPosition.offset, operation.howMany );\n\t\t\t\t}\n\n\t\t\t\t// Mark that the new element (split copy) was inserted.\n\t\t\t\tif ( !this._isInInsertedElement( operation.insertionPosition.parent ) ) {\n\t\t\t\t\tthis._markInsert( operation.insertionPosition.parent, operation.insertionPosition.offset, 1 );\n\t\t\t\t}\n\n\t\t\t\t// If the split took the element from the graveyard, mark that the element from the graveyard was removed.\n\t\t\t\tif ( operation.graveyardPosition ) {\n\t\t\t\t\tthis._markRemove( operation.graveyardPosition.parent, operation.graveyardPosition.offset, 1 );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'merge': {\n\t\t\t\t// Mark that the merged element was removed.\n\t\t\t\tconst mergedElement = operation.sourcePosition.parent;\n\n\t\t\t\tif ( !this._isInInsertedElement( mergedElement.parent ) ) {\n\t\t\t\t\tthis._markRemove( mergedElement.parent, mergedElement.startOffset, 1 );\n\t\t\t\t}\n\n\t\t\t\t// Mark that the merged element was inserted into graveyard.\n\t\t\t\tconst graveyardParent = operation.graveyardPosition.parent;\n\n\t\t\t\tthis._markInsert( graveyardParent, operation.graveyardPosition.offset, 1 );\n\n\t\t\t\t// Mark that children of merged element were inserted at new parent.\n\t\t\t\tconst mergedIntoElement = operation.targetPosition.parent;\n\n\t\t\t\tif ( !this._isInInsertedElement( mergedIntoElement ) ) {\n\t\t\t\t\tthis._markInsert( mergedIntoElement, operation.targetPosition.offset, mergedElement.maxOffset );\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Clear cache after each buffered operation as it is no longer valid.\n\t\tthis._cachedChanges = null;\n\t}\n\n\t/**\n\t * Buffers a marker change.\n\t *\n\t * @param {String} markerName The name of the marker that changed.\n\t * @param {module:engine/model/range~Range|null} oldRange Marker range before the change or `null` if the marker has just\n\t * been created.\n\t * @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.\n\t * @param {Boolean} affectsData Flag indicating whether marker affects the editor data.\n\t */\n\tbufferMarkerChange( markerName, oldRange, newRange, affectsData ) {\n\t\tconst buffered = this._changedMarkers.get( markerName );\n\n\t\tif ( !buffered ) {\n\t\t\tthis._changedMarkers.set( markerName, {\n\t\t\t\toldRange,\n\t\t\t\tnewRange,\n\t\t\t\taffectsData\n\t\t\t} );\n\t\t} else {\n\t\t\tbuffered.newRange = newRange;\n\t\t\tbuffered.affectsData = affectsData;\n\n\t\t\tif ( buffered.oldRange == null && buffered.newRange == null ) {\n\t\t\t\t// The marker is going to be removed (`newRange == null`) but it did not exist before the first buffered change\n\t\t\t\t// (`buffered.oldRange == null`). In this case, do not keep the marker in buffer at all.\n\t\t\t\tthis._changedMarkers.delete( markerName );\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns all markers that should be removed as a result of buffered changes.\n\t *\n\t * @returns {Array.} Markers to remove. Each array item is an object containing the `name` and `range` properties.\n\t */\n\tgetMarkersToRemove() {\n\t\tconst result = [];\n\n\t\tfor ( const [ name, change ] of this._changedMarkers ) {\n\t\t\tif ( change.oldRange != null ) {\n\t\t\t\tresult.push( { name, range: change.oldRange } );\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Returns all markers which should be added as a result of buffered changes.\n\t *\n\t * @returns {Array.} Markers to add. Each array item is an object containing the `name` and `range` properties.\n\t */\n\tgetMarkersToAdd() {\n\t\tconst result = [];\n\n\t\tfor ( const [ name, change ] of this._changedMarkers ) {\n\t\t\tif ( change.newRange != null ) {\n\t\t\t\tresult.push( { name, range: change.newRange } );\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Returns all markers which changed.\n\t *\n\t * @returns {Array.}\n\t */\n\tgetChangedMarkers() {\n\t\treturn Array.from( this._changedMarkers ).map( item => (\n\t\t\t{\n\t\t\t\tname: item[ 0 ],\n\t\t\t\tdata: {\n\t\t\t\t\toldRange: item[ 1 ].oldRange,\n\t\t\t\t\tnewRange: item[ 1 ].newRange\n\t\t\t\t}\n\t\t\t}\n\t\t) );\n\t}\n\n\t/**\n\t * Checks whether some of the buffered changes affect the editor data.\n\t *\n\t * Types of changes which affect the editor data:\n\t *\n\t * * model structure changes,\n\t * * attribute changes,\n\t * * changes of markers which were defined as `affectingData`.\n\t *\n\t * @returns {Boolean}\n\t */\n\thasDataChanges() {\n\t\tfor ( const [ , change ] of this._changedMarkers ) {\n\t\t\tif ( change.affectsData ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// If markers do not affect the data, check whether there are some changes in elements.\n\t\treturn this._changesInElement.size > 0;\n\t}\n\n\t/**\n\t * Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}\n\t * call) and the new model tree state (actual one). It should be called after all buffered operations are executed.\n\t *\n\t * The diff set is returned as an array of diff items, each describing a change done on the model. The items are sorted by\n\t * the position on which the change happened. If a position {@link module:engine/model/position~Position#isBefore is before}\n\t * another one, it will be on an earlier index in the diff set.\n\t *\n\t * Because calculating the diff is a costly operation, the result is cached. If no new operation was buffered since the\n\t * previous {@link #getChanges} call, the next call will return the cached value.\n\t *\n\t * @param {Object} options Additional options.\n\t * @param {Boolean} [options.includeChangesInGraveyard=false] If set to `true`, also changes that happened\n\t * in the graveyard root will be returned. By default, changes in the graveyard root are not returned.\n\t * @returns {Array.} Diff between the old and the new model tree state.\n\t */\n\tgetChanges( options = { includeChangesInGraveyard: false } ) {\n\t\t// If there are cached changes, just return them instead of calculating changes again.\n\t\tif ( this._cachedChanges ) {\n\t\t\tif ( options.includeChangesInGraveyard ) {\n\t\t\t\treturn this._cachedChangesWithGraveyard.slice();\n\t\t\t} else {\n\t\t\t\treturn this._cachedChanges.slice();\n\t\t\t}\n\t\t}\n\n\t\t// Will contain returned results.\n\t\tconst diffSet = [];\n\n\t\t// Check all changed elements.\n\t\tfor ( const element of this._changesInElement.keys() ) {\n\t\t\t// Get changes for this element and sort them.\n\t\t\tconst changes = this._changesInElement.get( element ).sort( ( a, b ) => {\n\t\t\t\tif ( a.offset === b.offset ) {\n\t\t\t\t\tif ( a.type != b.type ) {\n\t\t\t\t\t\t// If there are multiple changes at the same position, \"remove\" change should be first.\n\t\t\t\t\t\t// If the order is different, for example, we would first add some nodes and then removed them\n\t\t\t\t\t\t// (instead of the nodes that we should remove).\n\t\t\t\t\t\treturn a.type == 'remove' ? -1 : 1;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn a.offset < b.offset ? -1 : 1;\n\t\t\t} );\n\n\t\t\t// Get children of this element before any change was applied on it.\n\t\t\tconst snapshotChildren = this._elementSnapshots.get( element );\n\t\t\t// Get snapshot of current element's children.\n\t\t\tconst elementChildren = _getChildrenSnapshot( element.getChildren() );\n\n\t\t\t// Generate actions basing on changes done on element.\n\t\t\tconst actions = _generateActionsFromChanges( snapshotChildren.length, changes );\n\n\t\t\tlet i = 0; // Iterator in `elementChildren` array -- iterates through current children of element.\n\t\t\tlet j = 0; // Iterator in `snapshotChildren` array -- iterates through old children of element.\n\n\t\t\t// Process every action.\n\t\t\tfor ( const action of actions ) {\n\t\t\t\tif ( action === 'i' ) {\n\t\t\t\t\t// Generate diff item for this element and insert it into the diff set.\n\t\t\t\t\tdiffSet.push( this._getInsertDiff( element, i, elementChildren[ i ].name ) );\n\n\t\t\t\t\ti++;\n\t\t\t\t} else if ( action === 'r' ) {\n\t\t\t\t\t// Generate diff item for this element and insert it into the diff set.\n\t\t\t\t\tdiffSet.push( this._getRemoveDiff( element, i, snapshotChildren[ j ].name ) );\n\n\t\t\t\t\tj++;\n\t\t\t\t} else if ( action === 'a' ) {\n\t\t\t\t\t// Take attributes from saved and current children.\n\t\t\t\t\tconst elementAttributes = elementChildren[ i ].attributes;\n\t\t\t\t\tconst snapshotAttributes = snapshotChildren[ j ].attributes;\n\t\t\t\t\tlet range;\n\n\t\t\t\t\tif ( elementChildren[ i ].name == '$text' ) {\n\t\t\t\t\t\trange = new Range( Position._createAt( element, i ), Position._createAt( element, i + 1 ) );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst index = element.offsetToIndex( i );\n\t\t\t\t\t\trange = new Range( Position._createAt( element, i ), Position._createAt( element.getChild( index ), 0 ) );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Generate diff items for this change (there might be multiple attributes changed and\n\t\t\t\t\t// there is a single diff for each of them) and insert them into the diff set.\n\t\t\t\t\tdiffSet.push( ...this._getAttributesDiff( range, snapshotAttributes, elementAttributes ) );\n\n\t\t\t\t\ti++;\n\t\t\t\t\tj++;\n\t\t\t\t} else {\n\t\t\t\t\t// `action` is 'equal'. Child not changed.\n\t\t\t\t\ti++;\n\t\t\t\t\tj++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Then, sort the changes by the position (change at position before other changes is first).\n\t\tdiffSet.sort( ( a, b ) => {\n\t\t\t// If the change is in different root, we don't care much, but we'd like to have all changes in given\n\t\t\t// root \"together\" in the array. So let's just sort them by the root name. It does not matter which root\n\t\t\t// will be processed first.\n\t\t\tif ( a.position.root != b.position.root ) {\n\t\t\t\treturn a.position.root.rootName < b.position.root.rootName ? -1 : 1;\n\t\t\t}\n\n\t\t\t// If change happens at the same position...\n\t\t\tif ( a.position.isEqual( b.position ) ) {\n\t\t\t\t// Keep chronological order of operations.\n\t\t\t\treturn a.changeCount - b.changeCount;\n\t\t\t}\n\n\t\t\t// If positions differ, position \"on the left\" should be earlier in the result.\n\t\t\treturn a.position.isBefore( b.position ) ? -1 : 1;\n\t\t} );\n\n\t\t// Glue together multiple changes (mostly on text nodes).\n\t\tfor ( let i = 1; i < diffSet.length; i++ ) {\n\t\t\tconst prevDiff = diffSet[ i - 1 ];\n\t\t\tconst thisDiff = diffSet[ i ];\n\n\t\t\t// Glue remove changes if they happen on text on same position.\n\t\t\tconst isConsecutiveTextRemove =\n\t\t\t\tprevDiff.type == 'remove' && thisDiff.type == 'remove' &&\n\t\t\t\tprevDiff.name == '$text' && thisDiff.name == '$text' &&\n\t\t\t\tprevDiff.position.isEqual( thisDiff.position );\n\n\t\t\t// Glue insert changes if they happen on text on consecutive fragments.\n\t\t\tconst isConsecutiveTextAdd =\n\t\t\t\tprevDiff.type == 'insert' && thisDiff.type == 'insert' &&\n\t\t\t\tprevDiff.name == '$text' && thisDiff.name == '$text' &&\n\t\t\t\tprevDiff.position.parent == thisDiff.position.parent &&\n\t\t\t\tprevDiff.position.offset + prevDiff.length == thisDiff.position.offset;\n\n\t\t\t// Glue attribute changes if they happen on consecutive fragments and have same key, old value and new value.\n\t\t\tconst isConsecutiveAttributeChange =\n\t\t\t\tprevDiff.type == 'attribute' && thisDiff.type == 'attribute' &&\n\t\t\t\tprevDiff.position.parent == thisDiff.position.parent &&\n\t\t\t\tprevDiff.range.isFlat && thisDiff.range.isFlat &&\n\t\t\t\tprevDiff.position.offset + prevDiff.length == thisDiff.position.offset &&\n\t\t\t\tprevDiff.attributeKey == thisDiff.attributeKey &&\n\t\t\t\tprevDiff.attributeOldValue == thisDiff.attributeOldValue &&\n\t\t\t\tprevDiff.attributeNewValue == thisDiff.attributeNewValue;\n\n\t\t\tif ( isConsecutiveTextRemove || isConsecutiveTextAdd || isConsecutiveAttributeChange ) {\n\t\t\t\tdiffSet[ i - 1 ].length++;\n\n\t\t\t\tif ( isConsecutiveAttributeChange ) {\n\t\t\t\t\tdiffSet[ i - 1 ].range.end = diffSet[ i - 1 ].range.end.getShiftedBy( 1 );\n\t\t\t\t}\n\n\t\t\t\tdiffSet.splice( i, 1 );\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\n\t\t// Remove `changeCount` property from diff items. It is used only for sorting and is internal thing.\n\t\tfor ( const item of diffSet ) {\n\t\t\tdelete item.changeCount;\n\n\t\t\tif ( item.type == 'attribute' ) {\n\t\t\t\tdelete item.position;\n\t\t\t\tdelete item.length;\n\t\t\t}\n\t\t}\n\n\t\tthis._changeCount = 0;\n\n\t\t// Cache changes.\n\t\tthis._cachedChangesWithGraveyard = diffSet.slice();\n\t\tthis._cachedChanges = diffSet.slice().filter( _changesInGraveyardFilter );\n\n\t\tif ( options.includeChangesInGraveyard ) {\n\t\t\treturn this._cachedChangesWithGraveyard;\n\t\t} else {\n\t\t\treturn this._cachedChanges;\n\t\t}\n\t}\n\n\t/**\n\t * Resets `Differ`. Removes all buffered changes.\n\t */\n\treset() {\n\t\tthis._changesInElement.clear();\n\t\tthis._elementSnapshots.clear();\n\t\tthis._changedMarkers.clear();\n\t\tthis._cachedChanges = null;\n\t}\n\n\t/**\n\t * Saves and handles an insert change.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} parent\n\t * @param {Number} offset\n\t * @param {Number} howMany\n\t */\n\t_markInsert( parent, offset, howMany ) {\n\t\tconst changeItem = { type: 'insert', offset, howMany, count: this._changeCount++ };\n\n\t\tthis._markChange( parent, changeItem );\n\t}\n\n\t/**\n\t * Saves and handles a remove change.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} parent\n\t * @param {Number} offset\n\t * @param {Number} howMany\n\t */\n\t_markRemove( parent, offset, howMany ) {\n\t\tconst changeItem = { type: 'remove', offset, howMany, count: this._changeCount++ };\n\n\t\tthis._markChange( parent, changeItem );\n\n\t\tthis._removeAllNestedChanges( parent, offset, howMany );\n\t}\n\n\t/**\n\t * Saves and handles an attribute change.\n\t *\n\t * @private\n\t * @param {module:engine/model/item~Item} item\n\t */\n\t_markAttribute( item ) {\n\t\tconst changeItem = { type: 'attribute', offset: item.startOffset, howMany: item.offsetSize, count: this._changeCount++ };\n\n\t\tthis._markChange( item.parent, changeItem );\n\t}\n\n\t/**\n\t * Saves and handles a model change.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} parent\n\t * @param {Object} changeItem\n\t */\n\t_markChange( parent, changeItem ) {\n\t\t// First, make a snapshot of this parent's children (it will be made only if it was not made before).\n\t\tthis._makeSnapshot( parent );\n\n\t\t// Then, get all changes that already were done on the element (empty array if this is the first change).\n\t\tconst changes = this._getChangesForElement( parent );\n\n\t\t// Then, look through all the changes, and transform them or the new change.\n\t\tthis._handleChange( changeItem, changes );\n\n\t\t// Add the new change.\n\t\tchanges.push( changeItem );\n\n\t\t// Remove incorrect changes. During transformation some change might be, for example, included in another.\n\t\t// In that case, the change will have `howMany` property set to `0` or less. We need to remove those changes.\n\t\tfor ( let i = 0; i < changes.length; i++ ) {\n\t\t\tif ( changes[ i ].howMany < 1 ) {\n\t\t\t\tchanges.splice( i, 1 );\n\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Gets an array of changes that have already been saved for a given element.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} element\n\t * @returns {Array.}\n\t */\n\t_getChangesForElement( element ) {\n\t\tlet changes;\n\n\t\tif ( this._changesInElement.has( element ) ) {\n\t\t\tchanges = this._changesInElement.get( element );\n\t\t} else {\n\t\t\tchanges = [];\n\n\t\t\tthis._changesInElement.set( element, changes );\n\t\t}\n\n\t\treturn changes;\n\t}\n\n\t/**\n\t * Saves a children snapshot for a given element.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} element\n\t */\n\t_makeSnapshot( element ) {\n\t\tif ( !this._elementSnapshots.has( element ) ) {\n\t\t\tthis._elementSnapshots.set( element, _getChildrenSnapshot( element.getChildren() ) );\n\t\t}\n\t}\n\n\t/**\n\t * For a given newly saved change, compares it with a change already done on the element and modifies the incoming\n\t * change and/or the old change.\n\t *\n\t * @private\n\t * @param {Object} inc Incoming (new) change.\n\t * @param {Array.} changes An array containing all the changes done on that element.\n\t */\n\t_handleChange( inc, changes ) {\n\t\t// We need a helper variable that will store how many nodes are to be still handled for this change item.\n\t\t// `nodesToHandle` (how many nodes still need to be handled) and `howMany` (how many nodes were affected)\n\t\t// needs to be differentiated.\n\t\t//\n\t\t// This comes up when there are multiple changes that are affected by `inc` change item.\n\t\t//\n\t\t// For example: assume two insert changes: `{ offset: 2, howMany: 1 }` and `{ offset: 5, howMany: 1 }`.\n\t\t// Assume that `inc` change is remove `{ offset: 2, howMany: 2, nodesToHandle: 2 }`.\n\t\t//\n\t\t// Then, we:\n\t\t// - \"forget\" about first insert change (it is \"eaten\" by remove),\n\t\t// - because of that, at the end we will want to remove only one node (`nodesToHandle = 1`),\n\t\t// - but still we have to change offset of the second insert change from `5` to `3`!\n\t\t//\n\t\t// So, `howMany` does not change throughout items transformation and keeps information about how many nodes were affected,\n\t\t// while `nodesToHandle` means how many nodes need to be handled after the change item is transformed by other changes.\n\t\tinc.nodesToHandle = inc.howMany;\n\n\t\tfor ( const old of changes ) {\n\t\t\tconst incEnd = inc.offset + inc.howMany;\n\t\t\tconst oldEnd = old.offset + old.howMany;\n\n\t\t\tif ( inc.type == 'insert' ) {\n\t\t\t\tif ( old.type == 'insert' ) {\n\t\t\t\t\tif ( inc.offset <= old.offset ) {\n\t\t\t\t\t\told.offset += inc.howMany;\n\t\t\t\t\t} else if ( inc.offset < oldEnd ) {\n\t\t\t\t\t\told.howMany += inc.nodesToHandle;\n\t\t\t\t\t\tinc.nodesToHandle = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( old.type == 'remove' ) {\n\t\t\t\t\tif ( inc.offset < old.offset ) {\n\t\t\t\t\t\told.offset += inc.howMany;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( old.type == 'attribute' ) {\n\t\t\t\t\tif ( inc.offset <= old.offset ) {\n\t\t\t\t\t\told.offset += inc.howMany;\n\t\t\t\t\t} else if ( inc.offset < oldEnd ) {\n\t\t\t\t\t\t// This case is more complicated, because attribute change has to be split into two.\n\t\t\t\t\t\t// Example (assume that uppercase and lowercase letters mean different attributes):\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// initial state:\t\tabcxyz\n\t\t\t\t\t\t// attribute change:\taBCXYz\n\t\t\t\t\t\t// incoming insert:\t\taBCfooXYz\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// Change ranges cannot intersect because each item has to be described exactly (it was either\n\t\t\t\t\t\t// not changed, inserted, removed, or its attribute was changed). That's why old attribute\n\t\t\t\t\t\t// change has to be split and both parts has to be handled separately from now on.\n\t\t\t\t\t\tconst howMany = old.howMany;\n\n\t\t\t\t\t\told.howMany = inc.offset - old.offset;\n\n\t\t\t\t\t\t// Add the second part of attribute change to the beginning of processed array so it won't\n\t\t\t\t\t\t// be processed again in this loop.\n\t\t\t\t\t\tchanges.unshift( {\n\t\t\t\t\t\t\ttype: 'attribute',\n\t\t\t\t\t\t\toffset: incEnd,\n\t\t\t\t\t\t\thowMany: howMany - old.howMany,\n\t\t\t\t\t\t\tcount: this._changeCount++\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( inc.type == 'remove' ) {\n\t\t\t\tif ( old.type == 'insert' ) {\n\t\t\t\t\tif ( incEnd <= old.offset ) {\n\t\t\t\t\t\told.offset -= inc.howMany;\n\t\t\t\t\t} else if ( incEnd <= oldEnd ) {\n\t\t\t\t\t\tif ( inc.offset < old.offset ) {\n\t\t\t\t\t\t\tconst intersectionLength = incEnd - old.offset;\n\n\t\t\t\t\t\t\told.offset = inc.offset;\n\n\t\t\t\t\t\t\told.howMany -= intersectionLength;\n\t\t\t\t\t\t\tinc.nodesToHandle -= intersectionLength;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\told.howMany -= inc.nodesToHandle;\n\t\t\t\t\t\t\tinc.nodesToHandle = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif ( inc.offset <= old.offset ) {\n\t\t\t\t\t\t\tinc.nodesToHandle -= old.howMany;\n\t\t\t\t\t\t\told.howMany = 0;\n\t\t\t\t\t\t} else if ( inc.offset < oldEnd ) {\n\t\t\t\t\t\t\tconst intersectionLength = oldEnd - inc.offset;\n\n\t\t\t\t\t\t\told.howMany -= intersectionLength;\n\t\t\t\t\t\t\tinc.nodesToHandle -= intersectionLength;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( old.type == 'remove' ) {\n\t\t\t\t\tif ( incEnd <= old.offset ) {\n\t\t\t\t\t\told.offset -= inc.howMany;\n\t\t\t\t\t} else if ( inc.offset < old.offset ) {\n\t\t\t\t\t\tinc.nodesToHandle += old.howMany;\n\t\t\t\t\t\told.howMany = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( old.type == 'attribute' ) {\n\t\t\t\t\tif ( incEnd <= old.offset ) {\n\t\t\t\t\t\told.offset -= inc.howMany;\n\t\t\t\t\t} else if ( inc.offset < old.offset ) {\n\t\t\t\t\t\tconst intersectionLength = incEnd - old.offset;\n\n\t\t\t\t\t\told.offset = inc.offset;\n\t\t\t\t\t\told.howMany -= intersectionLength;\n\t\t\t\t\t} else if ( inc.offset < oldEnd ) {\n\t\t\t\t\t\tif ( incEnd <= oldEnd ) {\n\t\t\t\t\t\t\t// On first sight in this case we don't need to split attribute operation into two.\n\t\t\t\t\t\t\t// However the changes set is later converted to actions (see `_generateActionsFromChanges`).\n\t\t\t\t\t\t\t// For that reason, no two changes may intersect.\n\t\t\t\t\t\t\t// So we cannot have an attribute change that \"contains\" remove change.\n\t\t\t\t\t\t\t// Attribute change needs to be split.\n\t\t\t\t\t\t\tconst howMany = old.howMany;\n\n\t\t\t\t\t\t\told.howMany = inc.offset - old.offset;\n\n\t\t\t\t\t\t\tconst howManyAfter = howMany - old.howMany - inc.nodesToHandle;\n\n\t\t\t\t\t\t\t// Add the second part of attribute change to the beginning of processed array so it won't\n\t\t\t\t\t\t\t// be processed again in this loop.\n\t\t\t\t\t\t\tchanges.unshift( {\n\t\t\t\t\t\t\t\ttype: 'attribute',\n\t\t\t\t\t\t\t\toffset: inc.offset,\n\t\t\t\t\t\t\t\thowMany: howManyAfter,\n\t\t\t\t\t\t\t\tcount: this._changeCount++\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\told.howMany -= oldEnd - inc.offset;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( inc.type == 'attribute' ) {\n\t\t\t\t// In case of attribute change, `howMany` should be kept same as `nodesToHandle`. It's not an error.\n\t\t\t\tif ( old.type == 'insert' ) {\n\t\t\t\t\tif ( inc.offset < old.offset && incEnd > old.offset ) {\n\t\t\t\t\t\tif ( incEnd > oldEnd ) {\n\t\t\t\t\t\t\t// This case is similar to a case described when incoming change was insert and old change was attribute.\n\t\t\t\t\t\t\t// See comment above.\n\t\t\t\t\t\t\t//\n\t\t\t\t\t\t\t// This time incoming change is attribute. We need to split incoming change in this case too.\n\t\t\t\t\t\t\t// However this time, the second part of the attribute change needs to be processed further\n\t\t\t\t\t\t\t// because there might be other changes that it collides with.\n\t\t\t\t\t\t\tconst attributePart = {\n\t\t\t\t\t\t\t\ttype: 'attribute',\n\t\t\t\t\t\t\t\toffset: oldEnd,\n\t\t\t\t\t\t\t\thowMany: incEnd - oldEnd,\n\t\t\t\t\t\t\t\tcount: this._changeCount++\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tthis._handleChange( attributePart, changes );\n\n\t\t\t\t\t\t\tchanges.push( attributePart );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tinc.nodesToHandle = old.offset - inc.offset;\n\t\t\t\t\t\tinc.howMany = inc.nodesToHandle;\n\t\t\t\t\t} else if ( inc.offset >= old.offset && inc.offset < oldEnd ) {\n\t\t\t\t\t\tif ( incEnd > oldEnd ) {\n\t\t\t\t\t\t\tinc.nodesToHandle = incEnd - oldEnd;\n\t\t\t\t\t\t\tinc.offset = oldEnd;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tinc.nodesToHandle = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( old.type == 'remove' ) {\n\t\t\t\t\t// This is a case when attribute change \"contains\" remove change.\n\t\t\t\t\t// The attribute change needs to be split into two because changes cannot intersect.\n\t\t\t\t\tif ( inc.offset < old.offset && incEnd > old.offset ) {\n\t\t\t\t\t\tconst attributePart = {\n\t\t\t\t\t\t\ttype: 'attribute',\n\t\t\t\t\t\t\toffset: old.offset,\n\t\t\t\t\t\t\thowMany: incEnd - old.offset,\n\t\t\t\t\t\t\tcount: this._changeCount++\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tthis._handleChange( attributePart, changes );\n\n\t\t\t\t\t\tchanges.push( attributePart );\n\n\t\t\t\t\t\tinc.nodesToHandle = old.offset - inc.offset;\n\t\t\t\t\t\tinc.howMany = inc.nodesToHandle;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( old.type == 'attribute' ) {\n\t\t\t\t\t// There are only two conflicting scenarios possible here:\n\t\t\t\t\tif ( inc.offset >= old.offset && incEnd <= oldEnd ) {\n\t\t\t\t\t\t// `old` change includes `inc` change, or they are the same.\n\t\t\t\t\t\tinc.nodesToHandle = 0;\n\t\t\t\t\t\tinc.howMany = 0;\n\t\t\t\t\t\tinc.offset = 0;\n\t\t\t\t\t} else if ( inc.offset <= old.offset && incEnd >= oldEnd ) {\n\t\t\t\t\t\t// `inc` change includes `old` change.\n\t\t\t\t\t\told.howMany = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinc.howMany = inc.nodesToHandle;\n\t\tdelete inc.nodesToHandle;\n\t}\n\n\t/**\n\t * Returns an object with a single insert change description.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} parent The element in which the change happened.\n\t * @param {Number} offset The offset at which change happened.\n\t * @param {String} name The name of the removed element or `'$text'` for a character.\n\t * @returns {Object} The diff item.\n\t */\n\t_getInsertDiff( parent, offset, name ) {\n\t\treturn {\n\t\t\ttype: 'insert',\n\t\t\tposition: Position._createAt( parent, offset ),\n\t\t\tname,\n\t\t\tlength: 1,\n\t\t\tchangeCount: this._changeCount++\n\t\t};\n\t}\n\n\t/**\n\t * Returns an object with a single remove change description.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} parent The element in which change happened.\n\t * @param {Number} offset The offset at which change happened.\n\t * @param {String} name The name of the removed element or `'$text'` for a character.\n\t * @returns {Object} The diff item.\n\t */\n\t_getRemoveDiff( parent, offset, name ) {\n\t\treturn {\n\t\t\ttype: 'remove',\n\t\t\tposition: Position._createAt( parent, offset ),\n\t\t\tname,\n\t\t\tlength: 1,\n\t\t\tchangeCount: this._changeCount++\n\t\t};\n\t}\n\n\t/**\n\t * Returns an array of objects where each one is a single attribute change description.\n\t *\n\t * @private\n\t * @param {module:engine/model/range~Range} range The range where the change happened.\n\t * @param {Map} oldAttributes A map, map iterator or compatible object that contains attributes before the change.\n\t * @param {Map} newAttributes A map, map iterator or compatible object that contains attributes after the change.\n\t * @returns {Array.} An array containing one or more diff items.\n\t */\n\t_getAttributesDiff( range, oldAttributes, newAttributes ) {\n\t\t// Results holder.\n\t\tconst diffs = [];\n\n\t\t// Clone new attributes as we will be performing changes on this object.\n\t\tnewAttributes = new Map( newAttributes );\n\n\t\t// Look through old attributes.\n\t\tfor ( const [ key, oldValue ] of oldAttributes ) {\n\t\t\t// Check what is the new value of the attribute (or if it was removed).\n\t\t\tconst newValue = newAttributes.has( key ) ? newAttributes.get( key ) : null;\n\n\t\t\t// If values are different (or attribute was removed)...\n\t\t\tif ( newValue !== oldValue ) {\n\t\t\t\t// Add diff item.\n\t\t\t\tdiffs.push( {\n\t\t\t\t\ttype: 'attribute',\n\t\t\t\t\tposition: range.start,\n\t\t\t\t\trange: range.clone(),\n\t\t\t\t\tlength: 1,\n\t\t\t\t\tattributeKey: key,\n\t\t\t\t\tattributeOldValue: oldValue,\n\t\t\t\t\tattributeNewValue: newValue,\n\t\t\t\t\tchangeCount: this._changeCount++\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t// Prevent returning two diff items for the same change.\n\t\t\tnewAttributes.delete( key );\n\t\t}\n\n\t\t// Look through new attributes that weren't handled above.\n\t\tfor ( const [ key, newValue ] of newAttributes ) {\n\t\t\t// Each of them is a new attribute. Add diff item.\n\t\t\tdiffs.push( {\n\t\t\t\ttype: 'attribute',\n\t\t\t\tposition: range.start,\n\t\t\t\trange: range.clone(),\n\t\t\t\tlength: 1,\n\t\t\t\tattributeKey: key,\n\t\t\t\tattributeOldValue: null,\n\t\t\t\tattributeNewValue: newValue,\n\t\t\t\tchangeCount: this._changeCount++\n\t\t\t} );\n\t\t}\n\n\t\treturn diffs;\n\t}\n\n\t/**\n\t * Checks whether given element or any of its parents is an element that is buffered as an inserted element.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} element Element to check.\n\t * @returns {Boolean}\n\t */\n\t_isInInsertedElement( element ) {\n\t\tconst parent = element.parent;\n\n\t\tif ( !parent ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst changes = this._changesInElement.get( parent );\n\t\tconst offset = element.startOffset;\n\n\t\tif ( changes ) {\n\t\t\tfor ( const change of changes ) {\n\t\t\t\tif ( change.type == 'insert' && offset >= change.offset && offset < change.offset + change.howMany ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this._isInInsertedElement( parent );\n\t}\n\n\t/**\n\t * Removes deeply all buffered changes that are registered in elements from range specified by `parent`, `offset`\n\t * and `howMany`.\n\t *\n\t * @private\n\t * @param {module:engine/model/element~Element} parent\n\t * @param {Number} offset\n\t * @param {Number} howMany\n\t */\n\t_removeAllNestedChanges( parent, offset, howMany ) {\n\t\tconst range = new Range( Position._createAt( parent, offset ), Position._createAt( parent, offset + howMany ) );\n\n\t\tfor ( const item of range.getItems( { shallow: true } ) ) {\n\t\t\tif ( item.is( 'element' ) ) {\n\t\t\t\tthis._elementSnapshots.delete( item );\n\t\t\t\tthis._changesInElement.delete( item );\n\n\t\t\t\tthis._removeAllNestedChanges( item, 0, item.maxOffset );\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Returns an array that is a copy of passed child list with the exception that text nodes are split to one or more\n// objects, each representing one character and attributes set on that character.\nfunction _getChildrenSnapshot( children ) {\n\tconst snapshot = [];\n\n\tfor ( const child of children ) {\n\t\tif ( child.is( 'text' ) ) {\n\t\t\tfor ( let i = 0; i < child.data.length; i++ ) {\n\t\t\t\tsnapshot.push( {\n\t\t\t\t\tname: '$text',\n\t\t\t\t\tattributes: new Map( child.getAttributes() )\n\t\t\t\t} );\n\t\t\t}\n\t\t} else {\n\t\t\tsnapshot.push( {\n\t\t\t\tname: child.name,\n\t\t\t\tattributes: new Map( child.getAttributes() )\n\t\t\t} );\n\t\t}\n\t}\n\n\treturn snapshot;\n}\n\n// Generates array of actions for given changes set.\n// It simulates what `diff` function does.\n// Generated actions are:\n// - 'e' for 'equal' - when item at that position did not change,\n// - 'i' for 'insert' - when item at that position was inserted,\n// - 'r' for 'remove' - when item at that position was removed,\n// - 'a' for 'attribute' - when item at that position has it attributes changed.\n//\n// Example (assume that uppercase letters have bold attribute, compare with function code):\n//\n// children before:\tfooBAR\n// children after:\tfoxybAR\n//\n// changes: type: remove, offset: 1, howMany: 1\n//\t\t\ttype: insert, offset: 2, howMany: 2\n//\t\t\ttype: attribute, offset: 4, howMany: 1\n//\n// expected actions: equal (f), remove (o), equal (o), insert (x), insert (y), attribute (b), equal (A), equal (R)\n//\n// steps taken by th script:\n//\n// 1. change = \"type: remove, offset: 1, howMany: 1\"; offset = 0; oldChildrenHandled = 0\n// 1.1 between this change and the beginning is one not-changed node, fill with one equal action, one old child has been handled\n// 1.2 this change removes one node, add one remove action\n// 1.3 change last visited `offset` to 1\n// 1.4 since an old child has been removed, one more old child has been handled\n// 1.5 actions at this point are: equal, remove\n//\n// 2. change = \"type: insert, offset: 2, howMany: 2\"; offset = 1; oldChildrenHandled = 2\n// 2.1 between this change and previous change is one not-changed node, add equal action, another one old children has been handled\n// 2.2 this change inserts two nodes, add two insert actions\n// 2.3 change last visited offset to the end of the inserted range, that is 4\n// 2.4 actions at this point are: equal, remove, equal, insert, insert\n//\n// 3. change = \"type: attribute, offset: 4, howMany: 1\"; offset = 4, oldChildrenHandled = 3\n// 3.1 between this change and previous change are no not-changed nodes\n// 3.2 this change changes one node, add one attribute action\n// 3.3 change last visited `offset` to the end of change range, that is 5\n// 3.4 since an old child has been changed, one more old child has been handled\n// 3.5 actions at this point are: equal, remove, equal, insert, insert, attribute\n//\n// 4. after loop oldChildrenHandled = 4, oldChildrenLength = 6 (fooBAR is 6 characters)\n// 4.1 fill up with two equal actions\n//\n// The result actions are: equal, remove, equal, insert, insert, attribute, equal, equal.\nfunction _generateActionsFromChanges( oldChildrenLength, changes ) {\n\tconst actions = [];\n\n\tlet offset = 0;\n\tlet oldChildrenHandled = 0;\n\n\t// Go through all buffered changes.\n\tfor ( const change of changes ) {\n\t\t// First, fill \"holes\" between changes with \"equal\" actions.\n\t\tif ( change.offset > offset ) {\n\t\t\tfor ( let i = 0; i < change.offset - offset; i++ ) {\n\t\t\t\tactions.push( 'e' );\n\t\t\t}\n\n\t\t\toldChildrenHandled += change.offset - offset;\n\t\t}\n\n\t\t// Then, fill up actions accordingly to change type.\n\t\tif ( change.type == 'insert' ) {\n\t\t\tfor ( let i = 0; i < change.howMany; i++ ) {\n\t\t\t\tactions.push( 'i' );\n\t\t\t}\n\n\t\t\t// The last handled offset is after inserted range.\n\t\t\toffset = change.offset + change.howMany;\n\t\t} else if ( change.type == 'remove' ) {\n\t\t\tfor ( let i = 0; i < change.howMany; i++ ) {\n\t\t\t\tactions.push( 'r' );\n\t\t\t}\n\n\t\t\t// The last handled offset is at the position where the nodes were removed.\n\t\t\toffset = change.offset;\n\t\t\t// We removed `howMany` old nodes, update `oldChildrenHandled`.\n\t\t\toldChildrenHandled += change.howMany;\n\t\t} else {\n\t\t\tactions.push( ...'a'.repeat( change.howMany ).split( '' ) );\n\n\t\t\t// The last handled offset is at the position after the changed range.\n\t\t\toffset = change.offset + change.howMany;\n\t\t\t// We changed `howMany` old nodes, update `oldChildrenHandled`.\n\t\t\toldChildrenHandled += change.howMany;\n\t\t}\n\t}\n\n\t// Fill \"equal\" actions at the end of actions set. Use `oldChildrenHandled` to see how many children\n\t// has not been changed / removed at the end of their parent.\n\tif ( oldChildrenHandled < oldChildrenLength ) {\n\t\tfor ( let i = 0; i < oldChildrenLength - oldChildrenHandled - offset; i++ ) {\n\t\t\tactions.push( 'e' );\n\t\t}\n\t}\n\n\treturn actions;\n}\n\n// Filter callback for Array.filter that filters out change entries that are in graveyard.\nfunction _changesInGraveyardFilter( entry ) {\n\tconst posInGy = entry.position && entry.position.root.rootName == '$graveyard';\n\tconst rangeInGy = entry.range && entry.range.root.rootName == '$graveyard';\n\n\treturn !posInGy && !rangeInGy;\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/history\n */\n\n/**\n * `History` keeps the track of all the operations applied to the {@link module:engine/model/document~Document document}.\n */\nexport default class History {\n\t/**\n\t * Creates an empty History instance.\n\t */\n\tconstructor() {\n\t\t/**\n\t\t * Operations added to the history.\n\t\t *\n\t\t * @protected\n\t\t * @member {Array.} module:engine/model/history~History#_operations\n\t\t */\n\t\tthis._operations = [];\n\n\t\t/**\n\t\t * Holds an information which {@link module:engine/model/operation/operation~Operation operation} undoes which\n\t\t * {@link module:engine/model/operation/operation~Operation operation}.\n\t\t *\n\t\t * Keys of the map are \"undoing operations\", that is operations that undone some other operations. For each key, the\n\t\t * value is an operation that has been undone by the \"undoing operation\".\n\t\t *\n\t\t * @private\n\t\t * @member {Map} module:engine/model/history~History#_undoPairs\n\t\t */\n\t\tthis._undoPairs = new Map();\n\n\t\t/**\n\t\t * Holds all undone operations.\n\t\t *\n\t\t * @private\n\t\t * @member {Set.} module:engine/model/history~History#_undoneOperations\n\t\t */\n\t\tthis._undoneOperations = new Set();\n\t}\n\n\t/**\n\t * Adds an operation to the history.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} operation Operation to add.\n\t */\n\taddOperation( operation ) {\n\t\tif ( this._operations.includes( operation ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._operations.push( operation );\n\t}\n\n\t/**\n\t * Returns operations added to the history.\n\t *\n\t * @param {Number} [from=0] Base version from which operations should be returned (inclusive). Defaults to `0`, which means\n\t * that operations from the first one will be returned.\n\t * @param {Number} [to=Number.POSITIVE_INFINITY] Base version up to which operations should be returned (exclusive).\n\t * Defaults to `Number.POSITIVE_INFINITY` which means that operations up to the last one will be returned.\n\t * @returns {Iterable.} Operations added to the history.\n\t */\n\tgetOperations( from = 0, to = Number.POSITIVE_INFINITY ) {\n\t\tif ( from < 0 ) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn this._operations.slice( from, to );\n\t}\n\n\t/**\n\t * Returns operation from the history that bases on given `baseVersion`.\n\t *\n\t * @param {Number} baseVersion Base version of the operation to get.\n\t * @returns {module:engine/model/operation/operation~Operation|null} Operation with given base version or `null` if\n\t * there is no such operation in history.\n\t */\n\tgetOperation( baseVersion ) {\n\t\treturn this._operations[ baseVersion ];\n\t}\n\n\t/**\n\t * Marks in history that one operation is an operation that is undoing the other operation. By marking operation this way,\n\t * history is keeping more context information about operations, which helps in operational transformation.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} undoneOperation Operation which is undone by `undoingOperation`.\n\t * @param {module:engine/model/operation/operation~Operation} undoingOperation Operation which undoes `undoneOperation`.\n\t */\n\tsetOperationAsUndone( undoneOperation, undoingOperation ) {\n\t\tthis._undoPairs.set( undoingOperation, undoneOperation );\n\t\tthis._undoneOperations.add( undoneOperation );\n\t}\n\n\t/**\n\t * Checks whether given `operation` is undoing any other operation.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} operation Operation to check.\n\t * @returns {Boolean} `true` if given `operation` is undoing any other operation, `false` otherwise.\n\t */\n\tisUndoingOperation( operation ) {\n\t\treturn this._undoPairs.has( operation );\n\t}\n\n\t/**\n\t * Checks whether given `operation` has been undone by any other operation.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} operation Operation to check.\n\t * @returns {Boolean} `true` if given `operation` has been undone any other operation, `false` otherwise.\n\t */\n\tisUndoneOperation( operation ) {\n\t\treturn this._undoneOperations.has( operation );\n\t}\n\n\t/**\n\t * For given `undoingOperation`, returns the operation which has been undone by it.\n\t *\n\t * @param {module:engine/model/operation/operation~Operation} undoingOperation\n\t * @returns {module:engine/model/operation/operation~Operation|undefined} Operation that has been undone by given\n\t * `undoingOperation` or `undefined` if given `undoingOperation` is not undoing any other operation.\n\t */\n\tgetUndoneOperation( undoingOperation ) {\n\t\treturn this._undoPairs.get( undoingOperation );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * Set of utils to handle unicode characters.\n *\n * @module utils/unicode\n */\n\n/**\n * Checks whether given `character` is a combining mark.\n *\n * @param {String} character Character to check.\n * @returns {Boolean}\n */\nexport function isCombiningMark( character ) {\n\treturn !!character && character.length == 1 && /[\\u0300-\\u036f\\u1ab0-\\u1aff\\u1dc0-\\u1dff\\u20d0-\\u20ff\\ufe20-\\ufe2f]/.test( character );\n}\n\n/**\n * Checks whether given `character` is a high half of surrogate pair.\n *\n * Using UTF-16 terminology, a surrogate pair denotes UTF-16 character using two UTF-8 characters. The surrogate pair\n * consist of high surrogate pair character followed by low surrogate pair character.\n *\n * @param {String} character Character to check.\n * @returns {Boolean}\n */\nexport function isHighSurrogateHalf( character ) {\n\treturn !!character && character.length == 1 && /[\\ud800-\\udbff]/.test( character );\n}\n\n/**\n * Checks whether given `character` is a low half of surrogate pair.\n *\n * Using UTF-16 terminology, a surrogate pair denotes UTF-16 character using two UTF-8 characters. The surrogate pair\n * consist of high surrogate pair character followed by low surrogate pair character.\n *\n * @param {String} character Character to check.\n * @returns {Boolean}\n */\nexport function isLowSurrogateHalf( character ) {\n\treturn !!character && character.length == 1 && /[\\udc00-\\udfff]/.test( character );\n}\n\n/**\n * Checks whether given offset in a string is inside a surrogate pair (between two surrogate halves).\n *\n * @param {String} string String to check.\n * @param {Number} offset Offset to check.\n * @returns {Boolean}\n */\nexport function isInsideSurrogatePair( string, offset ) {\n\treturn isHighSurrogateHalf( string.charAt( offset - 1 ) ) && isLowSurrogateHalf( string.charAt( offset ) );\n}\n\n/**\n * Checks whether given offset in a string is between base character and combining mark or between two combining marks.\n *\n * @param {String} string String to check.\n * @param {Number} offset Offset to check.\n * @returns {Boolean}\n */\nexport function isInsideCombinedSymbol( string, offset ) {\n\treturn isCombiningMark( string.charAt( offset ) );\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/document\n */\n\nimport Differ from './differ';\nimport RootElement from './rootelement';\nimport History from './history';\nimport DocumentSelection from './documentselection';\nimport Collection from '@ckeditor/ckeditor5-utils/src/collection';\nimport EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\nimport mix from '@ckeditor/ckeditor5-utils/src/mix';\nimport { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckeditor5-utils/src/unicode';\nimport { clone } from 'lodash-es';\n\n// @if CK_DEBUG_ENGINE // const { logDocument } = require( '../dev-utils/utils' );\n\nconst graveyardName = '$graveyard';\n\n/**\n * Data model's document. It contains the model's structure, its selection and the history of changes.\n *\n * Read more about working with the model in\n * {@glink framework/guides/architecture/editing-engine#model introduction to the the editing engine's architecture}.\n *\n * Usually, the document contains just one {@link module:engine/model/document~Document#roots root element}, so\n * you can retrieve it by just calling {@link module:engine/model/document~Document#getRoot} without specifying its name:\n *\n *\t\tmodel.document.getRoot(); // -> returns the main root\n *\n * However, the document may contain multiple roots – e.g. when the editor has multiple editable areas\n * (e.g. a title and a body of a message).\n *\n * @mixes module:utils/emittermixin~EmitterMixin\n */\nexport default class Document {\n\t/**\n\t * Creates an empty document instance with no {@link #roots} (other than\n\t * the {@link #graveyard graveyard root}).\n\t */\n\tconstructor( model ) {\n\t\t/**\n\t\t * The {@link module:engine/model/model~Model model} that the document is a part of.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:engine/model/model~Model}\n\t\t */\n\t\tthis.model = model;\n\n\t\t/**\n\t\t * The document version. It starts from `0` and every operation increases the version number. It is used to ensure that\n\t\t * operations are applied on a proper document version.\n\t\t *\n\t\t * If the {@link module:engine/model/operation/operation~Operation#baseVersion base version} does not match the document version,\n\t\t * a {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.\n\t\t *\n\t\t * @readonly\n\t\t * @type {Number}\n\t\t */\n\t\tthis.version = 0;\n\n\t\t/**\n\t\t * The document's history.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:engine/model/history~History}\n\t\t */\n\t\tthis.history = new History( this );\n\n\t\t/**\n\t\t * The selection in this document.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:engine/model/documentselection~DocumentSelection}\n\t\t */\n\t\tthis.selection = new DocumentSelection( this );\n\n\t\t/**\n\t\t * A list of roots that are owned and managed by this document. Use {@link #createRoot} and\n\t\t * {@link #getRoot} to manipulate it.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:utils/collection~Collection}\n\t\t */\n\t\tthis.roots = new Collection( { idProperty: 'rootName' } );\n\n\t\t/**\n\t\t * The model differ object. Its role is to buffer changes done on the model document and then calculate a diff of those changes.\n\t\t *\n\t\t * @readonly\n\t\t * @type {module:engine/model/differ~Differ}\n\t\t */\n\t\tthis.differ = new Differ( model.markers );\n\n\t\t/**\n\t\t * Post-fixer callbacks registered to the model document.\n\t\t *\n\t\t * @private\n\t\t * @type {Set.}\n\t\t */\n\t\tthis._postFixers = new Set();\n\n\t\t/**\n\t\t * A boolean indicates whether the selection has changed until\n\t\t *\n\t\t * @private\n\t\t * @type {Boolean}\n\t\t */\n\t\tthis._hasSelectionChangedFromTheLastChangeBlock = false;\n\n\t\t// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.\n\t\tthis.createRoot( '$root', graveyardName );\n\n\t\t// First, if the operation is a document operation check if it's base version is correct.\n\t\tthis.listenTo( model, 'applyOperation', ( evt, args ) => {\n\t\t\tconst operation = args[ 0 ];\n\n\t\t\tif ( operation.isDocumentOperation && operation.baseVersion !== this.version ) {\n\t\t\t\t/**\n\t\t\t\t * Only operations with matching versions can be applied.\n\t\t\t\t *\n\t\t\t\t * @error document-applyOperation-wrong-version\n\t\t\t\t * @param {module:engine/model/operation/operation~Operation} operation\n\t\t\t\t */\n\t\t\t\tthrow new CKEditorError(\n\t\t\t\t\t'model-document-applyOperation-wrong-version: Only operations with matching versions can be applied.',\n\t\t\t\t\tthis,\n\t\t\t\t\t{ operation }\n\t\t\t\t);\n\t\t\t}\n\t\t}, { priority: 'highest' } );\n\n\t\t// Then, still before an operation is applied on model, buffer the change in differ.\n\t\tthis.listenTo( model, 'applyOperation', ( evt, args ) => {\n\t\t\tconst operation = args[ 0 ];\n\n\t\t\tif ( operation.isDocumentOperation ) {\n\t\t\t\tthis.differ.bufferOperation( operation );\n\t\t\t}\n\t\t}, { priority: 'high' } );\n\n\t\t// After the operation is applied, bump document's version and add the operation to the history.\n\t\tthis.listenTo( model, 'applyOperation', ( evt, args ) => {\n\t\t\tconst operation = args[ 0 ];\n\n\t\t\tif ( operation.isDocumentOperation ) {\n\t\t\t\tthis.version++;\n\t\t\t\tthis.history.addOperation( operation );\n\t\t\t}\n\t\t}, { priority: 'low' } );\n\n\t\t// Listen to selection changes. If selection changed, mark it.\n\t\tthis.listenTo( this.selection, 'change', () => {\n\t\t\tthis._hasSelectionChangedFromTheLastChangeBlock = true;\n\t\t} );\n\n\t\t// Buffer marker changes.\n\t\t// This is not covered in buffering operations because markers may change outside of them (when they\n\t\t// are modified using `model.markers` collection, not through `MarkerOperation`).\n\t\tthis.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange ) => {\n\t\t\t// Whenever marker is updated, buffer that change.\n\t\t\tthis.differ.bufferMarkerChange( marker.name, oldRange, newRange, marker.affectsData );\n\n\t\t\tif ( oldRange === null ) {\n\t\t\t\t// If this is a new marker, add a listener that will buffer change whenever marker changes.\n\t\t\t\tmarker.on( 'change', ( evt, oldRange ) => {\n\t\t\t\t\tthis.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange(), marker.affectsData );\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\t}\n\n\t/**\n\t * The graveyard tree root. A document always has a graveyard root that stores removed nodes.\n\t *\n\t * @readonly\n\t * @member {module:engine/model/rootelement~RootElement}\n\t */\n\tget graveyard() {\n\t\treturn this.getRoot( graveyardName );\n\t}\n\n\t/**\n\t * Creates a new root.\n\t *\n\t * @param {String} [elementName='$root'] The element name. Defaults to `'$root'` which also has some basic schema defined\n\t * (`$block`s are allowed inside the `$root`). Make sure to define a proper schema if you use a different name.\n\t * @param {String} [rootName='main'] A unique root name.\n\t * @returns {module:engine/model/rootelement~RootElement} The created root.\n\t */\n\tcreateRoot( elementName = '$root', rootName = 'main' ) {\n\t\tif ( this.roots.get( rootName ) ) {\n\t\t\t/**\n\t\t\t * A root with the specified name already exists.\n\t\t\t *\n\t\t\t * @error model-document-createRoot-name-exists\n\t\t\t * @param {module:engine/model/document~Document} doc\n\t\t\t * @param {String} name\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'model-document-createRoot-name-exists: Root with specified name already exists.',\n\t\t\t\tthis,\n\t\t\t\t{ name: rootName }\n\t\t\t);\n\t\t}\n\n\t\tconst root = new RootElement( this, elementName, rootName );\n\t\tthis.roots.add( root );\n\n\t\treturn root;\n\t}\n\n\t/**\n\t * Removes all event listeners set by the document instance.\n\t */\n\tdestroy() {\n\t\tthis.selection.destroy();\n\t\tthis.stopListening();\n\t}\n\n\t/**\n\t * Returns a root by its name.\n\t *\n\t * @param {String} [name='main'] A unique root name.\n\t * @returns {module:engine/model/rootelement~RootElement|null} The root registered under a given name or `null` when\n\t * there is no root with the given name.\n\t */\n\tgetRoot( name = 'main' ) {\n\t\treturn this.roots.get( name );\n\t}\n\n\t/**\n\t * Returns an array with names of all roots (without the {@link #graveyard}) added to the document.\n\t *\n\t * @returns {Array.} Roots names.\n\t */\n\tgetRootNames() {\n\t\treturn Array.from( this.roots, root => root.rootName ).filter( name => name != graveyardName );\n\t}\n\n\t/**\n\t * Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features\n\t * will operate on a correct model state.\n\t *\n\t * An execution of a feature may lead to an incorrect document tree state. The callbacks are used to fix the document tree after\n\t * it has changed. Post-fixers are fired just after all changes from the outermost change block were applied but\n\t * before the {@link module:engine/model/document~Document#event:change change event} is fired. If a post-fixer callback made\n\t * a change, it should return `true`. When this happens, all post-fixers are fired again to check if something else should\n\t * not be fixed in the new document tree state.\n\t *\n\t * As a parameter, a post-fixer callback receives a {@link module:engine/model/writer~Writer writer} instance connected with the\n\t * executed changes block. Thanks to that, all changes done by the callback will be added to the same\n\t * {@link module:engine/model/batch~Batch batch} (and undo step) as the original changes. This makes post-fixer changes transparent\n\t * for the user.\n\t *\n\t * An example of a post-fixer is a callback that checks if all the data were removed from the editor. If so, the\n\t * callback should add an empty paragraph so that the editor is never empty:\n\t *\n\t *\t\tdocument.registerPostFixer( writer => {\n\t *\t\t\tconst changes = document.differ.getChanges();\n\t *\n\t *\t\t\t// Check if the changes lead to an empty root in the editor.\n\t *\t\t\tfor ( const entry of changes ) {\n\t *\t\t\t\tif ( entry.type == 'remove' && entry.position.root.isEmpty ) {\n\t *\t\t\t\t\twriter.insertElement( 'paragraph', entry.position.root, 0 );\n\t *\n\t *\t\t\t\t\t// It is fine to return early, even if multiple roots would need to be fixed.\n\t *\t\t\t\t\t// All post-fixers will be fired again, so if there are more empty roots, those will be fixed, too.\n\t *\t\t\t\t\treturn true;\n\t *\t\t\t\t}\n\t *\t\t\t}\n\t *\t\t} );\n\t *\n\t * @param {Function} postFixer\n\t */\n\tregisterPostFixer( postFixer ) {\n\t\tthis._postFixers.add( postFixer );\n\t}\n\n\t/**\n\t * A custom `toJSON()` method to solve child-parent circular dependencies.\n\t *\n\t * @returns {Object} A clone of this object with the document property changed to a string.\n\t */\n\ttoJSON() {\n\t\tconst json = clone( this );\n\n\t\t// Due to circular references we need to remove parent reference.\n\t\tjson.selection = '[engine.model.DocumentSelection]';\n\t\tjson.model = '[engine.model.Model]';\n\n\t\treturn json;\n\t}\n\n\t/**\n\t * Check if there were any changes done on document, and if so, call post-fixers,\n\t * fire `change` event for features and conversion and then reset the differ.\n\t * Fire `change:data` event when at least one operation or buffered marker changes the data.\n\t *\n\t * @protected\n\t * @fires change\n\t * @fires change:data\n\t * @param {module:engine/model/writer~Writer} writer The writer on which post-fixers will be called.\n\t */\n\t_handleChangeBlock( writer ) {\n\t\tif ( this._hasDocumentChangedFromTheLastChangeBlock() ) {\n\t\t\tthis._callPostFixers( writer );\n\n\t\t\t// Refresh selection attributes according to the final position in the model after the change.\n\t\t\tthis.selection.refresh();\n\n\t\t\tif ( this.differ.hasDataChanges() ) {\n\t\t\t\tthis.fire( 'change:data', writer.batch );\n\t\t\t} else {\n\t\t\t\tthis.fire( 'change', writer.batch );\n\t\t\t}\n\n\t\t\t// Theoretically, it is not necessary to refresh selection after change event because\n\t\t\t// post-fixers are the last who should change the model, but just in case...\n\t\t\tthis.selection.refresh();\n\n\t\t\tthis.differ.reset();\n\t\t}\n\n\t\tthis._hasSelectionChangedFromTheLastChangeBlock = false;\n\t}\n\n\t/**\n\t * Returns whether there is a buffered change or if the selection has changed from the last\n\t * {@link module:engine/model/model~Model#enqueueChange `enqueueChange()` block}\n\t * or {@link module:engine/model/model~Model#change `change()` block}.\n\t *\n\t * @protected\n\t * @returns {Boolean} Returns `true` if document has changed from the last `change()` or `enqueueChange()` block.\n\t */\n\t_hasDocumentChangedFromTheLastChangeBlock() {\n\t\treturn !this.differ.isEmpty || this._hasSelectionChangedFromTheLastChangeBlock;\n\t}\n\n\t/**\n\t * Returns the default root for this document which is either the first root that was added to the document using\n\t * {@link #createRoot} or the {@link #graveyard graveyard root} if no other roots were created.\n\t *\n\t * @protected\n\t * @returns {module:engine/model/rootelement~RootElement} The default root for this document.\n\t */\n\t_getDefaultRoot() {\n\t\tfor ( const root of this.roots ) {\n\t\t\tif ( root !== this.graveyard ) {\n\t\t\t\treturn root;\n\t\t\t}\n\t\t}\n\n\t\treturn this.graveyard;\n\t}\n\n\t/**\n\t * Returns the default range for this selection. The default range is a collapsed range that starts and ends\n\t * at the beginning of this selection's document {@link #_getDefaultRoot default root}.\n\t *\n\t * @protected\n\t * @returns {module:engine/model/range~Range}\n\t */\n\t_getDefaultRange() {\n\t\tconst defaultRoot = this._getDefaultRoot();\n\t\tconst model = this.model;\n\t\tconst schema = model.schema;\n\n\t\t// Find the first position where the selection can be put.\n\t\tconst position = model.createPositionFromPath( defaultRoot, [ 0 ] );\n\t\tconst nearestRange = schema.getNearestSelectionRange( position );\n\n\t\t// If valid selection range is not found - return range collapsed at the beginning of the root.\n\t\treturn nearestRange || model.createRange( position );\n\t}\n\n\t/**\n\t * Checks whether a given {@link module:engine/model/range~Range range} is a valid range for\n\t * the {@link #selection document's selection}.\n\t *\n\t * @private\n\t * @param {module:engine/model/range~Range} range A range to check.\n\t * @returns {Boolean} `true` if `range` is valid, `false` otherwise.\n\t */\n\t_validateSelectionRange( range ) {\n\t\treturn validateTextNodePosition( range.start ) && validateTextNodePosition( range.end );\n\t}\n\n\t/**\n\t * Performs post-fixer loops. Executes post-fixer callbacks as long as none of them has done any changes to the model.\n\t *\n\t * @private\n\t * @param {module:engine/model/writer~Writer} writer The writer on which post-fixer callbacks will be called.\n\t */\n\t_callPostFixers( writer ) {\n\t\tlet wasFixed = false;\n\n\t\tdo {\n\t\t\tfor ( const callback of this._postFixers ) {\n\t\t\t\t// Ensure selection attributes are up to date before each post-fixer.\n\t\t\t\t// https://github.com/ckeditor/ckeditor5-engine/issues/1673.\n\t\t\t\t//\n\t\t\t\t// It might be good to refresh the selection after each operation but at the moment it leads\n\t\t\t\t// to losing attributes for composition or and spell checking\n\t\t\t\t// https://github.com/ckeditor/ckeditor5-typing/issues/188\n\t\t\t\tthis.selection.refresh();\n\n\t\t\t\twasFixed = callback( writer );\n\n\t\t\t\tif ( wasFixed ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} while ( wasFixed );\n\t}\n\n\t/**\n\t * Fired after each {@link module:engine/model/model~Model#enqueueChange `enqueueChange()` block} or the outermost\n\t * {@link module:engine/model/model~Model#change `change()` block} was executed and the document was changed\n\t * during that block's execution.\n\t *\n\t * The changes which this event will cover include:\n\t *\n\t * * document structure changes,\n\t * * selection changes,\n\t * * marker changes.\n\t *\n\t * If you want to be notified about all these changes, then simply listen to this event like this:\n\t *\n\t *\t\tmodel.document.on( 'change', () => {\n\t *\t\t\tconsole.log( 'The document has changed!' );\n\t *\t\t} );\n\t *\n\t * If, however, you only want to be notified about the data changes, then use the\n\t * {@link module:engine/model/document~Document#event:change:data change:data} event,\n\t * which is fired for document structure changes and marker changes (which affects the data).\n\t *\n\t *\t\tmodel.document.on( 'change:data', () => {\n\t *\t\t\tconsole.log( 'The data has changed!' );\n\t *\t\t} );\n\t *\n\t * @event change\n\t * @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.\n\t */\n\n\t/**\n\t * It is a narrower version of the {@link #event:change} event. It is fired for changes which\n\t * affect the editor data. This is:\n\t *\n\t * * document structure changes,\n\t * * marker changes (which affects the data).\n\t *\n\t * If you want to be notified about the data changes, then listen to this event:\n\t *\n\t *\t\tmodel.document.on( 'change:data', () => {\n\t *\t\t\tconsole.log( 'The data has changed!' );\n\t *\t\t} );\n\t *\n\t * If you would like to listen to all document changes, then check out the\n\t * {@link module:engine/model/document~Document#event:change change} event.\n\t *\n\t * @event change:data\n\t * @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.\n\t */\n\n\t// @if CK_DEBUG_ENGINE // log( version = null ) {\n\t// @if CK_DEBUG_ENGINE // \tversion = version === null ? this.version : version;\n\t// @if CK_DEBUG_ENGINE // \tlogDocument( this, version );\n\t// @if CK_DEBUG_ENGINE // }\n}\n\nmix( Document, EmitterMixin );\n\n// Checks whether given range boundary position is valid for document selection, meaning that is not between\n// unicode surrogate pairs or base character and combining marks.\nfunction validateTextNodePosition( rangeBoundary ) {\n\tconst textNode = rangeBoundary.textNode;\n\n\tif ( textNode ) {\n\t\tconst data = textNode.data;\n\t\tconst offset = rangeBoundary.offset - textNode.startOffset;\n\n\t\treturn !isInsideSurrogatePair( data, offset ) && !isInsideCombinedSymbol( data, offset );\n\t}\n\n\treturn true;\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/markercollection\n */\n\nimport LiveRange from './liverange';\nimport EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\nimport mix from '@ckeditor/ckeditor5-utils/src/mix';\n\n/**\n * The collection of all {@link module:engine/model/markercollection~Marker markers} attached to the document.\n * It lets you {@link module:engine/model/markercollection~MarkerCollection#get get} markers or track them using\n * {@link module:engine/model/markercollection~MarkerCollection#event:update} event.\n *\n * To create, change or remove makers use {@link module:engine/model/writer~Writer model writers'} methods:\n * {@link module:engine/model/writer~Writer#addMarker} or {@link module:engine/model/writer~Writer#removeMarker}. Since\n * the writer is the only proper way to change the data model it is not possible to change markers directly using this\n * collection. All markers created by the writer will be automatically added to this collection.\n *\n * By default there is one marker collection available as {@link module:engine/model/model~Model#markers model property}.\n *\n * @see module:engine/model/markercollection~Marker\n */\nexport default class MarkerCollection {\n\t/**\n\t * Creates a markers collection.\n\t */\n\tconstructor() {\n\t\t/**\n\t\t * Stores {@link ~Marker markers} added to the collection.\n\t\t *\n\t\t * @private\n\t\t * @member {Map} #_markers\n\t\t */\n\t\tthis._markers = new Map();\n\t}\n\n\t/**\n\t * Iterable interface.\n\t *\n\t * Iterates over all {@link ~Marker markers} added to the collection.\n\t *\n\t * @returns {Iterable}\n\t */\n\t[ Symbol.iterator ]() {\n\t\treturn this._markers.values();\n\t}\n\n\t/**\n\t * Checks if marker with given `markerName` is in the collection.\n\t *\n\t * @param {String} markerName Marker name.\n\t * @returns {Boolean} `true` if marker with given `markerName` is in the collection, `false` otherwise.\n\t */\n\thas( markerName ) {\n\t\treturn this._markers.has( markerName );\n\t}\n\n\t/**\n\t * Returns {@link ~Marker marker} with given `markerName`.\n\t *\n\t * @param {String} markerName Name of marker to get.\n\t * @returns {module:engine/model/markercollection~Marker|null} Marker with given name or `null` if such marker was\n\t * not added to the collection.\n\t */\n\tget( markerName ) {\n\t\treturn this._markers.get( markerName ) || null;\n\t}\n\n\t/**\n\t * Creates and adds a {@link ~Marker marker} to the `MarkerCollection` with given name on given\n\t * {@link module:engine/model/range~Range range}.\n\t *\n\t * If `MarkerCollection` already had a marker with given name (or {@link ~Marker marker} was passed), the marker in\n\t * collection is updated and {@link module:engine/model/markercollection~MarkerCollection#event:update} event is fired\n\t * but only if there was a change (marker range or {@link module:engine/model/markercollection~Marker#managedUsingOperations}\n\t * flag has changed.\n\t *\n\t * @protected\n\t * @fires module:engine/model/markercollection~MarkerCollection#event:update\n\t * @param {String|module:engine/model/markercollection~Marker} markerOrName Name of marker to set or marker instance to update.\n\t * @param {module:engine/model/range~Range} range Marker range.\n\t * @param {Boolean} [managedUsingOperations=false] Specifies whether the marker is managed using operations.\n\t * @param {Boolean} [affectsData=false] Specifies whether the marker affects the data produced by the data pipeline\n\t * (is persisted in the editor's data).\n\t * @returns {module:engine/model/markercollection~Marker} `Marker` instance which was added or updated.\n\t */\n\t_set( markerOrName, range, managedUsingOperations = false, affectsData = false ) {\n\t\tconst markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;\n\t\tconst oldMarker = this._markers.get( markerName );\n\n\t\tif ( oldMarker ) {\n\t\t\tconst oldRange = oldMarker.getRange();\n\t\t\tlet hasChanged = false;\n\n\t\t\tif ( !oldRange.isEqual( range ) ) {\n\t\t\t\toldMarker._attachLiveRange( LiveRange.fromRange( range ) );\n\t\t\t\thasChanged = true;\n\t\t\t}\n\n\t\t\tif ( managedUsingOperations != oldMarker.managedUsingOperations ) {\n\t\t\t\toldMarker._managedUsingOperations = managedUsingOperations;\n\t\t\t\thasChanged = true;\n\t\t\t}\n\n\t\t\tif ( typeof affectsData === 'boolean' && affectsData != oldMarker.affectsData ) {\n\t\t\t\toldMarker._affectsData = affectsData;\n\t\t\t\thasChanged = true;\n\t\t\t}\n\n\t\t\tif ( hasChanged ) {\n\t\t\t\tthis.fire( 'update:' + markerName, oldMarker, oldRange, range );\n\t\t\t}\n\n\t\t\treturn oldMarker;\n\t\t}\n\n\t\tconst liveRange = LiveRange.fromRange( range );\n\t\tconst marker = new Marker( markerName, liveRange, managedUsingOperations, affectsData );\n\n\t\tthis._markers.set( markerName, marker );\n\t\tthis.fire( 'update:' + markerName, marker, null, range );\n\n\t\treturn marker;\n\t}\n\n\t/**\n\t * Removes given {@link ~Marker marker} or a marker with given name from the `MarkerCollection`.\n\t *\n\t * @protected\n\t * @fires module:engine/model/markercollection~MarkerCollection#event:update\n\t * @param {String} markerOrName Marker or name of a marker to remove.\n\t * @returns {Boolean} `true` if marker was found and removed, `false` otherwise.\n\t */\n\t_remove( markerOrName ) {\n\t\tconst markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;\n\t\tconst oldMarker = this._markers.get( markerName );\n\n\t\tif ( oldMarker ) {\n\t\t\tthis._markers.delete( markerName );\n\t\t\tthis.fire( 'update:' + markerName, oldMarker, oldMarker.getRange(), null );\n\n\t\t\tthis._destroyMarker( oldMarker );\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Fires an {@link module:engine/model/markercollection~MarkerCollection#event:update} event for the given {@link ~Marker marker}\n\t * but does not change the marker. Useful to force {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast\n\t * conversion} for the marker.\n\t *\n\t * @protected\n\t * @fires module:engine/model/markercollection~MarkerCollection#event:update\n\t * @param {String} markerOrName Marker or name of a marker to refresh.\n\t */\n\t_refresh( markerOrName ) {\n\t\tconst markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;\n\t\tconst marker = this._markers.get( markerName );\n\n\t\tif ( !marker ) {\n\t\t\tthrow new CKEditorError( 'markercollection-refresh-marker-not-exists: Marker with provided name does not exists.', this );\n\t\t}\n\n\t\tconst range = marker.getRange();\n\n\t\tthis.fire( 'update:' + markerName, marker, range, range, marker.managedUsingOperations, marker.affectsData );\n\t}\n\n\t/**\n\t * Returns iterator that iterates over all markers, which ranges contain given {@link module:engine/model/position~Position position}.\n\t *\n\t * @param {module:engine/model/position~Position} position\n\t * @returns {Iterable.}\n\t */\n\t* getMarkersAtPosition( position ) {\n\t\tfor ( const marker of this ) {\n\t\t\tif ( marker.getRange().containsPosition( position ) ) {\n\t\t\t\tyield marker;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns iterator that iterates over all markers, which intersects with given {@link module:engine/model/range~Range range}.\n\t *\n\t * @param {module:engine/model/range~Range} range\n\t * @returns {Iterable.}\n\t */\n\t* getMarkersIntersectingRange( range ) {\n\t\tfor ( const marker of this ) {\n\t\t\tif ( marker.getRange().getIntersection( range ) !== null ) {\n\t\t\t\tyield marker;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Destroys marker collection and all markers inside it.\n\t */\n\tdestroy() {\n\t\tfor ( const marker of this._markers.values() ) {\n\t\t\tthis._destroyMarker( marker );\n\t\t}\n\n\t\tthis._markers = null;\n\n\t\tthis.stopListening();\n\t}\n\n\t/**\n\t * Iterates over all markers that starts with given `prefix`.\n\t *\n\t *\t\tconst markerFooA = markersCollection.set( 'foo:a', rangeFooA );\n\t *\t\tconst markerFooB = markersCollection.set( 'foo:b', rangeFooB );\n\t *\t\tconst markerBarA = markersCollection.set( 'bar:a', rangeBarA );\n\t *\t\tconst markerFooBarA = markersCollection.set( 'foobar:a', rangeFooBarA );\n\t *\t\tArray.from( markersCollection.getMarkersGroup( 'foo' ) ); // [ markerFooA, markerFooB ]\n\t *\t\tArray.from( markersCollection.getMarkersGroup( 'a' ) ); // []\n\t *\n\t * @param prefix\n\t * @returns {Iterable.}\n\t */\n\t* getMarkersGroup( prefix ) {\n\t\tfor ( const marker of this._markers.values() ) {\n\t\t\tif ( marker.name.startsWith( prefix + ':' ) ) {\n\t\t\t\tyield marker;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Destroys the marker.\n\t *\n\t * @private\n\t * @param {module:engine/model/markercollection~Marker} marker Marker to destroy.\n\t */\n\t_destroyMarker( marker ) {\n\t\tmarker.stopListening();\n\t\tmarker._detachLiveRange();\n\t}\n\n\t/**\n\t * Fired whenever marker is added, updated or removed from `MarkerCollection`.\n\t *\n\t * @event update\n\t * @param {module:engine/model/markercollection~Marker} marker Updated Marker.\n\t * @param {module:engine/model/range~Range|null} oldRange Marker range before the update. When is not defined it\n\t * means that marker is just added.\n\t * @param {module:engine/model/range~Range|null} newRange Marker range after update. When is not defined it\n\t * means that marker is just removed.\n\t */\n}\n\nmix( MarkerCollection, EmitterMixin );\n\n/**\n * `Marker` is a continuous parts of model (like a range), is named and represent some kind of information about marked\n * part of model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of\n * model document tree, markers are not stored directly in document tree but in\n * {@link module:engine/model/model~Model#markers model markers' collection}. Still, they are document data, by giving\n * additional meaning to the part of a model document between marker start and marker end.\n *\n * In this sense, markers are similar to adding and converting attributes on nodes. The difference is that attribute is\n * connected with a given node (e.g. a character is bold no matter if it gets moved or content around it changes).\n * Markers on the other hand are continuous ranges and are characterized by their start and end position. This means that\n * any character in the marker is marked by the marker. For example, if a character is moved outside of marker it stops being\n * \"special\" and the marker is shrunk. Similarly, when a character is moved into the marker from other place in document\n * model, it starts being \"special\" and the marker is enlarged.\n *\n * Another upside of markers is that finding marked part of document is fast and easy. Using attributes to mark some nodes\n * and then trying to find that part of document would require traversing whole document tree. Marker gives instant access\n * to the range which it is marking at the moment.\n *\n * Markers are built from a name and a range.\n *\n * Range of the marker is updated automatically when document changes, using\n * {@link module:engine/model/liverange~LiveRange live range} mechanism.\n *\n * Name is used to group and identify markers. Names have to be unique, but markers can be grouped by\n * using common prefixes, separated with `:`, for example: `user:john` or `search:3`. That's useful in term of creating\n * namespaces for custom elements (e.g. comments, highlights). You can use this prefixes in\n * {@link module:engine/model/markercollection~MarkerCollection#event:update} listeners to listen on changes in a group of markers.\n * For instance: `model.markers.on( 'update:user', callback );` will be called whenever any `user:*` markers changes.\n *\n * There are two types of markers.\n *\n * 1. Markers managed directly, without using operations. They are added directly by {@link module:engine/model/writer~Writer}\n * to the {@link module:engine/model/markercollection~MarkerCollection} without any additional mechanism. They can be used\n * as bookmarks or visual markers. They are great for showing results of the find, or select link when the focus is in the input.\n *\n * 1. Markers managed using operations. These markers are also stored in {@link module:engine/model/markercollection~MarkerCollection}\n * but changes in these markers is managed the same way all other changes in the model structure - using operations.\n * Therefore, they are handled in the undo stack and synchronized between clients if the collaboration plugin is enabled.\n * This type of markers is useful for solutions like spell checking or comments.\n *\n * Both type of them should be added / updated by {@link module:engine/model/writer~Writer#addMarker}\n * and removed by {@link module:engine/model/writer~Writer#removeMarker} methods.\n *\n *\t\tmodel.change( ( writer ) => {\n * \t\t\tconst marker = writer.addMarker( name, { range, usingOperation: true } );\n *\n * \t\t\t// ...\n *\n * \t\t\twriter.removeMarker( marker );\n *\t\t} );\n *\n * See {@link module:engine/model/writer~Writer} to find more examples.\n *\n * Since markers need to track change in the document, for efficiency reasons, it is best to create and keep as little\n * markers as possible and remove them as soon as they are not needed anymore.\n *\n * Markers can be downcasted and upcasted.\n *\n * Markers downcast happens on {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker} and\n * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:removeMarker} events.\n * Use {@link module:engine/conversion/downcasthelpers downcast converters} or attach a custom converter to mentioned events.\n * For {@link module:engine/controller/datacontroller~DataController data pipeline}, marker should be downcasted to an element.\n * Then, it can be upcasted back to a marker. Again, use {@link module:engine/conversion/upcasthelpers upcast converters} or\n * attach a custom converter to {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element}.\n *\n * `Marker` instances are created and destroyed only by {@link ~MarkerCollection MarkerCollection}.\n */\nclass Marker {\n\t/**\n\t * Creates a marker instance.\n\t *\n\t * @param {String} name Marker name.\n\t * @param {module:engine/model/liverange~LiveRange} liveRange Range marked by the marker.\n\t * @param {Boolean} managedUsingOperations Specifies whether the marker is managed using operations.\n\t * @param {Boolean} affectsData Specifies whether the marker affects the data produced by the data pipeline\n\t * (is persisted in the editor's data).\n\t */\n\tconstructor( name, liveRange, managedUsingOperations, affectsData ) {\n\t\t/**\n\t\t * Marker's name.\n\t\t *\n\t\t * @readonly\n\t\t * @type {String}\n\t\t */\n\t\tthis.name = name;\n\n\t\t/**\n\t\t * Range marked by the marker.\n\t\t *\n\t\t * @protected\n\t\t * @member {module:engine/model/liverange~LiveRange}\n\t\t */\n\t\tthis._liveRange = this._attachLiveRange( liveRange );\n\n\t\t/**\n\t\t * Flag indicates if the marker is managed using operations or not.\n\t\t *\n\t\t * @private\n\t\t * @member {Boolean}\n\t\t */\n\t\tthis._managedUsingOperations = managedUsingOperations;\n\n\t\t/**\n\t\t * Specifies whether the marker affects the data produced by the data pipeline\n\t\t * (is persisted in the editor's data).\n\t\t *\n\t\t * @private\n\t\t * @member {Boolean}\n\t\t */\n\t\tthis._affectsData = affectsData;\n\t}\n\n\t/**\n\t * A value indicating if the marker is managed using operations.\n\t * See {@link ~Marker marker class description} to learn more about marker types.\n\t * See {@link module:engine/model/writer~Writer#addMarker}.\n\t *\n\t * @returns {Boolean}\n\t */\n\tget managedUsingOperations() {\n\t\tif ( !this._liveRange ) {\n\t\t\tthrow new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.', this );\n\t\t}\n\n\t\treturn this._managedUsingOperations;\n\t}\n\n\t/**\n\t * A value indicating if the marker changes the data.\n\t *\n\t * @returns {Boolean}\n\t */\n\tget affectsData() {\n\t\tif ( !this._liveRange ) {\n\t\t\tthrow new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.', this );\n\t\t}\n\n\t\treturn this._affectsData;\n\t}\n\n\t/**\n\t * Returns current marker start position.\n\t *\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tgetStart() {\n\t\tif ( !this._liveRange ) {\n\t\t\tthrow new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.', this );\n\t\t}\n\n\t\treturn this._liveRange.start.clone();\n\t}\n\n\t/**\n\t * Returns current marker end position.\n\t *\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tgetEnd() {\n\t\tif ( !this._liveRange ) {\n\t\t\tthrow new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.', this );\n\t\t}\n\n\t\treturn this._liveRange.end.clone();\n\t}\n\n\t/**\n\t * Returns a range that represents the current state of the marker.\n\t *\n\t * Keep in mind that returned value is a {@link module:engine/model/range~Range Range}, not a\n\t * {@link module:engine/model/liverange~LiveRange LiveRange}. This means that it is up-to-date and relevant only\n\t * until next model document change. Do not store values returned by this method. Instead, store {@link ~Marker#name}\n\t * and get `Marker` instance from {@link module:engine/model/markercollection~MarkerCollection MarkerCollection} every\n\t * time there is a need to read marker properties. This will guarantee that the marker has not been removed and\n\t * that it's data is up-to-date.\n\t *\n\t * @returns {module:engine/model/range~Range}\n\t */\n\tgetRange() {\n\t\tif ( !this._liveRange ) {\n\t\t\tthrow new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.', this );\n\t\t}\n\n\t\treturn this._liveRange.toRange();\n\t}\n\n\t/**\n\t * Checks whether this object is of the given.\n\t *\n\t *\t\tmarker.is( 'marker' ); // -> true\n\t *\t\tmarker.is( 'model:marker' ); // -> true\n\t *\n\t *\t\tmarker.is( 'view:element' ); // -> false\n\t *\t\tmarker.is( 'documentSelection' ); // -> false\n\t *\n\t * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.\n\t *\n\t * @param {String} type\n\t * @returns {Boolean}\n\t */\n\tis( type ) {\n\t\treturn type == 'marker' || type == 'model:marker';\n\t}\n\n\t/**\n\t * Binds new live range to the marker and detach the old one if is attached.\n\t *\n\t * @protected\n\t * @param {module:engine/model/liverange~LiveRange} liveRange Live range to attach\n\t * @returns {module:engine/model/liverange~LiveRange} Attached live range.\n\t */\n\t_attachLiveRange( liveRange ) {\n\t\tif ( this._liveRange ) {\n\t\t\tthis._detachLiveRange();\n\t\t}\n\n\t\t// Delegating does not work with namespaces. Alternatively, we could delegate all events (using `*`).\n\t\tliveRange.delegate( 'change:range' ).to( this );\n\t\tliveRange.delegate( 'change:content' ).to( this );\n\n\t\tthis._liveRange = liveRange;\n\n\t\treturn liveRange;\n\t}\n\n\t/**\n\t * Unbinds and destroys currently attached live range.\n\t *\n\t * @protected\n\t */\n\t_detachLiveRange() {\n\t\tthis._liveRange.stopDelegating( 'change:range', this );\n\t\tthis._liveRange.stopDelegating( 'change:content', this );\n\t\tthis._liveRange.detach();\n\t\tthis._liveRange = null;\n\t}\n\n\t/**\n\t * Fired whenever {@link ~Marker#_liveRange marker range} is changed due to changes on {@link module:engine/model/document~Document}.\n\t * This is a delegated {@link module:engine/model/liverange~LiveRange#event:change:range LiveRange change:range event}.\n\t *\n\t * When marker is removed from {@link module:engine/model/markercollection~MarkerCollection MarkerCollection},\n\t * all event listeners listening to it should be removed. It is best to do it on\n\t * {@link module:engine/model/markercollection~MarkerCollection#event:update MarkerCollection update event}.\n\t *\n\t * @see module:engine/model/liverange~LiveRange#event:change:range\n\t * @event change:range\n\t * @param {module:engine/model/range~Range} oldRange\n\t * @param {Object} data\n\t */\n\n\t/**\n\t * Fired whenever change on {@link module:engine/model/document~Document} is done inside {@link ~Marker#_liveRange marker range}.\n\t * This is a delegated {@link module:engine/model/liverange~LiveRange#event:change:content LiveRange change:content event}.\n\t *\n\t * When marker is removed from {@link module:engine/model/markercollection~MarkerCollection MarkerCollection},\n\t * all event listeners listening to it should be removed. It is best to do it on\n\t * {@link module:engine/model/markercollection~MarkerCollection#event:update MarkerCollection update event}.\n\t *\n\t * @see module:engine/model/liverange~LiveRange#event:change:content\n\t * @event change:content\n\t * @param {module:engine/model/range~Range} oldRange\n\t * @param {Object} data\n\t */\n}\n\nmix( Marker, EmitterMixin );\n\n/**\n * Cannot use a {@link module:engine/model/markercollection~MarkerCollection#destroy destroyed marker} instance.\n *\n * @error marker-destroyed\n */\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/nooperation\n */\n\nimport Operation from './operation';\n\n/**\n * Operation which is doing nothing (\"empty operation\", \"do-nothing operation\", \"noop\"). This is an operation,\n * which when executed does not change the tree model. It still has some parameters defined for transformation purposes.\n *\n * In most cases this operation is a result of transforming operations. When transformation returns\n * {@link module:engine/model/operation/nooperation~NoOperation} it means that changes done by the transformed operation\n * have already been applied.\n *\n * @extends module:engine/model/operation/operation~Operation\n */\nexport default class NoOperation extends Operation {\n\tget type() {\n\t\treturn 'noop';\n\t}\n\n\t/**\n\t * Creates and returns an operation that has the same parameters as this operation.\n\t *\n\t * @returns {module:engine/model/operation/nooperation~NoOperation} Clone of this operation.\n\t */\n\tclone() {\n\t\treturn new NoOperation( this.baseVersion );\n\t}\n\n\t/**\n\t * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.\n\t *\n\t * @returns {module:engine/model/operation/nooperation~NoOperation}\n\t */\n\tgetReversed() {\n\t\treturn new NoOperation( this.baseVersion + 1 );\n\t}\n\n\t_execute() {\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tstatic get className() {\n\t\treturn 'NoOperation';\n\t}\n\n\t// @if CK_DEBUG_ENGINE // toString() {\n\t// @if CK_DEBUG_ENGINE // \treturn `NoOperation( ${ this.baseVersion } )`;\n\t// @if CK_DEBUG_ENGINE // }\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/operation/operationfactory\n */\n\nimport AttributeOperation from '../operation/attributeoperation';\nimport InsertOperation from '../operation/insertoperation';\nimport MarkerOperation from '../operation/markeroperation';\nimport MoveOperation from '../operation/moveoperation';\nimport NoOperation from '../operation/nooperation';\nimport Operation from '../operation/operation';\nimport RenameOperation from '../operation/renameoperation';\nimport RootAttributeOperation from '../operation/rootattributeoperation';\nimport SplitOperation from '../operation/splitoperation';\nimport MergeOperation from '../operation/mergeoperation';\n\nconst operations = {};\noperations[ AttributeOperation.className ] = AttributeOperation;\noperations[ InsertOperation.className ] = InsertOperation;\noperations[ MarkerOperation.className ] = MarkerOperation;\noperations[ MoveOperation.className ] = MoveOperation;\noperations[ NoOperation.className ] = NoOperation;\noperations[ Operation.className ] = Operation;\noperations[ RenameOperation.className ] = RenameOperation;\noperations[ RootAttributeOperation.className ] = RootAttributeOperation;\noperations[ SplitOperation.className ] = SplitOperation;\noperations[ MergeOperation.className ] = MergeOperation;\n\n/**\n * A factory class for creating operations.\n *\n * @abstract\n */\nexport default class OperationFactory {\n\t/**\n\t * Creates an operation instance from a JSON object (parsed JSON string).\n\t *\n\t * @param {Object} json Deserialized JSON object.\n\t * @param {module:engine/model/document~Document} document Document on which this operation will be applied.\n\t * @returns {module:engine/model/operation/operation~Operation}\n\t */\n\tstatic fromJSON( json, document ) {\n\t\treturn operations[ json.__className ].fromJSON( json, document );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/liveposition\n */\n\nimport Position from './position';\nimport EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';\nimport mix from '@ckeditor/ckeditor5-utils/src/mix';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * `LivePosition` is a type of {@link module:engine/model/position~Position Position}\n * that updates itself as {@link module:engine/model/document~Document document}\n * is changed through operations. It may be used as a bookmark.\n *\n * **Note:** Contrary to {@link module:engine/model/position~Position}, `LivePosition` works only in roots that are\n * {@link module:engine/model/rootelement~RootElement}.\n * If {@link module:engine/model/documentfragment~DocumentFragment} is passed, error will be thrown.\n *\n * **Note:** Be very careful when dealing with `LivePosition`. Each `LivePosition` instance bind events that might\n * have to be unbound.\n * Use {@link module:engine/model/liveposition~LivePosition#detach} whenever you don't need `LivePosition` anymore.\n *\n * @extends module:engine/model/position~Position\n */\nexport default class LivePosition extends Position {\n\t/**\n\t * Creates a live position.\n\t *\n\t * @see module:engine/model/position~Position\n\t * @param {module:engine/model/rootelement~RootElement} root\n\t * @param {Array.} path\n\t * @param {module:engine/model/position~PositionStickiness} [stickiness]\n\t */\n\tconstructor( root, path, stickiness = 'toNone' ) {\n\t\tsuper( root, path, stickiness );\n\n\t\tif ( !this.root.is( 'rootElement' ) ) {\n\t\t\t/**\n\t\t\t * LivePosition's root has to be an instance of RootElement.\n\t\t\t *\n\t\t\t * @error liveposition-root-not-rootelement\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'model-liveposition-root-not-rootelement: LivePosition\\'s root has to be an instance of RootElement.',\n\t\t\t\troot\n\t\t\t);\n\t\t}\n\n\t\tbindWithDocument.call( this );\n\t}\n\n\t/**\n\t * Unbinds all events previously bound by `LivePosition`. Use it whenever you don't need `LivePosition` instance\n\t * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was\n\t * referring to it).\n\t */\n\tdetach() {\n\t\tthis.stopListening();\n\t}\n\n\t/**\n\t * Checks whether this object is of the given.\n\t *\n\t *\t\tlivePosition.is( 'position' ); // -> true\n\t *\t\tlivePosition.is( 'model:position' ); // -> true\n\t *\t\tlivePosition.is( 'liveposition' ); // -> true\n\t *\t\tlivePosition.is( 'model:livePosition' ); // -> true\n\t *\n\t *\t\tlivePosition.is( 'view:position' ); // -> false\n\t *\t\tlivePosition.is( 'documentSelection' ); // -> false\n\t *\n\t * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.\n\t *\n\t * @param {String} type\n\t * @returns {Boolean}\n\t */\n\tis( type ) {\n\t\treturn type == 'livePosition' || type == 'model:livePosition' || super.is( type );\n\t}\n\n\t/**\n\t * Creates a {@link module:engine/model/position~Position position instance}, which is equal to this live position.\n\t *\n\t * @returns {module:engine/model/position~Position}\n\t */\n\ttoPosition() {\n\t\treturn new Position( this.root, this.path.slice(), this.stickiness );\n\t}\n\n\t/**\n\t * Creates a `LivePosition` instance that is equal to position.\n\t *\n\t * @param {module:engine/model/position~Position} position\n\t * @param {module:engine/model/position~PositionStickiness} [stickiness]\n\t * @returns {module:engine/model/position~Position}\n\t */\n\tstatic fromPosition( position, stickiness ) {\n\t\treturn new this( position.root, position.path.slice(), stickiness ? stickiness : position.stickiness );\n\t}\n\n\t/**\n\t * @static\n\t * @protected\n\t * @method module:engine/model/liveposition~LivePosition._createAfter\n\t * @see module:engine/model/position~Position._createAfter\n\t * @param {module:engine/model/node~Node} node\n\t * @param {module:engine/model/position~PositionStickiness} [stickiness='toNone']\n\t * @returns {module:engine/model/liveposition~LivePosition}\n\t */\n\n\t/**\n\t * @static\n\t * @protected\n\t * @method module:engine/model/liveposition~LivePosition._createBefore\n\t * @see module:engine/model/position~Position._createBefore\n\t * @param {module:engine/model/node~Node} node\n\t * @param {module:engine/model/position~PositionStickiness} [stickiness='toNone']\n\t * @returns {module:engine/model/liveposition~LivePosition}\n\t */\n\n\t/**\n\t * @static\n\t * @protected\n\t * @method module:engine/model/liveposition~LivePosition._createAt\n\t * @see module:engine/model/position~Position._createAt\n\t * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition\n\t * @param {Number|'end'|'before'|'after'} [offset]\n\t * @param {module:engine/model/position~PositionStickiness} [stickiness='toNone']\n\t * @returns {module:engine/model/liveposition~LivePosition}\n\t */\n\n\t/**\n\t * Fired when `LivePosition` instance is changed due to changes on {@link module:engine/model/document~Document}.\n\t *\n\t * @event module:engine/model/liveposition~LivePosition#change\n\t * @param {module:engine/model/position~Position} oldPosition Position equal to this live position before it got changed.\n\t */\n}\n\n// Binds this `LivePosition` to the {@link module:engine/model/document~Document document} that owns\n// this position's {@link module:engine/model/position~Position#root root}.\n//\n// @private\nfunction bindWithDocument() {\n\tthis.listenTo(\n\t\tthis.root.document.model,\n\t\t'applyOperation',\n\t\t( event, args ) => {\n\t\t\tconst operation = args[ 0 ];\n\n\t\t\tif ( !operation.isDocumentOperation ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttransform.call( this, operation );\n\t\t},\n\t\t{ priority: 'low' }\n\t);\n}\n\n// Updates this position accordingly to the updates applied to the model. Bases on change events.\n//\n// @private\n// @param {module:engine/model/operation/operation~Operation} operation Executed operation.\nfunction transform( operation ) {\n\tconst result = this.getTransformedByOperation( operation );\n\n\tif ( !this.isEqual( result ) ) {\n\t\tconst oldPosition = this.toPosition();\n\n\t\tthis.path = result.path;\n\t\tthis.root = result.root;\n\n\t\tthis.fire( 'change', oldPosition );\n\t}\n}\n\nmix( LivePosition, EmitterMixin );\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/utils/insertcontent\n */\n\nimport Position from '../position';\nimport LivePosition from '../liveposition';\nimport Element from '../element';\nimport Range from '../range';\nimport DocumentSelection from '../documentselection';\nimport Selection from '../selection';\nimport CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';\n\n/**\n * Inserts content into the editor (specified selection) as one would expect the paste\n * functionality to work.\n *\n * If an instance of {@link module:engine/model/selection~Selection} is passed as `selectable` it will be modified\n * to the insertion selection (equal to a range to be selected after insertion).\n *\n * If `selectable` is not passed, the content will be inserted using the current selection of the model document.\n *\n * **Note:** Use {@link module:engine/model/model~Model#insertContent} instead of this function.\n * This function is only exposed to be reusable in algorithms which change the {@link module:engine/model/model~Model#insertContent}\n * method's behavior.\n *\n * @param {module:engine/model/model~Model} model The model in context of which the insertion\n * should be performed.\n * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.\n * @param {module:engine/model/selection~Selectable} [selectable=model.document.selection]\n * Selection into which the content should be inserted.\n * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.\n * @returns {module:engine/model/range~Range} Range which contains all the performed changes. This is a range that, if removed,\n * would return the model to the state before the insertion. If no changes were preformed by `insertContent`, returns a range collapsed\n * at the insertion position.\n */\nexport default function insertContent( model, content, selectable, placeOrOffset ) {\n\treturn model.change( writer => {\n\t\tlet selection;\n\n\t\tif ( !selectable ) {\n\t\t\tselection = model.document.selection;\n\t\t} else if ( selectable instanceof Selection || selectable instanceof DocumentSelection ) {\n\t\t\tselection = selectable;\n\t\t} else {\n\t\t\tselection = writer.createSelection( selectable, placeOrOffset );\n\t\t}\n\n\t\tconst insertionPosition = selection.getFirstPosition();\n\n\t\tif ( !selection.isCollapsed ) {\n\t\t\tmodel.deleteContent( selection, { doNotAutoparagraph: true } );\n\t\t}\n\n\t\tconst insertion = new Insertion( model, writer, insertionPosition );\n\n\t\tlet nodesToInsert;\n\n\t\tif ( content.is( 'documentFragment' ) ) {\n\t\t\tnodesToInsert = content.getChildren();\n\t\t} else {\n\t\t\tnodesToInsert = [ content ];\n\t\t}\n\n\t\tinsertion.handleNodes( nodesToInsert, {\n\t\t\t// The set of children being inserted is the only set in this context\n\t\t\t// so it's the first and last (it's a hack ;)).\n\t\t\tisFirst: true,\n\t\t\tisLast: true\n\t\t} );\n\n\t\tconst newRange = insertion.getSelectionRange();\n\n\t\t/* istanbul ignore else */\n\t\tif ( newRange ) {\n\t\t\tif ( selection instanceof DocumentSelection ) {\n\t\t\t\twriter.setSelection( newRange );\n\t\t\t} else {\n\t\t\t\tselection.setTo( newRange );\n\t\t\t}\n\t\t} else {\n\t\t\t// We are not testing else because it's a safe check for unpredictable edge cases:\n\t\t\t// an insertion without proper range to select.\n\t\t\t//\n\t\t\t// @if CK_DEBUG // console.warn( 'Cannot determine a proper selection range after insertion.' );\n\t\t}\n\n\t\tconst affectedRange = insertion.getAffectedRange() || model.createRange( insertionPosition );\n\n\t\tinsertion.destroy();\n\n\t\treturn affectedRange;\n\t} );\n}\n\n/**\n * Utility class for performing content insertion.\n *\n * @private\n */\nclass Insertion {\n\tconstructor( model, writer, position ) {\n\t\t/**\n\t\t * The model in context of which the insertion should be performed.\n\t\t *\n\t\t * @member {module:engine/model~Model} #model\n\t\t */\n\t\tthis.model = model;\n\n\t\t/**\n\t\t * Batch to which operations will be added.\n\t\t *\n\t\t * @member {module:engine/controller/writer~Batch} #writer\n\t\t */\n\t\tthis.writer = writer;\n\n\t\t/**\n\t\t * The position at which (or near which) the next node will be inserted.\n\t\t *\n\t\t * @member {module:engine/model/position~Position} #position\n\t\t */\n\t\tthis.position = position;\n\n\t\t/**\n\t\t * Elements with which the inserted elements can be merged.\n\t\t *\n\t\t *\t\tx^
y
+ z
(can merge to x
)\n\t\t *\t\tx
^y
+ z
(can merge to y
)\n\t\t *\t\tx^y
+ z
(can merge to xy
which will be split during the action,\n\t\t *\t\t\t\t\t\t\t\tso both its pieces will be added to this set)\n\t\t *\n\t\t *\n\t\t * @member {Set} #canMergeWith\n\t\t */\n\t\tthis.canMergeWith = new Set( [ this.position.parent ] );\n\n\t\t/**\n\t\t * Schema of the model.\n\t\t *\n\t\t * @member {module:engine/model/schema~Schema} #schema\n\t\t */\n\t\tthis.schema = model.schema;\n\n\t\tthis._filterAttributesOf = [];\n\n\t\t/**\n\t\t * Beginning of the affected range. See {@link module:engine/model/utils/insertcontent~Insertion#getAffectedRange}.\n\t\t *\n\t\t * @private\n\t\t * @member {module:engine/model/liveposition~LivePosition|null} #_affectedStart\n\t\t */\n\t\tthis._affectedStart = null;\n\n\t\t/**\n\t\t * End of the affected range. See {@link module:engine/model/utils/insertcontent~Insertion#getAffectedRange}.\n\t\t *\n\t\t * @private\n\t\t * @member {module:engine/model/liveposition~LivePosition|null} #_affectedEnd\n\t\t */\n\t\tthis._affectedEnd = null;\n\t}\n\n\t/**\n\t * Handles insertion of a set of nodes.\n\t *\n\t * @param {Iterable.} nodes Nodes to insert.\n\t * @param {Object} parentContext Context in which parent of these nodes was supposed to be inserted.\n\t * If the parent context is passed it means that the parent element was stripped (was not allowed).\n\t */\n\thandleNodes( nodes, parentContext ) {\n\t\tnodes = Array.from( nodes );\n\n\t\tfor ( let i = 0; i < nodes.length; i++ ) {\n\t\t\tconst node = nodes[ i ];\n\n\t\t\tthis._handleNode( node, {\n\t\t\t\tisFirst: i === 0 && parentContext.isFirst,\n\t\t\t\tisLast: ( i === ( nodes.length - 1 ) ) && parentContext.isLast\n\t\t\t} );\n\t\t}\n\n\t\t// TMP this will become a post-fixer.\n\t\tthis.schema.removeDisallowedAttributes( this._filterAttributesOf, this.writer );\n\t\tthis._filterAttributesOf = [];\n\t}\n\n\t/**\n\t * Returns range to be selected after insertion.\n\t * Returns `null` if there is no valid range to select after insertion.\n\t *\n\t * @returns {module:engine/model/range~Range|null}\n\t */\n\tgetSelectionRange() {\n\t\tif ( this.nodeToSelect ) {\n\t\t\treturn Range._createOn( this.nodeToSelect );\n\t\t}\n\n\t\treturn this.model.schema.getNearestSelectionRange( this.position );\n\t}\n\n\t/**\n\t * Returns a range which contains all the performed changes. This is a range that, if removed, would return the model to the state\n\t * before the insertion. Returns `null` if no changes were done.\n\t *\n\t * @returns {module:engine/model/range~Range|null}\n\t */\n\tgetAffectedRange() {\n\t\tif ( !this._affectedStart ) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn new Range( this._affectedStart, this._affectedEnd );\n\t}\n\n\t/**\n\t * Destroys `Insertion` instance.\n\t */\n\tdestroy() {\n\t\tif ( this._affectedStart ) {\n\t\t\tthis._affectedStart.detach();\n\t\t}\n\n\t\tif ( this._affectedEnd ) {\n\t\t\tthis._affectedEnd.detach();\n\t\t}\n\t}\n\n\t/**\n\t * Handles insertion of a single node.\n\t *\n\t * @private\n\t * @param {module:engine/model/node~Node} node\n\t * @param {Object} context\n\t * @param {Boolean} context.isFirst Whether the given node is the first one in the content to be inserted.\n\t * @param {Boolean} context.isLast Whether the given node is the last one in the content to be inserted.\n\t */\n\t_handleNode( node, context ) {\n\t\t// Let's handle object in a special way.\n\t\t// * They should never be merged with other elements.\n\t\t// * If they are not allowed in any of the selection ancestors, they could be either autoparagraphed or totally removed.\n\t\tif ( this.schema.isObject( node ) ) {\n\t\t\tthis._handleObject( node, context );\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Try to find a place for the given node.\n\t\t// Split the position.parent's branch up to a point where the node can be inserted.\n\t\t// If it isn't allowed in the whole branch, then of course don't split anything.\n\t\tconst isAllowed = this._checkAndSplitToAllowedPosition( node, context );\n\n\t\tif ( !isAllowed ) {\n\t\t\tthis._handleDisallowedNode( node, context );\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis._insert( node );\n\n\t\t// After the node was inserted we may try to merge it with its siblings.\n\t\t// This should happen only if it was the first and/or last of the nodes (so only with boundary nodes)\n\t\t// and only if the selection was in those elements initially.\n\t\t//\n\t\t// E.g.:\n\t\t// x^
+ y
=> x
y
=> xy[]
\n\t\t// and:\n\t\t// x^y
+ z
=> x
^y
+ z
=> x
z
y
=> xz[]y
\n\t\t// but:\n\t\t// x
^
z
+ y
=> x
y
z
(no merging)\n\t\t// x
[ ]z
+ y
=> x
y
z
(no merging, note: after running deleteContents\n\t\t//\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t it's exactly the same case as above)\n\t\tthis._mergeSiblingsOf( node, context );\n\t}\n\n\t/**\n\t * @private\n\t * @param {module:engine/model/element~Element} node The object element.\n\t * @param {Object} context\n\t */\n\t_handleObject( node, context ) {\n\t\t// Try finding it a place in the tree.\n\t\tif ( this._checkAndSplitToAllowedPosition( node ) ) {\n\t\t\tthis._insert( node );\n\t\t}\n\t\t// Try autoparagraphing.\n\t\telse {\n\t\t\tthis._tryAutoparagraphing( node, context );\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {module:engine/model/node~Node} node The disallowed node which needs to be handled.\n\t * @param {Object} context\n\t */\n\t_handleDisallowedNode( node, context ) {\n\t\t// If the node is an element, try inserting its children (strip the parent).\n\t\tif ( node.is( 'element' ) ) {\n\t\t\tthis.handleNodes( node.getChildren(), context );\n\t\t}\n\t\t// If text is not allowed, try autoparagraphing it.\n\t\telse {\n\t\t\tthis._tryAutoparagraphing( node, context );\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {module:engine/model/node~Node} node The node to insert.\n\t */\n\t_insert( node ) {\n\t\t/* istanbul ignore if */\n\t\tif ( !this.schema.checkChild( this.position, node ) ) {\n\t\t\t// Algorithm's correctness check. We should never end up here but it's good to know that we did.\n\t\t\t// Note that it would often be a silent issue if we insert node in a place where it's not allowed.\n\n\t\t\t/**\n\t\t\t * Given node cannot be inserted on the given position.\n\t\t\t *\n\t\t\t * @error insertcontent-wrong-position\n\t\t\t * @param {module:engine/model/node~Node} node Node to insert.\n\t\t\t * @param {module:engine/model/position~Position} position Position to insert the node at.\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'insertcontent-wrong-position: Given node cannot be inserted on the given position.',\n\t\t\t\tthis,\n\t\t\t\t{ node, position: this.position }\n\t\t\t);\n\t\t}\n\n\t\tconst livePos = LivePosition.fromPosition( this.position, 'toNext' );\n\n\t\tthis._setAffectedBoundaries( this.position );\n\t\tthis.writer.insert( node, this.position );\n\n\t\tthis.position = livePos.toPosition();\n\t\tlivePos.detach();\n\n\t\t// The last inserted object should be selected because we can't put a collapsed selection after it.\n\t\tif ( this.schema.isObject( node ) && !this.schema.checkChild( this.position, '$text' ) ) {\n\t\t\tthis.nodeToSelect = node;\n\t\t} else {\n\t\t\tthis.nodeToSelect = null;\n\t\t}\n\n\t\tthis._filterAttributesOf.push( node );\n\t}\n\n\t/**\n\t * Sets `_affectedStart` and `_affectedEnd` to the given `position`. Should be used before a change is done during insertion process to\n\t * mark the affected range.\n\t *\n\t * This method is used before inserting a node or splitting a parent node. `_affectedStart` and `_affectedEnd` are also changed\n\t * during merging, but the logic there is more complicated so it is left out of this function.\n\t *\n\t * @private\n\t * @param {module:engine/model/position~Position} position\n\t */\n\t_setAffectedBoundaries( position ) {\n\t\t// Set affected boundaries stickiness so that those position will \"expand\" when something is inserted in between them:\n\t\t// Foo][bar -> Foo]xx[bar \n\t\t// This is why it cannot be a range but two separate positions.\n\t\tif ( !this._affectedStart ) {\n\t\t\tthis._affectedStart = LivePosition.fromPosition( position, 'toPrevious' );\n\t\t}\n\n\t\t// If `_affectedEnd` is before the new boundary position, expand `_affectedEnd`. This can happen if first inserted node was\n\t\t// inserted into the parent but the next node is moved-out of that parent:\n\t\t// (1) Foo][ -> Foo]xx[ \n\t\t// (2) Foo]xx[ -> Foo]xx [\n\t\tif ( !this._affectedEnd || this._affectedEnd.isBefore( position ) ) {\n\t\t\tif ( this._affectedEnd ) {\n\t\t\t\tthis._affectedEnd.detach();\n\t\t\t}\n\n\t\t\tthis._affectedEnd = LivePosition.fromPosition( position, 'toNext' );\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {module:engine/model/node~Node} node The node which could potentially be merged.\n\t * @param {Object} context\n\t */\n\t_mergeSiblingsOf( node, context ) {\n\t\tif ( !( node instanceof Element ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst mergeLeft = this._canMergeLeft( node, context );\n\t\tconst mergeRight = this._canMergeRight( node, context );\n\t\tconst mergePosLeft = LivePosition._createBefore( node );\n\t\tmergePosLeft.stickiness = 'toNext';\n\t\tconst mergePosRight = LivePosition._createAfter( node );\n\t\tmergePosRight.stickiness = 'toNext';\n\n\t\tif ( mergeLeft ) {\n\t\t\tconst livePosition = LivePosition.fromPosition( this.position );\n\t\t\tlivePosition.stickiness = 'toNext';\n\n\t\t\t// If `_affectedStart` is sames as merge position, it means that the element \"marked\" by `_affectedStart` is going to be\n\t\t\t// removed and its contents will be moved. This won't transform `LivePosition` so `_affectedStart` needs to be moved\n\t\t\t// by hand to properly reflect affected range. (Due to `_affectedStart` and `_affectedEnd` stickiness, the \"range\" is\n\t\t\t// shown as `][`).\n\t\t\t//\n\t\t\t// Example - insert `Abc Xyz ` at the end of `Foo^ `:\n\t\t\t//\n\t\t\t// Foo Bar -->\n\t\t\t// Foo ]Abc Xyz [Bar -->\n\t\t\t// Foo]Abc Xyz [Bar \n\t\t\t//\n\t\t\t// Note, that if we are here then something must have been inserted, so `_affectedStart` and `_affectedEnd` have to be set.\n\t\t\tif ( this._affectedStart.isEqual( mergePosLeft ) ) {\n\t\t\t\tthis._affectedStart.detach();\n\t\t\t\tthis._affectedStart = LivePosition._createAt( mergePosLeft.nodeBefore, 'end', 'toPrevious' );\n\t\t\t}\n\n\t\t\tthis.writer.merge( mergePosLeft );\n\n\t\t\t// If only one element (the merged one) is in the \"affected range\", also move the affected range end appropriately.\n\t\t\t//\n\t\t\t// Example - insert `Abc ` at the of `Foo^ `:\n\t\t\t//\n\t\t\t// Foo Bar -->\n\t\t\t// Foo ]Abc [Bar -->\n\t\t\t// Foo]Abc [Bar -->\n\t\t\t// Foo]Abc[ Bar \n\t\t\tif ( mergePosLeft.isEqual( this._affectedEnd ) && context.isLast ) {\n\t\t\t\tthis._affectedEnd.detach();\n\t\t\t\tthis._affectedEnd = LivePosition._createAt( mergePosLeft.nodeBefore, 'end', 'toNext' );\n\t\t\t}\n\n\t\t\tthis.position = livePosition.toPosition();\n\t\t\tlivePosition.detach();\n\t\t}\n\n\t\tif ( mergeRight ) {\n\t\t\t/* istanbul ignore if */\n\t\t\tif ( !this.position.isEqual( mergePosRight ) ) {\n\t\t\t\t// Algorithm's correctness check. We should never end up here but it's good to know that we did.\n\t\t\t\t// At this point the insertion position should be after the node we'll merge. If it isn't,\n\t\t\t\t// it should need to be secured as in the left merge case.\n\t\t\t\t/**\n\t\t\t\t * An internal error occured during merging insertion content with siblings.\n\t\t\t\t * The insertion position should equal to the merge position.\n\t\t\t\t *\n\t\t\t\t * @error insertcontent-invalid-insertion-position\n\t\t\t\t */\n\t\t\t\tthrow new CKEditorError( 'insertcontent-invalid-insertion-position', this );\n\t\t\t}\n\n\t\t\t// Move the position to the previous node, so it isn't moved to the graveyard on merge.\n\t\t\t// x
[]y
=> x[]
y
\n\t\t\tthis.position = Position._createAt( mergePosRight.nodeBefore, 'end' );\n\n\t\t\t// OK: xx[]
+ yy
=> xx[]yy
(when sticks to previous)\n\t\t\t// NOK: xx[]
+ yy
=> xxyy[]
(when sticks to next)\n\t\t\tconst livePosition = LivePosition.fromPosition( this.position, 'toPrevious' );\n\n\t\t\t// See comment above on moving `_affectedStart`.\n\t\t\tif ( this._affectedEnd.isEqual( mergePosRight ) ) {\n\t\t\t\tthis._affectedEnd.detach();\n\t\t\t\tthis._affectedEnd = LivePosition._createAt( mergePosRight.nodeBefore, 'end', 'toNext' );\n\t\t\t}\n\n\t\t\tthis.writer.merge( mergePosRight );\n\n\t\t\t// See comment above on moving `_affectedStart`.\n\t\t\tif ( mergePosRight.getShiftedBy( -1 ).isEqual( this._affectedStart ) && context.isFirst ) {\n\t\t\t\tthis._affectedStart.detach();\n\t\t\t\tthis._affectedStart = LivePosition._createAt( mergePosRight.nodeBefore, 0, 'toPrevious' );\n\t\t\t}\n\n\t\t\tthis.position = livePosition.toPosition();\n\t\t\tlivePosition.detach();\n\t\t}\n\n\t\tif ( mergeLeft || mergeRight ) {\n\t\t\t// After merge elements that were marked by _insert() to be filtered might be gone so\n\t\t\t// we need to mark the new container.\n\t\t\tthis._filterAttributesOf.push( this.position.parent );\n\t\t}\n\n\t\tmergePosLeft.detach();\n\t\tmergePosRight.detach();\n\t}\n\n\t/**\n\t * Checks whether specified node can be merged with previous sibling element.\n\t *\n\t * @private\n\t * @param {module:engine/model/node~Node} node The node which could potentially be merged.\n\t * @param {Object} context\n\t * @returns {Boolean}\n\t */\n\t_canMergeLeft( node, context ) {\n\t\tconst previousSibling = node.previousSibling;\n\n\t\treturn context.isFirst &&\n\t\t\t( previousSibling instanceof Element ) &&\n\t\t\tthis.canMergeWith.has( previousSibling ) &&\n\t\t\tthis.model.schema.checkMerge( previousSibling, node );\n\t}\n\n\t/**\n\t * Checks whether specified node can be merged with next sibling element.\n\t *\n\t * @private\n\t * @param {module:engine/model/node~Node} node The node which could potentially be merged.\n\t * @param {Object} context\n\t * @returns {Boolean}\n\t */\n\t_canMergeRight( node, context ) {\n\t\tconst nextSibling = node.nextSibling;\n\n\t\treturn context.isLast &&\n\t\t\t( nextSibling instanceof Element ) &&\n\t\t\tthis.canMergeWith.has( nextSibling ) &&\n\t\t\tthis.model.schema.checkMerge( node, nextSibling );\n\t}\n\n\t/**\n\t * Tries wrapping the node in a new paragraph and inserting it this way.\n\t *\n\t * @private\n\t * @param {module:engine/model/node~Node} node The node which needs to be autoparagraphed.\n\t * @param {Object} context\n\t */\n\t_tryAutoparagraphing( node, context ) {\n\t\tconst paragraph = this.writer.createElement( 'paragraph' );\n\n\t\t// Do not autoparagraph if the paragraph won't be allowed there,\n\t\t// cause that would lead to an infinite loop. The paragraph would be rejected in\n\t\t// the next _handleNode() call and we'd be here again.\n\t\tif ( this._getAllowedIn( paragraph, this.position.parent ) && this.schema.checkChild( paragraph, node ) ) {\n\t\t\tparagraph._appendChild( node );\n\t\t\tthis._handleNode( paragraph, context );\n\t\t}\n\t}\n\n\t/**\n\t * @private\n\t * @param {module:engine/model/node~Node} node\n\t * @returns {Boolean} Whether an allowed position was found.\n\t * `false` is returned if the node isn't allowed at any position up in the tree, `true` if was.\n\t */\n\t_checkAndSplitToAllowedPosition( node ) {\n\t\tconst allowedIn = this._getAllowedIn( node, this.position.parent );\n\n\t\tif ( !allowedIn ) {\n\t\t\treturn false;\n\t\t}\n\n\t\twhile ( allowedIn != this.position.parent ) {\n\t\t\t// If a parent which we'd need to leave is a limit element, break.\n\t\t\tif ( this.schema.isLimit( this.position.parent ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif ( this.position.isAtStart ) {\n\t\t\t\t// If insertion position is at the beginning of the parent, move it out instead of splitting.\n\t\t\t\t// ^Foo
-> ^Foo
\n\t\t\t\tconst parent = this.position.parent;\n\n\t\t\t\tthis.position = this.writer.createPositionBefore( parent );\n\n\t\t\t\t// Special case – parent is empty (^
).\n\t\t\t\t//\n\t\t\t\t// 1. parent.isEmpty\n\t\t\t\t// We can remove the element after moving insertion position out of it.\n\t\t\t\t//\n\t\t\t\t// 2. parent.parent === allowedIn\n\t\t\t\t// However parent should remain in place when allowed element is above limit element in document tree.\n\t\t\t\t// For example there shouldn't be allowed to remove empty paragraph from tableCell, when is pasted\n\t\t\t\t// content allowed in $root.\n\t\t\t\tif ( parent.isEmpty && parent.parent === allowedIn ) {\n\t\t\t\t\tthis.writer.remove( parent );\n\t\t\t\t}\n\t\t\t} else if ( this.position.isAtEnd ) {\n\t\t\t\t// If insertion position is at the end of the parent, move it out instead of splitting.\n\t\t\t\t// Foo^
-> Foo
^\n\t\t\t\tthis.position = this.writer.createPositionAfter( this.position.parent );\n\t\t\t} else {\n\t\t\t\tconst tempPos = this.writer.createPositionAfter( this.position.parent );\n\n\t\t\t\tthis._setAffectedBoundaries( this.position );\n\t\t\t\tthis.writer.split( this.position );\n\n\t\t\t\tthis.position = tempPos;\n\n\t\t\t\tthis.canMergeWith.add( this.position.nodeAfter );\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Gets the element in which the given node is allowed. It checks the passed element and all its ancestors.\n\t *\n\t * @private\n\t * @param {module:engine/model/node~Node} node The node to check.\n\t * @param {module:engine/model/element~Element} element The element in which the node's correctness should be checked.\n\t * @returns {module:engine/model/element~Element|null}\n\t */\n\t_getAllowedIn( node, element ) {\n\t\tif ( this.schema.checkChild( element, node ) ) {\n\t\t\treturn element;\n\t\t}\n\n\t\tif ( element.parent ) {\n\t\t\treturn this._getAllowedIn( node, element.parent );\n\t\t}\n\n\t\treturn null;\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/utils/deletecontent\n */\n\nimport LivePosition from '../liveposition';\nimport Range from '../range';\nimport DocumentSelection from '../documentselection';\n\n/**\n * Deletes content of the selection and merge siblings. The resulting selection is always collapsed.\n *\n * **Note:** Use {@link module:engine/model/model~Model#deleteContent} instead of this function.\n * This function is only exposed to be reusable in algorithms\n * which change the {@link module:engine/model/model~Model#deleteContent}\n * method's behavior.\n *\n * @param {module:engine/model/model~Model} model The model in context of which the insertion\n * should be performed.\n * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection\n * Selection of which the content should be deleted.\n * @param {Object} [options]\n * @param {Boolean} [options.leaveUnmerged=false] Whether to merge elements after removing the content of the selection.\n *\n * For example `x[x y]y ` will become:\n *\n * * `x^y ` with the option disabled (`leaveUnmerged == false`)\n * * `x^ y ` with enabled (`leaveUnmerged == true`).\n *\n * Note: {@link module:engine/model/schema~Schema#isObject object} and {@link module:engine/model/schema~Schema#isLimit limit}\n * elements will not be merged.\n *\n * @param {Boolean} [options.doNotResetEntireContent=false] Whether to skip replacing the entire content with a\n * paragraph when the entire content was selected.\n *\n * For example `[x y] ` will become:\n *\n * * `^ ` with the option disabled (`doNotResetEntireContent == false`)\n * * `^ ` with enabled (`doNotResetEntireContent == true`).\n *\n * @param {Boolean} [options.doNotAutoparagraph=false] Whether to create a paragraph if after content deletion selection is moved\n * to a place where text cannot be inserted.\n *\n * For example `x [ ]` will become:\n *\n * * `x [] ` with the option disabled (`doNotAutoparagraph == false`)\n * * `x[] ` with the option enabled (`doNotAutoparagraph == true`).\n *\n * **Note:** if there is no valid position for the selection, the paragraph will always be created:\n *\n * `[ ]` -> `[] `.\n */\nexport default function deleteContent( model, selection, options = {} ) {\n\tif ( selection.isCollapsed ) {\n\t\treturn;\n\t}\n\n\tconst selRange = selection.getFirstRange();\n\n\t// If the selection is already removed, don't do anything.\n\tif ( selRange.root.rootName == '$graveyard' ) {\n\t\treturn;\n\t}\n\n\tconst schema = model.schema;\n\n\tmodel.change( writer => {\n\t\t// 1. Replace the entire content with paragraph.\n\t\t// See: https://github.com/ckeditor/ckeditor5-engine/issues/1012#issuecomment-315017594.\n\t\tif ( !options.doNotResetEntireContent && shouldEntireContentBeReplacedWithParagraph( schema, selection ) ) {\n\t\t\treplaceEntireContentWithParagraph( writer, selection, schema );\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst startPos = selRange.start;\n\t\tconst endPos = LivePosition.fromPosition( selRange.end, 'toNext' );\n\n\t\t// 2. Remove the content if there is any.\n\t\tif ( !selRange.start.isTouching( selRange.end ) ) {\n\t\t\twriter.remove( selRange );\n\t\t}\n\n\t\t// 3. Merge elements in the right branch to the elements in the left branch.\n\t\t// The only reasonable (in terms of data and selection correctness) case in which we need to do that is:\n\t\t//\n\t\t// Fo[ ]ar => Fo^ar \n\t\t//\n\t\t// However, the algorithm supports also merging deeper structures (up to the depth of the shallower branch),\n\t\t// as it's hard to imagine what should actually be the default behavior. Usually, specific features will\n\t\t// want to override that behavior anyway.\n\t\tif ( !options.leaveUnmerged ) {\n\t\t\tmergeBranches( writer, startPos, endPos );\n\n\t\t\t// TMP this will be replaced with a postfixer.\n\t\t\t// We need to check and strip disallowed attributes in all nested nodes because after merge\n\t\t\t// some attributes could end up in a path where are disallowed.\n\t\t\t//\n\t\t\t// e.g. bold is disallowed for \n\t\t\t// Fo{o b}ar
->
Fo{}ar -> Fo{}ar.\n\t\t\tschema.removeDisallowedAttributes( startPos.parent.getChildren(), writer );\n\t\t}\n\n\t\tcollapseSelectionAt( writer, selection, startPos );\n\n\t\t// 4. Add a paragraph to set selection in it.\n\t\t// Check if a text is allowed in the new container. If not, try to create a new paragraph (if it's allowed here).\n\t\tif ( shouldAutoparagraph( schema, startPos ) ) {\n\t\t\t// If auto-paragraphing is off, find the closest valid selection range and collapse the selection there.\n\t\t\t// If there is no valid selection range, create paragraph anyway and set selection there.\n\t\t\tconst validSelectionRange = schema.getNearestSelectionRange( startPos );\n\n\t\t\tif ( options.doNotAutoparagraph && validSelectionRange ) {\n\t\t\t\tcollapseSelectionAt( writer, selection, validSelectionRange );\n\t\t\t} else {\n\t\t\t\tinsertParagraph( writer, startPos, selection );\n\t\t\t}\n\t\t}\n\n\t\tendPos.detach();\n\t} );\n}\n\n// This function is a result of reaching the Ballmer's peak for just the right amount of time.\n// Even I had troubles documenting it after a while and after reading it again I couldn't believe that it really works.\nfunction mergeBranches( writer, startPos, endPos ) {\n\tconst startParent = startPos.parent;\n\tconst endParent = endPos.parent;\n\n\t// If both positions ended up in the same parent, then there's nothing more to merge:\n\t// <$root> x[]
{}y
$root> => <$root>xy
[]{}$root>\n\tif ( startParent == endParent ) {\n\t\treturn;\n\t}\n\n\t// 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.\n\tif ( writer.model.schema.isLimit( startParent ) || writer.model.schema.isLimit( endParent ) ) {\n\t\treturn;\n\t}\n\n\t// Check if operations we'll need to do won't need to cross object or limit boundaries.\n\t// E.g., we can't merge endParent into startParent in this case:\n\t// x[] {} \n\tif ( !checkCanBeMerged( startPos, endPos, writer.model.schema ) ) {\n\t\treturn;\n\t}\n\n\t// Remember next positions to merge. For example:\n\t// x[] {}y \n\t// will become:\n\t// xy []{} \n\tstartPos = writer.createPositionAfter( startParent );\n\tendPos = writer.createPositionBefore( endParent );\n\n\tif ( !endPos.isEqual( startPos ) ) {\n\t\t// In this case, before we merge, we need to move `endParent` to the `startPos`:\n\t\t// x[] {}y \n\t\t// becomes:\n\t\t// x []y {} \n\t\twriter.insert( endParent, startPos );\n\t}\n\n\t// Merge two siblings:\n\t// x []y -> xy (the usual case)\n\t// x []y -> xy [] (this is the \"move parent\" case shown above)\n\twriter.merge( startPos );\n\n\t// Remove empty end ancestors:\n\t// fo[o bar] \n\t// becomes:\n\t// fo[] {} \n\t// So we can remove and .\n\twhile ( endPos.parent.isEmpty ) {\n\t\tconst parentToRemove = endPos.parent;\n\n\t\tendPos = writer.createPositionBefore( parentToRemove );\n\n\t\twriter.remove( parentToRemove );\n\t}\n\n\t// Continue merging next level.\n\tmergeBranches( writer, startPos, endPos );\n}\n\nfunction shouldAutoparagraph( schema, position ) {\n\tconst isTextAllowed = schema.checkChild( position, '$text' );\n\tconst isParagraphAllowed = schema.checkChild( position, 'paragraph' );\n\n\treturn !isTextAllowed && isParagraphAllowed;\n}\n\n// Check if parents of two positions can be merged by checking if there are no limit/object\n// boundaries between those two positions.\n//\n// E.g. in x[]
{} \n// we'll check , , and .\n// Usually, widget and caption are marked as objects/limits in the schema, so in this case merging will be blocked.\nfunction checkCanBeMerged( leftPos, rightPos, schema ) {\n\tconst rangeToCheck = new Range( leftPos, rightPos );\n\n\tfor ( const value of rangeToCheck.getWalker() ) {\n\t\tif ( schema.isLimit( value.item ) ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction insertParagraph( writer, position, selection ) {\n\tconst paragraph = writer.createElement( 'paragraph' );\n\n\twriter.insert( paragraph, position );\n\n\tcollapseSelectionAt( writer, selection, writer.createPositionAt( paragraph, 0 ) );\n}\n\nfunction replaceEntireContentWithParagraph( writer, selection ) {\n\tconst limitElement = writer.model.schema.getLimitElement( selection );\n\n\twriter.remove( writer.createRangeIn( limitElement ) );\n\tinsertParagraph( writer, writer.createPositionAt( limitElement, 0 ), selection );\n}\n\n// We want to replace the entire content with a paragraph when:\n// * the entire content is selected,\n// * selection contains at least two elements,\n// * whether the paragraph is allowed in schema in the common ancestor.\nfunction shouldEntireContentBeReplacedWithParagraph( schema, selection ) {\n\tconst limitElement = schema.getLimitElement( selection );\n\n\tif ( !selection.containsEntireContent( limitElement ) ) {\n\t\treturn false;\n\t}\n\n\tconst range = selection.getFirstRange();\n\n\tif ( range.start.parent == range.end.parent ) {\n\t\treturn false;\n\t}\n\n\treturn schema.checkChild( limitElement, 'paragraph' );\n}\n\n// Helper function that sets the selection. Depending whether given `selection` is a document selection or not,\n// uses a different method to set it.\nfunction collapseSelectionAt( writer, selection, positionOrRange ) {\n\tif ( selection instanceof DocumentSelection ) {\n\t\twriter.setSelection( positionOrRange );\n\t} else {\n\t\tselection.setTo( positionOrRange );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/utils/modifyselection\n */\n\nimport Position from '../position';\nimport TreeWalker from '../treewalker';\nimport Range from '../range';\nimport { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckeditor5-utils/src/unicode';\nimport DocumentSelection from '../documentselection';\n\nconst wordBoundaryCharacters = ' ,.?!:;\"-()';\n\n/**\n * Modifies the selection. Currently, the supported modifications are:\n *\n * * Extending. The selection focus is moved in the specified `options.direction` with a step specified in `options.unit`.\n * Possible values for `unit` are:\n * * `'character'` (default) - moves selection by one user-perceived character. In most cases this means moving by one\n * character in `String` sense. However, unicode also defines \"combing marks\". These are special symbols, that combines\n * with a symbol before it (\"base character\") to create one user-perceived character. For example, `q̣̇` is a normal\n * letter `q` with two \"combining marks\": upper dot (`Ux0307`) and lower dot (`Ux0323`). For most actions, i.e. extending\n * selection by one position, it is correct to include both \"base character\" and all of it's \"combining marks\". That is\n * why `'character'` value is most natural and common method of modifying selection.\n * * `'codePoint'` - moves selection by one unicode code point. In contrary to, `'character'` unit, this will insert\n * selection between \"base character\" and \"combining mark\", because \"combining marks\" have their own unicode code points.\n * However, for technical reasons, unicode code points with values above `UxFFFF` are represented in native `String` by\n * two characters, called \"surrogate pairs\". Halves of \"surrogate pairs\" have a meaning only when placed next to each other.\n * For example `𨭎` is represented in `String` by `\\uD862\\uDF4E`. Both `\\uD862` and `\\uDF4E` do not have any meaning\n * outside the pair (are rendered as ? when alone). Position between them would be incorrect. In this case, selection\n * extension will include whole \"surrogate pair\".\n * * `'word'` - moves selection by a whole word.\n *\n * **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.\n *\n * **Note:** Use {@link module:engine/model/model~Model#modifySelection} instead of this function.\n * This function is only exposed to be reusable in algorithms\n * which change the {@link module:engine/model/model~Model#modifySelection}\n * method's behavior.\n *\n * @param {module:engine/model/model~Model} model The model in context of which\n * the selection modification should be performed.\n * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection\n * The selection to modify.\n * @param {Object} [options]\n * @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.\n * @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.\n */\nexport default function modifySelection( model, selection, options = {} ) {\n\tconst schema = model.schema;\n\tconst isForward = options.direction != 'backward';\n\tconst unit = options.unit ? options.unit : 'character';\n\n\tconst focus = selection.focus;\n\n\tconst walker = new TreeWalker( {\n\t\tboundaries: getSearchRange( focus, isForward ),\n\t\tsingleCharacters: true,\n\t\tdirection: isForward ? 'forward' : 'backward'\n\t} );\n\n\tconst data = { walker, schema, isForward, unit };\n\n\tlet next;\n\n\twhile ( ( next = walker.next() ) ) {\n\t\tif ( next.done ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst position = tryExtendingTo( data, next.value );\n\n\t\tif ( position ) {\n\t\t\tif ( selection instanceof DocumentSelection ) {\n\t\t\t\tmodel.change( writer => {\n\t\t\t\t\twriter.setSelectionFocus( position );\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tselection.setFocus( position );\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n// Checks whether the selection can be extended to the the walker's next value (next position).\n// @param {{ walker, unit, isForward, schema }} data\n// @param {module:engine/view/treewalker~TreeWalkerValue} value\nfunction tryExtendingTo( data, value ) {\n\t// If found text, we can certainly put the focus in it. Let's just find a correct position\n\t// based on the unit.\n\tif ( value.type == 'text' ) {\n\t\tif ( data.unit === 'word' ) {\n\t\t\treturn getCorrectWordBreakPosition( data.walker, data.isForward );\n\t\t}\n\n\t\treturn getCorrectPosition( data.walker, data.unit, data.isForward );\n\t}\n\n\t// Entering an element.\n\tif ( value.type == ( data.isForward ? 'elementStart' : 'elementEnd' ) ) {\n\t\t// If it's an object, we can select it now.\n\t\tif ( data.schema.isObject( value.item ) ) {\n\t\t\treturn Position._createAt( value.item, data.isForward ? 'after' : 'before' );\n\t\t}\n\n\t\t// If text allowed on this position, extend to this place.\n\t\tif ( data.schema.checkChild( value.nextPosition, '$text' ) ) {\n\t\t\treturn value.nextPosition;\n\t\t}\n\t}\n\t// Leaving an element.\n\telse {\n\t\t// If leaving a limit element, stop.\n\t\tif ( data.schema.isLimit( value.item ) ) {\n\t\t\t// NOTE: Fast-forward the walker until the end.\n\t\t\tdata.walker.skip( () => true );\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If text allowed on this position, extend to this place.\n\t\tif ( data.schema.checkChild( value.nextPosition, '$text' ) ) {\n\t\t\treturn value.nextPosition;\n\t\t}\n\t}\n}\n\n// Finds a correct position by walking in a text node and checking whether selection can be extended to given position\n// or should be extended further.\n//\n// @param {module:engine/model/treewalker~TreeWalker} walker\n// @param {String} unit The unit by which selection should be modified.\nfunction getCorrectPosition( walker, unit ) {\n\tconst textNode = walker.position.textNode;\n\n\tif ( textNode ) {\n\t\tconst data = textNode.data;\n\t\tlet offset = walker.position.offset - textNode.startOffset;\n\n\t\twhile ( isInsideSurrogatePair( data, offset ) || ( unit == 'character' && isInsideCombinedSymbol( data, offset ) ) ) {\n\t\t\twalker.next();\n\n\t\t\toffset = walker.position.offset - textNode.startOffset;\n\t\t}\n\t}\n\n\treturn walker.position;\n}\n\n// Finds a correct position of a word break by walking in a text node and checking whether selection can be extended to given position\n// or should be extended further.\n//\n// @param {module:engine/model/treewalker~TreeWalker} walker\n// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.\nfunction getCorrectWordBreakPosition( walker, isForward ) {\n\tlet textNode = walker.position.textNode;\n\n\tif ( textNode ) {\n\t\tlet offset = walker.position.offset - textNode.startOffset;\n\n\t\twhile ( !isAtWordBoundary( textNode.data, offset, isForward ) && !isAtNodeBoundary( textNode, offset, isForward ) ) {\n\t\t\twalker.next();\n\n\t\t\t// Check of adjacent text nodes with different attributes (like BOLD).\n\t\t\t// Example : 'foofoo []bar<$text bold=\"true\">bar$text> bazbaz'\n\t\t\t// should expand to : 'foofoo [bar<$text bold=\"true\">bar$text>] bazbaz'.\n\t\t\tconst nextNode = isForward ? walker.position.nodeAfter : walker.position.nodeBefore;\n\n\t\t\t// Scan only text nodes. Ignore inline elements (like ``).\n\t\t\tif ( nextNode && nextNode.is( 'text' ) ) {\n\t\t\t\t// Check boundary char of an adjacent text node.\n\t\t\t\tconst boundaryChar = nextNode.data.charAt( isForward ? 0 : nextNode.data.length - 1 );\n\n\t\t\t\t// Go to the next node if the character at the boundary of that node belongs to the same word.\n\t\t\t\tif ( !wordBoundaryCharacters.includes( boundaryChar ) ) {\n\t\t\t\t\t// If adjacent text node belongs to the same word go to it & reset values.\n\t\t\t\t\twalker.next();\n\n\t\t\t\t\ttextNode = walker.position.textNode;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toffset = walker.position.offset - textNode.startOffset;\n\t\t}\n\t}\n\n\treturn walker.position;\n}\n\nfunction getSearchRange( start, isForward ) {\n\tconst root = start.root;\n\tconst searchEnd = Position._createAt( root, isForward ? 'end' : 0 );\n\n\tif ( isForward ) {\n\t\treturn new Range( start, searchEnd );\n\t} else {\n\t\treturn new Range( searchEnd, start );\n\t}\n}\n\n// Checks if selection is on word boundary.\n//\n// @param {String} data The text node value to investigate.\n// @param {Number} offset Position offset.\n// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.\nfunction isAtWordBoundary( data, offset, isForward ) {\n\t// The offset to check depends on direction.\n\tconst offsetToCheck = offset + ( isForward ? 0 : -1 );\n\n\treturn wordBoundaryCharacters.includes( data.charAt( offsetToCheck ) );\n}\n\n// Checks if selection is on node boundary.\n//\n// @param {module:engine/model/text~Text} textNode The text node to investigate.\n// @param {Number} offset Position offset.\n// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.\nfunction isAtNodeBoundary( textNode, offset, isForward ) {\n\treturn offset === ( isForward ? textNode.endOffset : 0 );\n}\n","/**\n * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n/**\n * @module engine/model/utils/getselectedcontent\n */\n\n/**\n * Gets a clone of the selected content.\n *\n * For example, for the following selection:\n *\n * ```html\n *