Socket
Socket
Sign inDemoInstall

@ckeditor/ckeditor5-engine

Package Overview
Dependencies
Maintainers
1
Versions
618
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-engine - npm Package Compare versions

Comparing version 10.0.0 to 10.1.0

src/model/utils/selection-post-fixer.js

21

CHANGELOG.md
Changelog
=========
## [10.1.0](https://github.com/ckeditor/ckeditor5-engine/compare/v10.0.0...v10.1.0) (2018-06-21)
### Features
* Introduce `ElementDefinition#priority` property which allows specifying the priority of created element during the downcast conversion. Closes [#1408](https://github.com/ckeditor/ckeditor5-engine/issues/1408). ([e20e133](https://github.com/ckeditor/ckeditor5-engine/commit/e20e133))
* Introduced `ModelDocument#change:data` event. Closes [#1418](https://github.com/ckeditor/ckeditor5-engine/issues/1418). ([872f4ff](https://github.com/ckeditor/ckeditor5-engine/commit/872f4ff))
* Introduced a selection post-fixer. Its role is to ensure that after all changes are applied the selection is placed in a correct position. Closes [#1156](https://github.com/ckeditor/ckeditor5-engine/issues/1156). Closes [#1176](https://github.com/ckeditor/ckeditor5-engine/issues/1176). Closes [#1182](https://github.com/ckeditor/ckeditor5-engine/issues/1182). Closes [ckeditor/ckeditor5-table#11](https://github.com/ckeditor/ckeditor5-table/issues/11). Closes [ckeditor/ckeditor5-table#12](https://github.com/ckeditor/ckeditor5-table/issues/12). Closes [ckeditor/ckeditor5-table#15](https://github.com/ckeditor/ckeditor5-table/issues/15). Closes [ckeditor/ckeditor5#562](https://github.com/ckeditor/ckeditor5/issues/562). Closes [ckeditor/ckeditor5#611](https://github.com/ckeditor/ckeditor5/issues/611). ([6cf91a1](https://github.com/ckeditor/ckeditor5-engine/commit/6cf91a1))
### Bug fixes
* Block filler will be inserted into the container if its last child is a `<br>` element. Closes [#1422](https://github.com/ckeditor/ckeditor5-engine/issues/1422). ([ba3d641](https://github.com/ckeditor/ckeditor5-engine/commit/ba3d641))
* Fixed view <-> DOM conversion of whitespaces around `<br>` elements. Closes [ckeditor/ckeditor5#1024](https://github.com/ckeditor/ckeditor5/issues/1024). ([3e74554](https://github.com/ckeditor/ckeditor5-engine/commit/3e74554))
* Renderer should avoid doing unnecessary DOM structure changes. Ensuring that the DOM gets updated less frequently fixes many issues with text composition. Closes [#1417](https://github.com/ckeditor/ckeditor5-engine/issues/1417). Closes [#1409](https://github.com/ckeditor/ckeditor5-engine/issues/1409). Closes [#1349](https://github.com/ckeditor/ckeditor5-engine/issues/1349). Closes [#1334](https://github.com/ckeditor/ckeditor5-engine/issues/1334). Closes [#898](https://github.com/ckeditor/ckeditor5-engine/issues/898). Closes [ckeditor/ckeditor5-typing#129](https://github.com/ckeditor/ckeditor5-typing/issues/129). Closes [ckeditor/ckeditor5-typing#89](https://github.com/ckeditor/ckeditor5-typing/issues/89). Closes [#1427](https://github.com/ckeditor/ckeditor5-engine/issues/1427). ([457afde](https://github.com/ckeditor/ckeditor5-engine/commit/457afde))
### Other changes
* Renderer now uses partial text replacing when updating text nodes instead of replacing entire nodes. Closes [#403](https://github.com/ckeditor/ckeditor5-engine/issues/403). ([797cd97](https://github.com/ckeditor/ckeditor5-engine/commit/797cd97))
## [10.0.0](https://github.com/ckeditor/ckeditor5-engine/compare/v1.0.0-beta.4...v10.0.0) (2018-04-25)

@@ -103,3 +122,3 @@

* Conversion utilities refactor. Closes [#1236](https://github.com/ckeditor/ckeditor5-engine/issues/1236). ([fd128a1](https://github.com/ckeditor/ckeditor5-engine/commit/fd128a1))
* Fix `render()` and `change()` flow. Introduce postfixers in view. Closes [#1312](https://github.com/ckeditor/ckeditor5-engine/issues/1312). ([63b9d14](https://github.com/ckeditor/ckeditor5-engine/commit/63b9d14))
* Fixed `render()` and `change()` methods flow. Introduced post-fixers in the view. Closes [#1312](https://github.com/ckeditor/ckeditor5-engine/issues/1312). ([63b9d14](https://github.com/ckeditor/ckeditor5-engine/commit/63b9d14))
* Introduced several improvements to conversion helpers. Closes [#1295](https://github.com/ckeditor/ckeditor5-engine/issues/1295). Closes [#1293](https://github.com/ckeditor/ckeditor5-engine/issues/1293). Closes [#1292](https://github.com/ckeditor/ckeditor5-engine/issues/1292). Closes [#1291](https://github.com/ckeditor/ckeditor5-engine/issues/1291). Closes [#1290](https://github.com/ckeditor/ckeditor5-engine/issues/1290). Closes [#1305](https://github.com/ckeditor/ckeditor5-engine/issues/1305). ([809ea24](https://github.com/ckeditor/ckeditor5-engine/commit/809ea24))

@@ -106,0 +125,0 @@ * Keep the same marker instance when marker is updated. ([8eba5e9](https://github.com/ckeditor/ckeditor5-engine/commit/8eba5e9))

28

package.json
{
"name": "@ckeditor/ckeditor5-engine",
"version": "10.0.0",
"version": "10.1.0",
"description": "CKEditor 5 editing engine.",

@@ -28,17 +28,17 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-utils": "^10.0.0"
"@ckeditor/ckeditor5-utils": "^10.1.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^10.0.0",
"@ckeditor/ckeditor5-core": "^10.0.0",
"@ckeditor/ckeditor5-editor-classic": "^10.0.0",
"@ckeditor/ckeditor5-enter": "^10.0.0",
"@ckeditor/ckeditor5-essentials": "^10.0.0",
"@ckeditor/ckeditor5-heading": "^10.0.0",
"@ckeditor/ckeditor5-list": "^10.0.0",
"@ckeditor/ckeditor5-paragraph": "^10.0.0",
"@ckeditor/ckeditor5-typing": "^10.0.0",
"@ckeditor/ckeditor5-undo": "^10.0.0",
"@ckeditor/ckeditor5-widget": "^10.0.0",
"@ckeditor/ckeditor5-link": "^10.0.0",
"@ckeditor/ckeditor5-basic-styles": "^10.0.1",
"@ckeditor/ckeditor5-core": "^10.1.0",
"@ckeditor/ckeditor5-editor-classic": "^10.0.1",
"@ckeditor/ckeditor5-enter": "^10.1.0",
"@ckeditor/ckeditor5-essentials": "^10.1.0",
"@ckeditor/ckeditor5-heading": "^10.0.1",
"@ckeditor/ckeditor5-list": "^11.0.0",
"@ckeditor/ckeditor5-paragraph": "^10.0.1",
"@ckeditor/ckeditor5-typing": "^10.0.1",
"@ckeditor/ckeditor5-undo": "^10.0.1",
"@ckeditor/ckeditor5-widget": "^10.1.0",
"@ckeditor/ckeditor5-link": "^10.0.2",
"eslint": "^4.15.0",

@@ -45,0 +45,0 @@ "eslint-config-ckeditor5": "^1.0.7",

@@ -7,3 +7,3 @@ CKEditor 5 editing engine

[![Build Status](https://travis-ci.org/ckeditor/ckeditor5-engine.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-engine)
[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://www.browserstack.com/automate/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
[![BrowserStack Status](https://automate.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://automate.browserstack.com/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-engine/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-engine?branch=master)

@@ -10,0 +10,0 @@ <br>

@@ -16,7 +16,3 @@ /**

import { convertSelectionChange } from '../conversion/upcast-selection-converters';
import {
convertRangeSelection,
convertCollapsedSelection,
clearAttributes
} from '../conversion/downcast-selection-converters';
import { clearAttributes, convertCollapsedSelection, convertRangeSelection } from '../conversion/downcast-selection-converters';

@@ -23,0 +19,0 @@ import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';

@@ -25,6 +25,6 @@ /**

/**
* An utility class that helps adding converters to upcast and downcast dispatchers.
* A utility class that helps add converters to upcast and downcast dispatchers.
*
* We recommend reading first the {@glink framework/guides/architecture/editing-engine} guide to understand the
* core concepts of the conversion mechanisms.
* We recommend reading the {@glink framework/guides/architecture/editing-engine Editing engine architecture} guide first to
* understand the core concepts of the conversion mechanisms.
*

@@ -40,3 +40,3 @@ * The instance of the conversion manager is available in the

*
* To add a converter to a specific group use the {@link module:engine/conversion/conversion~Conversion#for `for()`}
* To add a converter to a specific group, use the {@link module:engine/conversion/conversion~Conversion#for `for()`}
* method:

@@ -53,5 +53,5 @@ *

* The functions used in `add()` calls are one-way converters (i.e. you need to remember yourself to add
* a converter in the other direction, if you feature requires that). They are also called "conversion helpers".
* a converter in the other direction, if your feature requires that). They are also called "conversion helpers".
* You can find a set of them in the {@link module:engine/conversion/downcast-converters} and
* {@link module:engine/conversion/upcast-converters} modules
* {@link module:engine/conversion/upcast-converters} modules.
*

@@ -61,12 +61,12 @@ * Besides allowing to register converters to specific dispatchers, you can also use methods available in this

*
* * {@link module:engine/conversion/conversion~Conversion#elementToElement `elementToElement()`} –
* model element to view element and vice versa
* * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} –
* model attribute to view element and vice versa
* * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} –
* model attribute to view element and vice versa
* * {@link module:engine/conversion/conversion~Conversion#elementToElement `elementToElement()`} &ndash;
* Model element to view element and vice versa.
* * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} &ndash;
* Model attribute to view element and vice versa.
* * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} &ndash;
* Model attribute to view element and vice versa.
*/
export default class Conversion {
/**
* Creates new Conversion instance.
* Creates a new conversion instance.
*/

@@ -82,12 +82,12 @@ constructor() {

/**
* Registers one or more converters under given group name. Then, group name can be used to assign a converter
* Registers one or more converters under a given group name. The group name can then be used to assign a converter
* to multiple dispatchers at once.
*
* If given group name is used for a second time,
* {@link module:utils/ckeditorerror~CKEditorError conversion-register-group-exists} error is thrown.
* If a given group name is used for the second time, the
* {@link module:utils/ckeditorerror~CKEditorError `conversion-register-group-exists` error} is thrown.
*
* @param {String} groupName A name for dispatchers group.
* @param {String} groupName The name for dispatchers group.
* @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
* module:engine/conversion/upcastdispatcher~UpcastDispatcher>} dispatchers Dispatchers to register
* under given name.
* under the given name.
*/

@@ -108,4 +108,4 @@ register( groupName, dispatchers ) {

/**
* Provides chainable API to assign converters to dispatchers registered under given group name. Converters are added
* by calling `.add()` method of an object returned by this function.
* Provides chainable API to assign converters to dispatchers registered under a given group name. Converters are added
* by calling the `.add()` method of an object returned by this function.
*

@@ -116,23 +116,23 @@ * conversion.for( 'downcast' )

*
* In above example, `conversionHelperA` and `conversionHelperB` will be called for all dispatchers from `'model'` group.
* In this example `conversionHelperA` and `conversionHelperB` will be called for all dispatchers from the `'model'` group.
*
* `.add()` takes exactly one parameter, which is a function. That function should accept one parameter, which
* is a dispatcher instance. The function should add an actual converter to passed dispatcher instance.
* The `.add()` method takes exactly one parameter, which is a function. This function should accept one parameter that
* is a dispatcher instance. The function should add an actual converter to the passed dispatcher instance.
*
* Conversion helpers for most common cases are already provided. They are flexible enough to cover most use cases.
* See documentation to learn how they can be configured.
* See the documentation to learn how they can be configured.
*
* For downcast (model to view conversion), these are:
* For downcast (model-to-view conversion), these are:
*
* * {@link module:engine/conversion/downcast-converters~downcastElementToElement downcast element to element converter},
* * {@link module:engine/conversion/downcast-converters~downcastAttributeToElement downcast attribute to element converter},
* * {@link module:engine/conversion/downcast-converters~downcastAttributeToAttribute downcast attribute to attribute converter}.
* * {@link module:engine/conversion/downcast-converters~downcastElementToElement Downcast element-to-element converter},
* * {@link module:engine/conversion/downcast-converters~downcastAttributeToElement Downcast attribute-to-element converter},
* * {@link module:engine/conversion/downcast-converters~downcastAttributeToAttribute Downcast attribute-to-attribute converter}.
*
* For upcast (view to model conversion), these are:
* For upcast (view-to-model conversion), these are:
*
* * {@link module:engine/conversion/upcast-converters~upcastElementToElement upcast element to element converter},
* * {@link module:engine/conversion/upcast-converters~upcastElementToAttribute upcast attribute to element converter},
* * {@link module:engine/conversion/upcast-converters~upcastAttributeToAttribute upcast attribute to attribute converter}.
* * {@link module:engine/conversion/upcast-converters~upcastElementToElement Upcast element-to-element converter},
* * {@link module:engine/conversion/upcast-converters~upcastElementToAttribute Upcast attribute-to-element converter},
* * {@link module:engine/conversion/upcast-converters~upcastAttributeToAttribute Upcast attribute-to-attribute converter}.
*
* An example of using conversion helpers to convert `paragraph` model element to `p` view element (and back):
* An example of using conversion helpers to convert the `paragraph` model element to the `p` view element (and back):
*

@@ -146,9 +146,9 @@ * // Define conversion configuration - model element 'paragraph' should be converted to view element 'p'.

*
* An example of providing custom conversion helper that uses custom converter function:
* An example of providing a custom conversion helper that uses a custom converter function:
*
* // Adding custom `myConverter` converter for 'paragraph' element insertion, with default priority ('normal').
* // Adding a custom `myConverter` converter for 'paragraph' element insertion, with the default priority ('normal').
* conversion.for( 'downcast' ).add( conversion.customConverter( 'insert:paragraph', myConverter ) );
*
* @param {String} groupName Name of dispatchers group to add converters to.
* @returns {Object} Object with `.add()` method, providing a way to add converters.
* @param {String} groupName The name of dispatchers group to add the converters to.
* @returns {Object} An object with the `.add()` method, providing a way to add converters.
*/

@@ -168,10 +168,10 @@ for( groupName ) {

/**
* Sets up converters between the model and the view which convert a model element to a view element (and vice versa).
* For example, model `<paragraph>Foo</paragraph>` is `<p>Foo</p>` in the view.
* Sets up converters between the model and the view that convert a model element to a view element (and vice versa).
* For example, the model `<paragraph>Foo</paragraph>` is `<p>Foo</p>` in the view.
*
* // Simple conversion from `paragraph` model element to `<p>` view element (and vice versa).
* // A simple conversion from the `paragraph` model element to the `<p>` view element (and vice versa).
* conversion.elementToElement( { model: 'paragraph', view: 'p' } );
*
* // Override other converters by specifying converter definition with higher priority.
* conversion.elementToElement( { model: 'paragraph', view: 'div', priority: 'high' } );
* // Override other converters by specifying a converter definition with a higher priority.
* conversion.elementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );
*

@@ -187,3 +187,3 @@ * // View specified as an object instead of a string.

*
* // Use `upcastAlso` to define other view elements that should be also converted to `paragraph` element.
* // Use `upcastAlso` to define other view elements that should also be converted to a `paragraph` element.
* conversion.elementToElement( {

@@ -195,3 +195,3 @@ * model: 'paragraph',

* {
* // Any element with `display: block` style.
* // Any element with the `display: block` style.
* styles: {

@@ -225,5 +225,5 @@ * display: 'block'

* if ( size > 26 ) {
* // Returned value be an object with the matched properties.
* // Those properties will be "consumed" during conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*

@@ -237,6 +237,6 @@ * return { name: true, styles: [ 'font-size' ] };

*
* `definition.model` is a `String` with a model element name to converter from/to.
* `definition.model` is a `String` with a model element name to convert from or to.
* See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
*
* @param {module:engine/conversion/conversion~ConverterDefinition} definition Converter definition.
* @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
*/

@@ -253,3 +253,3 @@ elementToElement( definition ) {

view,
priority: definition.priority
converterPriority: definition.converterPriority
} )

@@ -261,10 +261,10 @@ );

/**
* Sets up converters between the model and the view which convert a model attribute to a view element (and vice versa).
* For example, model text node with data `"Foo"` and `bold` attribute is `<strong>Foo</strong>` in the view.
* Sets up converters between the model and the view that convert a model attribute to a view element (and vice versa).
* For example, a model text node with `"Foo"` as data and the `bold` attribute is `<strong>Foo</strong>` in the view.
*
* // Simple conversion from `bold=true` attribute to `<strong>` view element (and vice versa).
* // A simple conversion from the `bold=true` attribute to the `<strong>` view element (and vice versa).
* conversion.attributeToElement( { model: 'bold', view: 'strong' } );
*
* // Override other converters by specifying converter definition with higher priority.
* conversion.attributeToElement( { model: 'bold', view: 'b', priority: 'high' } );
* // Override other converters by specifying a converter definition with a higher priority.
* conversion.attributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );
*

@@ -280,4 +280,4 @@ * // View specified as an object instead of a string.

*
* // Use `config.model.name` to define conversion only from given node type, `$text` in this case.
* // The same attribute on different elements may be then handled by a different converter.
* // Use `config.model.name` to define the conversion only from a given node type, `$text` in this case.
* // The same attribute on different elements may then be handled by a different converter.
* conversion.attributeToElement( {

@@ -305,3 +305,3 @@ * model: {

*
* // Use `upcastAlso` to define other view elements that should be also converted to `bold` attribute.
* // Use `upcastAlso` to define other view elements that should also be converted to the `bold` attribute.
* conversion.attributeToElement( {

@@ -326,5 +326,5 @@ * model: 'bold',

* if ( viewElement.is( 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
* // Returned value be an object with the matched properties.
* // Those properties will be "consumed" during conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*

@@ -340,3 +340,3 @@ * return {

*
* // Conversion from/to a model attribute key which value is an enum (`fontSize=big|small`).
* // Conversion from and to a model attribute key whose value is an enum (`fontSize=big|small`).
* // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.

@@ -379,5 +379,5 @@ * conversion.attributeToElement( {

* if ( viewElement.is( 'span' ) && size > 10 ) {
* // Returned value be an object with the matched properties.
* // Those properties will be "consumed" during conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*

@@ -405,5 +405,5 @@ * return { name: true, styles: [ 'font-size' ] };

* if ( viewElement.is( 'span' ) && size < 10 ) {
* // Returned value be an object with the matched properties.
* // Those properties will be "consumed" during conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more.
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*

@@ -418,7 +418,7 @@ * return { name: true, styles: [ 'font-size' ] };

*
* `definition.model` parameter specifies what model attribute should be converted from/to. It can be a `{ key, value }` object
* describing attribute key and value to convert or a `String` specifying just attribute key (then `value` is set to `true`).
* The `definition.model` parameter specifies which model attribute should be converted from or to. It can be a `{ key, value }` object
* describing the attribute key and value to convert or a `String` specifying just the attribute key (then `value` is set to `true`).
* See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
*
* @param {module:engine/conversion/conversion~ConverterDefinition} definition Converter definition.
* @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
*/

@@ -442,9 +442,9 @@ attributeToElement( definition ) {

/**
* Sets up converters between the model and the view which convert a model attribute to a view attribute (and vice versa).
* For example, `<image src='foo.jpg'></image>` is converted to `<img src='foo.jpg'></img>` (same attribute key and value).
* Sets up converters between the model and the view that convert a model attribute to a view attribute (and vice versa).
* For example, `<image src='foo.jpg'></image>` is converted to `<img src='foo.jpg'></img>` (the same attribute key and value).
*
* // Simple conversion from `source` model attribute to `src` view attribute (and vice versa).
* // A simple conversion from the `source` model attribute to the `src` view attribute (and vice versa).
* conversion.attributeToAttribute( { model: 'source', view: 'src' } );
*
* // Attributes values are strictly specified.
* // Attribute values are strictly specified.
* conversion.attributeToAttribute( {

@@ -465,3 +465,3 @@ * model: {

*
* // Set style attribute.
* // Set the style attribute.
* conversion.attributeToAttribute( {

@@ -486,4 +486,4 @@ * model: {

*
* // Conversion from/to a model attribute key which value is an enum (`align=right|center`).
* // Use `upcastAlso` to define other view elements that should be also converted to `align=right` attribute.
* // Conversion from and to a model attribute key whose value is an enum (`align=right|center`).
* // Use `upcastAlso` to define other view elements that should also be converted to the `align=right` attribute.
* conversion.attributeToAttribute( {

@@ -518,13 +518,13 @@ * model: {

*
* `definition.model` parameter specifies what model attribute should be converted from/to.
* The `definition.model` parameter specifies which model attribute should be converted from and to.
* It can be a `{ key, [ values ], [ name ] }` object or a `String`, which will be treated like `{ key: definition.model }`.
* `key` property is the model attribute key to convert from/to.
* `values` are the possible model attribute values. If `values` is not set, model attribute value will be the same as the
* The `key` property is the model attribute key to convert from and to.
* The `values` are the possible model attribute values. If `values` is not set, the model attribute value will be the same as the
* view attribute value.
* If `name` is set, conversion will be set up only for model elements with the given name.
* If `name` is set, the conversion will be set up only for model elements with the given name.
*
* `definition.view` parameter specifies what view attribute should be converted from/to.
* The `definition.view` parameter specifies which view attribute should be converted from and to.
* It can be a `{ key, value, [ name ] }` object or a `String`, which will be treated like `{ key: definition.view }`.
* `key` property is the view attribute key to convert from/to.
* `value` is the view attribute value to convert from/to. If `definition.value` is not set, view attribute value will be
* The `key` property is the view attribute key to convert from and to.
* The `value` is the view attribute value to convert from and to. If `definition.value` is not set, the view attribute value will be
* the same as the model attribute value.

@@ -534,16 +534,16 @@ * If `key` is `'class'`, `value` can be a `String` or an array of `String`s.

* In other cases, `value` is a `String`.
* If `name` is set, conversion will be set up only for model elements with the given name.
* If `definition.model.values` is set, `definition.view` is an object which assigns values from `definition.model.values`
* If `name` is set, the conversion will be set up only for model elements with the given name.
* If `definition.model.values` is set, `definition.view` is an object that assigns values from `definition.model.values`
* to `{ key, value, [ name ] }` objects.
*
* `definition.upcastAlso` specifies which other matching view elements should be also upcast to given model configuration.
* `definition.upcastAlso` specifies which other matching view elements should also be upcast to the given model configuration.
* If `definition.model.values` is set, `definition.upcastAlso` should be an object assigning values from `definition.model.values`
* to {@link module:engine/view/matcher~MatcherPattern}s or arrays of {@link module:engine/view/matcher~MatcherPattern}s.
*
* **Note:** `definition.model` and `definition.view` form should be mirrored, that is the same type of parameters should
* **Note:** `definition.model` and `definition.view` form should be mirrored, so the same types of parameters should
* be given in both parameters.
*
* @param {Object} definition Converter definition.
* @param {String|Object} definition.model Model attribute to convert from/to.
* @param {String|Object} definition.view View attribute to convert from/to.
* @param {Object} definition The converter definition.
* @param {String|Object} definition.model The model attribute to convert from and to.
* @param {String|Object} definition.view The view attribute to convert from and to.
* @param {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [definition.upcastAlso]

@@ -569,6 +569,6 @@ * Any view element matching `definition.upcastAlso` will also be converted to the given model attribute. `definition.upcastAlso`

/**
* Returns dispatchers registered under given group name.
* Returns dispatchers registered under a given group name.
*
* If given group name has not been registered,
* {@link module:utils/ckeditorerror~CKEditorError conversion-for-unknown-group} error is thrown.
* If the given group name has not been registered, the
* {@link module:utils/ckeditorerror~CKEditorError `conversion-for-unknown-group` error} is thrown.
*

@@ -597,21 +597,21 @@ * @private

/**
* Defines how the model should be converted from/to the view.
* Defines how the model should be converted from and to the view.
*
* @typedef {Object} module:engine/conversion/conversion~ConverterDefinition
*
* @property {*} [model] Model conversion definition. Describes model element or model attribute to convert. This parameter differs
* for different functions that accepts `ConverterDefinition`. See the description of a function to learn how to set it.
* @property {module:engine/view/elementdefinition~ElementDefinition|Object} view Definition of a view element to convert from/to.
* If `model` describes multiple values, `view` is an object that assigns those values (`view` object keys) to view element definitions
* @property {*} [model] The model conversion definition. Describes the model element or model attribute to convert. This parameter differs
* for different functions that accept `ConverterDefinition`. See the description of the function to learn how to set it.
* @property {module:engine/view/elementdefinition~ElementDefinition|Object} view The definition of the view element to convert from and
* to. If `model` describes multiple values, `view` is an object that assigns these values (`view` object keys) to view element definitions
* (`view` object values).
* @property {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [upcastAlso]
* Any view element matching `upcastAlso` will also be converted to model. If `model` describes multiple values, `upcastAlso`
* is an object that assigns those values (`upcastAlso` object keys) to {@link module:engine/view/matcher~MatcherPattern}s
* Any view element matching `upcastAlso` will also be converted to the model. If `model` describes multiple values, `upcastAlso`
* is an object that assigns these values (`upcastAlso` object keys) to {@link module:engine/view/matcher~MatcherPattern}s
* (`upcastAlso` object values).
* @property {module:utils/priorities~PriorityString} [priority] Conversion priority.
* @property {module:utils/priorities~PriorityString} [converterPriority] The converter priority.
*/
// Helper function for `Conversion` `.add()` method.
// Helper function for the `Conversion` `.add()` method.
//
// Calls `conversionHelper` on each dispatcher from the group specified earlier in `.for()` call, effectively
// Calls `conversionHelper` on each dispatcher from the group specified earlier in the `.for()` call, effectively
// adding converters to all specified dispatchers.

@@ -618,0 +618,0 @@ //

@@ -17,3 +17,3 @@ /**

/**
* Contains downcast (model to view) converters for {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}.
* Contains downcast (model-to-view) converters for {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}.
*

@@ -30,3 +30,3 @@ * @module engine/conversion/downcast-converters

*
* downcastElementToElement( { model: 'paragraph', view: 'div', priority: 'high' } );
* downcastElementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );
*

@@ -46,8 +46,9 @@ * downcastElementToElement( {

*
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @param {Object} config Conversion configuration.
* @param {String} config.model Name of the model element to convert.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view View element definition or a function
* that takes model element and view writer as a parameters and returns a view container element.
* @param {String} config.model The name of the model element to convert.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
* that takes the model element and view writer as parameters and returns a view container element.
* @returns {Function} Conversion helper.

@@ -61,3 +62,3 @@ */

return dispatcher => {
dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.priority || 'normal' } );
dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } );
};

@@ -69,8 +70,8 @@ }

*
* This conversion results in wrapping view nodes in a view attribute element. For example, model text node with data
* `"Foo"` and `bold` attribute becomes `<strong>Foo</strong>` in the view.
* This conversion results in wrapping view nodes with a view attribute element. For example, a model text node with
* `"Foo"` as data and the `bold` attribute becomes `<strong>Foo</strong>` in the view.
*
* downcastAttributeToElement( { model: 'bold', view: 'strong' } );
*
* downcastAttributeToElement( { model: 'bold', view: 'b', priority: 'high' } );
* downcastAttributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );
*

@@ -123,11 +124,12 @@ * downcastAttributeToElement( {

*
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @param {Object} config Conversion configuration.
* @param {String|Object} config.model Key of the attribute to convert from or a `{ key, values }` object. `values` is an array
* of `String`s with possible values if the model attribute is enumerable.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view View element definition or a function
* that takes model attribute value and view writer as parameters and returns a view attribute element. If `config.model.values` is
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
* of `String`s with possible values if the model attribute is an enumerable.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
* that takes the model attribute value and view writer as parameters and returns a view attribute element. If `config.model.values` is
* given, `config.view` should be an object assigning values from `config.model.values` to view element definitions or functions.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -156,3 +158,3 @@ */

return dispatcher => {
dispatcher.on( eventName, wrap( elementCreator ), { priority: config.priority || 'normal' } );
dispatcher.on( eventName, wrap( elementCreator ), { priority: config.converterPriority || 'normal' } );
};

@@ -164,3 +166,3 @@ }

*
* This conversion results in adding an attribute on a view node, basing on an attribute from a model node. For example,
* This conversion results in adding an attribute to a view node, basing on an attribute from a model node. For example,
* `<image src='foo.jpg'></image>` is converted to `<img src='foo.jpg'></img>`.

@@ -170,3 +172,3 @@ *

*
* downcastAttributeToAttribute( { model: 'source', view: 'href', priority: 'high' } );
* downcastAttributeToAttribute( { model: 'source', view: 'href', converterPriority: 'high' } );
*

@@ -203,13 +205,14 @@ * downcastAttributeToAttribute( {

*
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @param {Object} config Conversion configuration.
* @param {String|Object} config.model Key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
* the attribute key, possible values and, optionally, an element name to convert from.
* @param {String|Object|Function} config.view View attribute key, or a `{ key, value }` object or a function that takes
* model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
* @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
* the model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
* array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
* If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
* `{ key, value }` objects or a functions.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -238,3 +241,3 @@ */

return dispatcher => {
dispatcher.on( eventName, changeAttribute( elementCreator ), { priority: config.priority || 'normal' } );
dispatcher.on( eventName, changeAttribute( elementCreator ), { priority: config.converterPriority || 'normal' } );
};

@@ -246,4 +249,4 @@ }

*
* This conversion results in creating a view element on the boundaries of the converted marker. If converted marker
* is collapsed, only one element is created. For example, model marker set like this `<paragraph>F[oo b]ar</paragraph>`
* This conversion results in creating a view element on the boundaries of the converted marker. If the converted marker
* is collapsed, only one element is created. For example, model marker set like this: `<paragraph>F[oo b]ar</paragraph>`
* becomes `<p>F<span data-marker="search"></span>oo b<span data-marker="search"></span>ar</p>` in the view.

@@ -253,3 +256,3 @@ *

*
* downcastMarkerToElement( { model: 'search', view: 'search-result', priority: 'high' } );
* downcastMarkerToElement( { model: 'search', view: 'search-result', converterPriority: 'high' } );
*

@@ -273,18 +276,18 @@ * downcastMarkerToElement( {

*
* If function is passed as `config.view` parameter, it will be used to generate both boundary elements. The function
* receives `data` object as parameter and should return an instance of {@link module:engine/view/uielement~UIElement view.UIElement}.
* The `data` and `conversionApi` objects are passed from
* If a function is passed as the `config.view` parameter, it will be used to generate both boundary elements. The function
* receives the `data` object as a parameter and should return an instance of the
* {@link module:engine/view/uielement~UIElement view UI element}. The `data` and `conversionApi` objects are passed from
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
* `data.isOpening` parameter is passed, which is set to `true` for marker start boundary element, and `false` to
* marker end boundary element.
* the `data.isOpening` parameter is passed, which is set to `true` for the marker start boundary element, and `false` to
* the marker end boundary element.
*
* This kind of conversion is useful for saving data into data base, so it should be used in data conversion pipeline.
* This kind of conversion is useful for saving data into the database, so it should be used in the data conversion pipeline.
*
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add a converter to the conversion process.
*
* @param {Object} config Conversion configuration.
* @param {String} config.model Name of the model marker (or model marker group) to convert.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view View element definition or a function
* that takes model marker data as a parameter and returns view ui element.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
* that takes the model marker data as a parameter and returns a view UI element.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -298,4 +301,4 @@ */

return dispatcher => {
dispatcher.on( 'addMarker:' + config.model, insertUIElement( config.view ), { priority: config.priority || 'normal' } );
dispatcher.on( 'removeMarker:' + config.model, removeUIElement( config.view ), { priority: config.priority || 'normal' } );
dispatcher.on( 'addMarker:' + config.model, insertUIElement( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'removeMarker:' + config.model, removeUIElement( config.view ), { priority: config.converterPriority || 'normal' } );
};

@@ -310,18 +313,18 @@ }

*
* For text nodes, a `span` {@link module:engine/view/attributeelement~AttributeElement} is created and it wraps all text nodes
* in the converted marker range. For example, model marker set like this `<paragraph>F[oo b]ar</paragraph>` becomes
* For text nodes, a `<span>` {@link module:engine/view/attributeelement~AttributeElement} is created and it wraps all text nodes
* in the converted marker range. For example, a model marker set like this: `<paragraph>F[oo b]ar</paragraph>` becomes
* `<p>F<span class="comment">oo b</span>ar</p>` in the view.
*
* {@link module:engine/view/containerelement~ContainerElement} may provide custom way of handling highlight. Most often,
* the element itself is given classes and attributes described in the highlight descriptor (instead of being wrapped in `span`).
* For example, model marker set like this `[<image src="foo.jpg"></image>]` becomes `<img src="foo.jpg" class="comment"></img>`
* {@link module:engine/view/containerelement~ContainerElement} may provide a custom way of handling highlight. Most often,
* the element itself is given classes and attributes described in the highlight descriptor (instead of being wrapped in `<span>`).
* For example, a model marker set like this: `[<image src="foo.jpg"></image>]` becomes `<img src="foo.jpg" class="comment"></img>`
* in the view.
*
* For container elements, the conversion is two-step. While the converter processes highlight descriptor and passes it
* to a container element, it is the container element instance itself which applies values from highlight descriptor.
* So, in a sense, converter takes care of stating what should be applied on what, while element decides how to apply that.
* For container elements, the conversion is two-step. While the converter processes the highlight descriptor and passes it
* to a container element, it is the container element instance itself that applies values from the highlight descriptor.
* So, in a sense, the converter takes care of stating what should be applied on what, while the element decides how to apply that.
*
* downcastMarkerToHighlight( { model: 'comment', view: { classes: 'comment' } } );
*
* downcastMarkerToHighlight( { model: 'comment', view: { classes: 'new-comment' }, priority: 'high' } );
* downcastMarkerToHighlight( { model: 'comment', view: { classes: 'new-comment' }, converterPriority: 'high' } );
*

@@ -331,3 +334,3 @@ * downcastMarkerToHighlight( {

* view: data => {
* // Assuming that marker name is in a form of comment:commentType.
* // Assuming that the marker name is in a form of comment:commentType.
* const commentType = data.markerName.split( ':' )[ 1 ];

@@ -341,13 +344,14 @@ *

*
* If function is passed as `config.view` parameter, it will be used to generate highlight descriptor. The function
* receives `data` object as parameter and should return a {@link module:engine/conversion/downcast-converters~HighlightDescriptor}.
* If a function is passed as the `config.view` parameter, it will be used to generate the highlight descriptor. The function
* receives the `data` object as a parameter and should return a
* {@link module:engine/conversion/downcast-converters~HighlightDescriptor highlight descriptor}.
* The `data` object properties are passed from {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}.
*
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add converter to conversion process.
* See {@link module:engine/conversion/conversion~Conversion#for} to learn how to add a converter to the conversion process.
*
* @param {Object} config Conversion configuration.
* @param {String} config.model Name of the model marker (or model marker group) to convert.
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor|Function} config.view Highlight descriptor
* which will be used for highlighting or a function that takes model marker data as a parameter and returns a highlight descriptor.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor|Function} config.view A highlight descriptor
* that will be used for highlighting or a function that takes the model marker data as a parameter and returns a highlight descriptor.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -357,10 +361,10 @@ */

return dispatcher => {
dispatcher.on( 'addMarker:' + config.model, highlightText( config.view ), { priority: config.priority || 'normal' } );
dispatcher.on( 'addMarker:' + config.model, highlightElement( config.view ), { priority: config.priority || 'normal' } );
dispatcher.on( 'removeMarker:' + config.model, removeHighlight( config.view ), { priority: config.priority || 'normal' } );
dispatcher.on( 'addMarker:' + config.model, highlightText( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'addMarker:' + config.model, highlightElement( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'removeMarker:' + config.model, removeHighlight( config.view ), { priority: config.converterPriority || 'normal' } );
};
}
// Takes `config.view`, and if it is a {@link module:engine/view/elementdefinition~ElementDefinition}, converts it
// to a function (because lower level converters accepts only element creator functions).
// Takes `config.view`, and if it is an {@link module:engine/view/elementdefinition~ElementDefinition}, converts it
// to a function (because lower level converters accept only element creator functions).
//

@@ -379,3 +383,3 @@ // @param {module:engine/view/elementdefinition~ElementDefinition|Function} view View configuration.

// Creates view element instance from provided viewElementDefinition and class.
// Creates a view element instance from the provided {@link module:engine/view/elementdefinition~ElementDefinition} and class.
//

@@ -393,10 +397,15 @@ // @param {module:engine/view/elementdefinition~ElementDefinition} viewElementDefinition

let element;
const attributes = Object.assign( {}, viewElementDefinition.attributes );
if ( viewElementType == 'container' ) {
element = viewWriter.createContainerElement( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attributes ) );
element = viewWriter.createContainerElement( viewElementDefinition.name, attributes );
} else if ( viewElementType == 'attribute' ) {
element = viewWriter.createAttributeElement( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attributes ) );
const options = {
priority: viewElementDefinition.priority || ViewAttributeElement.DEFAULT_PRIORITY
};
element = viewWriter.createAttributeElement( viewElementDefinition.name, attributes, options );
} else {
// 'ui'.
element = viewWriter.createUIElement( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attributes ) );
element = viewWriter.createUIElement( viewElementDefinition.name, attributes );
}

@@ -443,4 +452,4 @@

// Takes config and adds default parameters if they don't exist and normalizes other parameters to be used in downcast converters
// for generating view attribute.
// Takes the configuration, adds default parameters if they do not exist and normalizes other parameters to be used in downcast converters
// for generating a view attribute.
//

@@ -467,10 +476,10 @@ // @param {Object} view View configuration.

/**
* Function factory, creates a converter that converts node insertion changes from the model to the view.
* Passed function will be provided with all the parameters of the dispatcher's
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert insert event}.
* It's expected that the function returns a {@link module:engine/view/element~Element}.
* The result of the function will be inserted to the view.
* Function factory that creates a converter which converts node insertion changes from the model to the view.
* The function passed will be provided with all the parameters of the dispatcher's
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert` event}.
* It is expected that the function returns an {@link module:engine/view/element~Element}.
* The result of the function will be inserted into the view.
*
* The converter automatically consumes corresponding value from consumables list, stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}) and bind model and view elements.
* The converter automatically consumes the corresponding value from the consumables list, stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}) and binds the model and view elements.
*

@@ -483,3 +492,3 @@ * downcastDispatcher.on(

*
* // Do something fancy with myElem using `modelItem` or other parameters.
* // Do something fancy with `myElem` using `modelItem` or other parameters.
*

@@ -513,5 +522,5 @@ * return myElem;

/**
* Function factory, creates a default downcast converter for text insertion changes.
* Function factory that creates a default downcast converter for text insertion changes.
*
* The converter automatically consumes corresponding value from consumables list and stops the event (see
* The converter automatically consumes the corresponding value from the consumables list and stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).

@@ -538,3 +547,3 @@ *

/**
* Function factory, creates a default downcast converter for node remove changes.
* Function factory that creates a default downcast converter for node remove changes.
*

@@ -567,13 +576,13 @@ * modelDispatcher.on( 'remove', remove() );

/**
* Function factory, creates a converter that converts marker adding change to the view ui element.
* Function factory that creates a converter which converts marker adding change to the
* {@link module:engine/view/uielement~UIElement view UI element}.
*
* The view ui element, that will be added to the view, depends on passed parameter. See {@link ~insertElement}.
* In a case of a non-collapsed range, the ui element will not wrap nodes but separate elements will be placed at the beginning
* The view UI element that will be added to the view depends on the passed parameter. See {@link ~insertElement}.
* In case of a non-collapsed range, the UI element will not wrap nodes but separate elements will be placed at the beginning
* and at the end of the range.
*
* This converter binds created {@link module:engine/view/uielement~UIElement}s with marker name using
* the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
* This converter binds created UI elements with the marker name using {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
*
* @param {module:engine/view/uielement~UIElement|Function} elementCreator View ui element or a function returning a view element
* which will be inserted.
* @param {module:engine/view/uielement~UIElement|Function} elementCreator A view UI element or a function returning the view element
* that will be inserted.
* @returns {Function} Insert element event converter.

@@ -629,8 +638,8 @@ */

/**
* Function factory, returns a default downcast converter for removing {@link module:engine/view/uielement~UIElement ui element}
* Function factory that returns a default downcast converter for removing a {@link module:engine/view/uielement~UIElement UI element}
* basing on marker remove change.
*
* This converter unbinds elements from marker name.
* This converter unbinds elements from the marker name.
*
* @returns {Function} Remove ui element converter.
* @returns {Function} Removed UI element converter.
*/

@@ -658,15 +667,15 @@ export function removeUIElement() {

/**
* Function factory, creates a converter that converts set/change/remove attribute changes from the model to the view.
* Function factory that creates a converter which converts set/change/remove attribute changes from the model to the view.
*
* Attributes from model are converted to the view element attributes in the view. You may provide a custom function to generate
* a key-value attribute pair to add/change/remove. If not provided, model attributes will be converted to view elements
* attributes on 1-to-1 basis.
* Attributes from the model are converted to the view element attributes in the view. You may provide a custom function to generate
* a key-value attribute pair to add/change/remove. If not provided, model attributes will be converted to view element
* attributes on a one-to-one basis.
*
* **Note:** Provided attribute creator should always return the same `key` for given attribute from the model.
* **Note:** The provided attribute creator should always return the same `key` for a given attribute from the model.
*
* The converter automatically consumes corresponding value from consumables list and stops the event (see
* The converter automatically consumes the corresponding value from the consumables list and stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).
*
* modelDispatcher.on( 'attribute:customAttr:myElem', changeAttribute( ( value, data ) => {
* // Change attribute key from `customAttr` to `class` in view.
* // Change attribute key from `customAttr` to `class` in the view.
* const key = 'class';

@@ -680,3 +689,3 @@ * let value = data.attributeNewValue;

*
* // Return key-value pair.
* // Return the key-value pair.
* return { key, value };

@@ -686,4 +695,4 @@ * } ) );

* @param {Function} [attributeCreator] Function returning an object with two properties: `key` and `value`, which
* represents attribute key and attribute value to be set on a {@link module:engine/view/element~Element view element}.
* The function is passed model attribute value as first parameter and additional data about the change as a second parameter.
* represent the attribute key and attribute value to be set on a {@link module:engine/view/element~Element view element}.
* The function is passed the model attribute value as the first parameter and additional data about the change as the second parameter.
* @returns {Function} Set/change attribute converter.

@@ -750,8 +759,8 @@ */

/**
* Function factory, creates a converter that converts set/change/remove attribute changes from the model to the view.
* Also can be used to convert selection attributes. In that case, an empty attribute element will be created and the
* Function factory that creates a converter which converts set/change/remove attribute changes from the model to the view.
* It can also be used to convert selection attributes. In that case, an empty attribute element will be created and the
* selection will be put inside it.
*
* Attributes from model are converted to a view element that will be wrapping those view nodes that are bound to
* model elements having given attribute. This is useful for attributes like `bold`, which may be set on text nodes in model
* Attributes from the model are converted to a view element that will be wrapping these view nodes that are bound to
* model elements having the given attribute. This is useful for attributes like `bold` that may be set on text nodes in the model
* but are represented as an element in the view:

@@ -764,9 +773,9 @@ *

*
* Passed `Function` will be provided with attribute value and then all the parameters of the
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute attribute event}.
* It's expected that the function returns a {@link module:engine/view/element~Element}.
* Passed `Function` will be provided with the attribute value and then all the parameters of the
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute` event}.
* It is expected that the function returns an {@link module:engine/view/element~Element}.
* The result of the function will be the wrapping element.
* When provided `Function` does not return element, then will be no conversion.
* When the provided `Function` does not return any element, no conversion will take place.
*
* The converter automatically consumes corresponding value from consumables list, stops the event (see
* The converter automatically consumes the corresponding value from the consumables list and stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).

@@ -778,3 +787,3 @@ *

*
* @param {Function} elementCreator Function returning a view element, which will be used for wrapping.
* @param {Function} elementCreator Function returning a view element that will be used for wrapping.
* @returns {Function} Set/change attribute converter.

@@ -822,18 +831,18 @@ */

/**
* Function factory, creates converter that converts text inside marker's range. Converter wraps the text with
* {@link module:engine/view/attributeelement~AttributeElement} created from provided descriptor.
* Function factory that creates a converter which converts the text inside marker's range. The converter wraps the text with
* {@link module:engine/view/attributeelement~AttributeElement} created from the provided descriptor.
* See {link module:engine/conversion/downcast-converters~createViewElementFromHighlightDescriptor}.
*
* Also can be used to convert selection that is inside a marker. In that case, an empty attribute element will be
* It can also be used to convert the selection that is inside a marker. In that case, an empty attribute element will be
* created and the selection will be put inside it.
*
* If the highlight descriptor will not provide `priority` property, `10` will be used.
* If the highlight descriptor does not provide the `priority` property, `10` will be used.
*
* If the highlight descriptor will not provide `id` property, name of the marker will be used.
* If the highlight descriptor does not provide the `id` property, the name of the marker will be used.
*
* This converter binds created {@link module:engine/view/attributeelement~AttributeElement}s with marker name using
* the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
* This converter binds the created {@link module:engine/view/attributeelement~AttributeElement attribute elemens} with the marker name
* using the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker} method.
*
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor|Function} highlightDescriptor
* @return {Function}
* @returns {Function}
*/

@@ -884,20 +893,20 @@ export function highlightText( highlightDescriptor ) {

/**
* Converter function factory. Creates a function which applies the marker's highlight to an element inside the marker's range.
* Converter function factory. It creates a function which applies the marker's highlight to an element inside the marker's range.
*
* The converter checks if an element has `addHighlight` function stored as
* The converter checks if an element has the `addHighlight` function stored as a
* {@link module:engine/view/element~Element#_setCustomProperty custom property} and, if so, uses it to apply the highlight.
* In such case converter will consume all element's children, assuming that they were handled by element itself.
* In such case the converter will consume all element's children, assuming that they were handled by the element itself.
*
* When `addHighlight` custom property is not present, element is not converted in any special way.
* This means that converters will proceed to convert element's child nodes.
* When the `addHighlight` custom property is not present, the element is not converted in any special way.
* This means that converters will proceed to convert the element's child nodes.
*
* If the highlight descriptor will not provide `priority` property, `10` will be used.
* If the highlight descriptor does not provide the `priority` property, `10` will be used.
*
* If the highlight descriptor will not provide `id` property, name of the marker will be used.
* If the highlight descriptor does not provide the `id` property, the name of the marker will be used.
*
* This converter binds altered {@link module:engine/view/containerelement~ContainerElement}s with marker name using
* the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
* This converter binds altered {@link module:engine/view/containerelement~ContainerElement container elements} with the marker name using
* the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker} method.
*
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor|Function} highlightDescriptor
* @return {Function}
* @returns {Function}
*/

@@ -943,24 +952,24 @@ export function highlightElement( highlightDescriptor ) {

/**
* Function factory, creates a converter that converts removing model marker to the view.
* Function factory that creates a converter which converts the removing model marker to the view.
*
* Both text nodes and elements are handled by this converter but they are handled a bit differently.
*
* Text nodes are unwrapped using {@link module:engine/view/attributeelement~AttributeElement} created from provided
* highlight descriptor. See {link module:engine/conversion/downcast-converters~highlightDescriptorToAttributeElement}.
* Text nodes are unwrapped using the {@link module:engine/view/attributeelement~AttributeElement attribute element} created from the
* provided highlight descriptor. See {link module:engine/conversion/downcast-converters~HighlightDescriptor}.
*
* For elements, the converter checks if an element has `removeHighlight` function stored as
* For elements, the converter checks if an element has the `removeHighlight` function stored as a
* {@link module:engine/view/element~Element#_setCustomProperty custom property}. If so, it uses it to remove the highlight.
* In such case, children of that element will not be converted.
* In such case, the children of that element will not be converted.
*
* When `removeHighlight` is not present, element is not converted in any special way.
* Instead converter will proceed to convert element's child nodes.
* When `removeHighlight` is not present, the element is not converted in any special way.
* The converter will proceed to convert the element's child nodes instead.
*
* If the highlight descriptor will not provide `priority` property, `10` will be used.
* If the highlight descriptor does not provide the `priority` property, `10` will be used.
*
* If the highlight descriptor will not provide `id` property, name of the marker will be used.
* If the highlight descriptor does not provide the `id` property, the name of the marker will be used.
*
* This converter unbinds elements from marker name.
* This converter unbinds elements from the marker name.
*
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor|Function} highlightDescriptor
* @return {Function}
* @returns {Function}
*/

@@ -1030,5 +1039,5 @@ export function removeHighlight( highlightDescriptor ) {

/**
* Creates `span` {@link module:engine/view/attributeelement~AttributeElement view attribute element} from information
* provided by {@link module:engine/conversion/downcast-converters~HighlightDescriptor} object. If priority
* is not provided in descriptor - default priority will be used.
* Creates a `<span>` {@link module:engine/view/attributeelement~AttributeElement view attribute element} from the information
* provided by the {@link module:engine/conversion/downcast-converters~HighlightDescriptor highlight descriptor} object. If a priority
* is not provided in the descriptor, the default priority will be used.
*

@@ -1055,33 +1064,34 @@ * @param {module:engine/conversion/downcast-converters~HighlightDescriptor} descriptor

/**
* Object describing how the marker highlight should be represented in the view.
* An object describing how the marker highlight should be represented in the view.
*
* Each text node contained in a highlighted range will be wrapped in a `span` {@link module:engine/view/attributeelement~AttributeElement}
* with CSS class(es), attributes and priority described by this object.
* Each text node contained in a highlighted range will be wrapped in a `<span>`
* {@link module:engine/view/attributeelement~AttributeElement view attribute element} with CSS class(es), attributes and a priority
* described by this object.
*
* Additionally, each {@link module:engine/view/containerelement~ContainerElement} can handle displaying the highlight separately
* by providing `addHighlight` and `removeHighlight` custom properties. In this case:
* Additionally, each {@link module:engine/view/containerelement~ContainerElement container element} can handle displaying the highlight
* separately by providing the `addHighlight` and `removeHighlight` custom properties. In this case:
*
* * `HighlightDescriptor` object is passed to the `addHighlight` function upon conversion and should be used to apply the highlight to
* the element,
* * descriptor `id` is passed to the `removeHighlight` function upon conversion and should be used to remove the highlight of given
* id from the element.
* * The `HighlightDescriptor` object is passed to the `addHighlight` function upon conversion and should be used to apply the highlight to
* the element.
* * The descriptor `id` is passed to the `removeHighlight` function upon conversion and should be used to remove the highlight with the
* given ID from the element.
*
* @typedef {Object} module:engine/conversion/downcast-converters~HighlightDescriptor
*
* @property {String|Array.<String>} classes CSS class or array of classes to set. If descriptor is used to
* create {@link module:engine/view/attributeelement~AttributeElement} over text nodes, those classes will be set
* on that {@link module:engine/view/attributeelement~AttributeElement}. If descriptor is applied to an element,
* usually those class will be set on that element, however this depends on how the element converts the descriptor.
* @property {String|Array.<String>} classes A CSS class or an array of classes to set. If the descriptor is used to
* create an {@link module:engine/view/attributeelement~AttributeElement attribute element} over text nodes, these classes will be set
* on that attribute element. If the descriptor is applied to an element, usually these classes will be set on that element, however,
* this depends on how the element converts the descriptor.
*
* @property {String} [id] Descriptor identifier. If not provided, defaults to converted marker's name.
* @property {String} [id] Descriptor identifier. If not provided, it defaults to the converted marker's name.
*
* @property {Number} [priority] Descriptor priority. If not provided, defaults to `10`. If descriptor is used to create
* {@link module:engine/view/attributeelement~AttributeElement}, it will be that element's
* {@link module:engine/view/attributeelement~AttributeElement#priority}. If descriptor is applied to an element,
* @property {Number} [priority] Descriptor priority. If not provided, it defaults to `10`. If the descriptor is used to create
* an {@link module:engine/view/attributeelement~AttributeElement attribute element}, it will be that element's
* {@link module:engine/view/attributeelement~AttributeElement#priority priority}. If the descriptor is applied to an element,
* the priority will be used to determine which descriptor is more important.
*
* @property {Object} [attributes] Attributes to set. If descriptor is used to create
* {@link module:engine/view/attributeelement~AttributeElement} over text nodes, those attributes will be set on that
* {@link module:engine/view/attributeelement~AttributeElement}. If descriptor is applied to an element, usually those
* attributes will be set on that element, however this depends on how the element converts the descriptor.
* @property {Object} [attributes] Attributes to set. If the descriptor is used to create
* an {@link module:engine/view/attributeelement~AttributeElement attribute element} over text nodes, these attributes will be set on that
* attribute element. If the descriptor is applied to an element, usually these attributes will be set on that element, however,
* this depends on how the element converts the descriptor.
*/

@@ -9,3 +9,3 @@ /**

* {@link module:engine/view/documentselection~DocumentSelection view selection} converters for
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}.
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast dispatcher}.
*

@@ -16,5 +16,5 @@ * @module engine/conversion/downcast-selection-converters

/**
* Function factory, creates a converter that converts non-collapsed {@link module:engine/model/selection~Selection model selection} to
* {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
* value from `consumable` object and maps model positions from selection to view positions.
* Function factory that creates a converter which converts a non-collapsed {@link module:engine/model/selection~Selection model selection}
* to a {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
* value from the `consumable` object and maps model positions from the selection to view positions.
*

@@ -49,5 +49,5 @@ * modelDispatcher.on( 'selection', convertRangeSelection() );

/**
* Function factory, creates a converter that converts collapsed {@link module:engine/model/selection~Selection model selection} to
* {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
* value from `consumable` object, maps model selection position to view position and breaks
* Function factory that creates a converter which converts a collapsed {@link module:engine/model/selection~Selection model selection} to
* a {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
* value from the `consumable` object, maps the model selection position to the view position and breaks
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} at the selection position.

@@ -57,3 +57,3 @@ *

*
* Example of view state before and after converting collapsed selection:
* An example of the view state before and after converting the collapsed selection:
*

@@ -63,5 +63,5 @@ * <p><strong>f^oo<strong>bar</p>

*
* By breaking attribute elements like `<strong>`, selection is in correct element. Then, when selection attribute is
* converted, the broken attributes might be merged again, or the position where the selection is may be wrapped
* in different, appropriate attribute elements.
* By breaking attribute elements like `<strong>`, the selection is in a correct element. Then, when the selection attribute is
* converted, broken attributes might be merged again, or the position where the selection is may be wrapped
* with different, appropriate attribute elements.
*

@@ -95,5 +95,5 @@ * See also {@link module:engine/conversion/downcast-selection-converters~clearAttributes} which does a clean-up

/**
* Function factory, creates a converter that clears artifacts after the previous
* Function factory that creates a converter which clears artifacts after the previous
* {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
* {@link module:engine/view/attributeelement~AttributeElement view attribute elements} and merge sibling attributes at all start and end
* {@link module:engine/view/attributeelement~AttributeElement view attribute elements} and merges sibling attributes at all start and end
* positions of all ranges.

@@ -115,3 +115,3 @@ *

* See {@link module:engine/conversion/downcast-selection-converters~convertCollapsedSelection}
* which do the opposite by breaking attributes in the selection position.
* which does the opposite by breaking attributes in the selection position.
*

@@ -118,0 +118,0 @@ * @returns {Function} Selection converter.

@@ -29,3 +29,3 @@ /**

*
* upcastElementToElement( { view: 'p', model: 'paragraph', priority: 'high' } );
* upcastElementToElement( { view: 'p', model: 'paragraph', converterPriority: 'high' } );
*

@@ -56,3 +56,3 @@ * upcastElementToElement( {

* instance or a function that takes a view element and returns a model element. The model element will be inserted in the model.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -69,3 +69,3 @@ */

return dispatcher => {
dispatcher.on( eventName, converter, { priority: config.priority || 'normal' } );
dispatcher.on( eventName, converter, { priority: config.converterPriority || 'normal' } );
};

@@ -84,3 +84,3 @@ }

*
* upcastElementToAttribute( { view: 'strong', model: 'bold', priority: 'high' } );
* upcastElementToAttribute( { view: 'strong', model: 'bold', converterPriority: 'high' } );
*

@@ -137,3 +137,3 @@ * upcastElementToAttribute( {

* If `String` is given, the model attribute value will be set to `true`.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -152,3 +152,3 @@ */

return dispatcher => {
dispatcher.on( eventName, converter, { priority: config.priority || 'normal' } );
dispatcher.on( eventName, converter, { priority: config.converterPriority || 'normal' } );
};

@@ -169,3 +169,3 @@ }

*
* upcastAttributeToAttribute( { view: { key: 'src' }, model: 'source', priority: 'normal' } );
* upcastAttributeToAttribute( { view: { key: 'src' }, model: 'source', converterPriority: 'normal' } );
*

@@ -219,3 +219,3 @@ * upcastAttributeToAttribute( {

* If `String` is given, the model attribute value will be same as view attribute value.
* @param {module:utils/priorities~PriorityString} [config.priority='low'] Converter priority.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
* @returns {Function} Conversion helper.

@@ -237,3 +237,3 @@ */

return dispatcher => {
dispatcher.on( 'element', converter, { priority: config.priority || 'low' } );
dispatcher.on( 'element', converter, { priority: config.converterPriority || 'low' } );
};

@@ -252,3 +252,3 @@ }

*
* upcastElementToMarker( { view: 'marker-search', model: 'search', priority: 'high' } );
* upcastElementToMarker( { view: 'marker-search', model: 'search', converterPriority: 'high' } );
*

@@ -276,3 +276,3 @@ * upcastElementToMarker( {

* a model marker name.
* @param {module:utils/priorities~PriorityString} [config.priority='normal'] Converter priority.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {Function} Conversion helper.

@@ -279,0 +279,0 @@ */

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

import Model from '../model/model';
import Batch from '../model/batch';
import ModelRange from '../model/range';

@@ -98,3 +97,2 @@ import ModelPosition from '../model/position';

const modelRoot = model.document.getRoot( options.rootName || 'main' );
const batch = new Batch( options.batchType || 'transparent' );

@@ -116,3 +114,3 @@ // Parse data string to model.

model.enqueueChange( batch, writer => {
model.change( writer => {
// Replace existing model in document by new one.

@@ -119,0 +117,0 @@ writer.remove( ModelRange.createIn( modelRoot ) );

@@ -75,3 +75,3 @@ /**

* @param {module:engine/model/delta/delta~Delta} delta A delta to add.
* @return {module:engine/model/delta/delta~Delta} An added delta.
* @returns {module:engine/model/delta/delta~Delta} An added delta.
*/

@@ -78,0 +78,0 @@ addDelta( delta ) {

@@ -114,3 +114,3 @@ /**

/**
* Buffers a given operation. An operation has to be buffered before it is executed.
* Buffers the given operation. An operation has to be buffered before it is executed.
*

@@ -175,3 +175,3 @@ * Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ`

this.bufferMarkerChange( marker.name, markerRange, markerRange );
this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
}

@@ -188,3 +188,3 @@

/**
* Buffers marker change.
* Buffers a marker change.
*

@@ -195,4 +195,5 @@ * @param {String} markerName The name of the marker that changed.

* @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.
* @param {Boolean} affectsData Flag indicating whether marker affects the editor data.
*/
bufferMarkerChange( markerName, oldRange, newRange ) {
bufferMarkerChange( markerName, oldRange, newRange, affectsData ) {
const buffered = this._changedMarkers.get( markerName );

@@ -203,6 +204,8 @@

oldRange,
newRange
newRange,
affectsData
} );
} else {
buffered.newRange = newRange;
buffered.affectsData = affectsData;

@@ -252,2 +255,24 @@ if ( buffered.oldRange == null && buffered.newRange == null ) {

/**
* Checks whether some of the buffered changes affect the editor data.
*
* Types of changes which affect the editor data:
*
* * model structure changes,
* * attribute changes,
* * changes of markers which were defined as `affectingData`.
*
* @returns {Boolean}
*/
hasDataChanges() {
for ( const [ , change ] of this._changedMarkers ) {
if ( change.affectsData ) {
return true;
}
}
// If markers do not affect the data, check whether there are some changes in elements.
return this._changesInElement.size > 0;
}
/**
* Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}

@@ -254,0 +279,0 @@ * call) and the new model tree state (actual one). It should be called after all buffered operations are executed.

@@ -26,6 +26,15 @@ /**

/**
* Document tree model describes all editable data in the editor. It may contain multiple
* {@link module:engine/model/document~Document#roots root elements}. For example, if the editor has multiple editable areas,
* each area will be represented by a separate root.
* Data model's document. It contains the model's structure, its selection and the history of changes.
*
* Read more about working with the model in
* {@glink framework/guides/architecture/editing-engine#model introduction to the the editing engine's architecture}.
*
* Usually, the document contains just one {@link module:engine/model/document~Document#roots root element}, so
* you can retrieve it by just calling {@link module:engine/model/document~Document#getRoot} without specifying its name:
*
* model.document.getRoot(); // -> returns the main root
*
* However, the document may contain multiple roots – e.g. when the editor has multiple editable areas
* (e.g. a title and a body of a message).
*
* @mixes module:utils/emittermixin~EmitterMixin

@@ -50,2 +59,3 @@ */

* operations are applied on a proper document version.
*
* If the {@link module:engine/model/operation/operation~Operation#baseVersion base version} does not match the document version,

@@ -70,3 +80,3 @@ * a {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.

/**
* The selection done on this document.
* The selection in this document.
*

@@ -151,4 +161,5 @@ * @readonly

// Wait for `_change` event from model, which signalizes that outermost change block has finished.
// When this happens, check if there were any changes done on document, and if so, call post fixers,
// When this happens, check if there were any changes done on document, and if so, call post-fixers,
// fire `change` event for features and conversion and then reset the differ.
// Fire `change:data` event when at least one operation or buffered marker changes the data.
this.listenTo( model, '_change', ( evt, writer ) => {

@@ -158,3 +169,7 @@ if ( !this.differ.isEmpty || hasSelectionChanged ) {

this.fire( 'change', writer.batch );
if ( this.differ.hasDataChanges() ) {
this.fire( 'change:data', writer.batch );
} else {
this.fire( 'change', writer.batch );
}

@@ -171,3 +186,3 @@ this.differ.reset();

// Whenever marker is updated, buffer that change.
this.differ.bufferMarkerChange( marker.name, oldRange, newRange );
this.differ.bufferMarkerChange( marker.name, oldRange, newRange, marker.affectsData );

@@ -177,3 +192,3 @@ if ( oldRange === null ) {

marker.on( 'change', ( evt, oldRange ) => {
this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange() );
this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange(), marker.affectsData );
} );

@@ -386,12 +401,11 @@ }

* model.document.on( 'change', () => {
* console.log( 'The Document has changed!' );
* console.log( 'The document has changed!' );
* } );
*
* If, however, you only want to be notified about structure changes, then check whether the
* {@link module:engine/model/differ~Differ differ} contains any changes:
* If, however, you only want to be notified about the data changes, then use the
* {@link module:engine/model/document~Document#event:change:data change:data} event,
* which is fired for document structure changes and marker changes (which affects the data).
*
* model.document.on( 'change', () => {
* if ( model.document.differ.getChanges().length > 0 ) {
* console.log( 'The Document has changed!' );
* }
* model.document.on( 'change:data', () => {
* console.log( 'The data has changed!' );
* } );

@@ -402,2 +416,22 @@ *

*/
/**
* It is a narrower version of the {@link #event:change} event. It is fired for changes which
* affect the editor data. This is:
*
* * document structure changes,
* * marker changes (which affects the data).
*
* If you want to be notified about the data changes, then listen to this event:
*
* model.document.on( 'change:data', () => {
* console.log( 'The data has changed!' );
* } );
*
* If you would like to listen to all document changes, then check out the
* {@link module:engine/model/document~Document#event:change change} event.
*
* @event change:data
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
*/
}

@@ -404,0 +438,0 @@

@@ -319,3 +319,3 @@ /**

// @param {String|module:engine/model/item~Item|Iterable.<module:engine/model/item~Item>}
// @return {Iterable.<module:engine/model/node~Node>}
// @returns {Iterable.<module:engine/model/node~Node>}
function normalize( nodes ) {

@@ -322,0 +322,0 @@ // Separate condition because string is iterable.

@@ -145,3 +145,3 @@ /**

* @readonly
* @return {Boolean}
* @returns {Boolean}
*/

@@ -148,0 +148,0 @@ get isGravityOverridden() {

@@ -322,3 +322,3 @@ /**

// @param {String|module:engine/model/item~Item|Iterable.<String|module:engine/model/item~Item>}
// @return {Iterable.<module:engine/model/node~Node>}
// @returns {Iterable.<module:engine/model/node~Node>}
function normalize( nodes ) {

@@ -325,0 +325,0 @@ // Separate condition because string is iterable.

@@ -91,5 +91,7 @@ /**

* @param {Boolean} [managedUsingOperations=false] Specifies whether the marker is managed using operations.
* @param {Boolean} [affectsData=false] Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
* @returns {module:engine/model/markercollection~Marker} `Marker` instance which was added or updated.
*/
_set( markerOrName, range, managedUsingOperations = false ) {
_set( markerOrName, range, managedUsingOperations = false, affectsData = false ) {
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;

@@ -112,2 +114,7 @@ const oldMarker = this._markers.get( markerName );

if ( typeof affectsData === 'boolean' && affectsData != oldMarker.affectsData ) {
oldMarker._affectsData = affectsData;
hasChanged = true;
}
if ( hasChanged ) {

@@ -121,3 +128,3 @@ this.fire( 'update:' + markerName, oldMarker, oldRange, range );

const liveRange = LiveRange.createFromRange( range );
const marker = new Marker( markerName, liveRange, managedUsingOperations );
const marker = new Marker( markerName, liveRange, managedUsingOperations, affectsData );

@@ -319,4 +326,6 @@ this._markers.set( markerName, marker );

* @param {Boolean} managedUsingOperations Specifies whether the marker is managed using operations.
* @param {Boolean} affectsData Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
*/
constructor( name, liveRange, managedUsingOperations ) {
constructor( name, liveRange, managedUsingOperations, affectsData ) {
/**

@@ -331,5 +340,13 @@ * Marker's name.

/**
* Range marked by the marker.
*
* @protected
* @member {module:engine/model/liverange~LiveRange}
*/
this._liveRange = this._attachLiveRange( liveRange );
/**
* Flag indicates if the marker is managed using operations or not.
*
* @protected
* @private
* @member {Boolean}

@@ -340,12 +357,13 @@ */

/**
* Range marked by the marker.
* Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
*
* @private
* @member {module:engine/model/liverange~LiveRange} #_liveRange
* @member {Boolean}
*/
this._liveRange = this._attachLiveRange( liveRange );
this._affectsData = affectsData;
}
/**
* Returns value of flag indicates if the marker is managed using operations or not.
* A value indicating if the marker is managed using operations.
* See {@link ~Marker marker class description} to learn more about marker types.

@@ -365,2 +383,15 @@ * See {@link module:engine/model/writer~Writer#addMarker}.

/**
* A value indicating if the marker changes the data.
*
* @returns {Boolean}
*/
get affectsData() {
if ( !this._liveRange ) {
throw new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.' );
}
return this._affectsData;
}
/**
* Returns current marker start position.

@@ -392,3 +423,3 @@ *

/**
* Returns a range that represents current state of marker.
* Returns a range that represents the current state of the marker.
*

@@ -413,7 +444,7 @@ * Keep in mind that returned value is a {@link module:engine/model/range~Range Range}, not a

/**
* Binds new live range to marker and detach the old one if is attached.
* Binds new live range to the marker and detach the old one if is attached.
*
* @protected
* @param {module:engine/model/liverange~LiveRange} liveRange Live range to attach
* @return {module:engine/model/liverange~LiveRange} Attached live range.
* @returns {module:engine/model/liverange~LiveRange} Attached live range.
*/

@@ -420,0 +451,0 @@ _attachLiveRange( liveRange ) {

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

import getSelectedContent from './utils/getselectedcontent';
import { injectSelectionPostFixer } from './utils/selection-post-fixer';

@@ -115,2 +116,4 @@ /**

} );
injectSelectionPostFixer( this );
}

@@ -117,0 +120,0 @@

@@ -157,5 +157,10 @@ /**

*
* @error nodelist-offset-out-of-bounds
* @error model-nodelist-offset-out-of-bounds
* @param {Number} offset
* @param {module:engine/model/nodelist~NodeList} nodeList Stringified node list.
*/
throw new CKEditorError( 'model-nodelist-offset-out-of-bounds: Given offset cannot be found in the node list.' );
throw new CKEditorError( 'model-nodelist-offset-out-of-bounds: Given offset cannot be found in the node list.', {
offset,
nodeList: this
} );
}

@@ -162,0 +167,0 @@

@@ -23,5 +23,7 @@ /**

* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* @param {Boolean} affectsData Specifies whether the marker operation affects the data produced by the data pipeline
* (is persisted in the editor's data).
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( name, oldRange, newRange, markers, baseVersion ) {
constructor( name, oldRange, newRange, markers, baseVersion, affectsData ) {
super( baseVersion );

@@ -54,2 +56,11 @@

/**
* Specifies whether the marker operation affects the data produced by the data pipeline
* (is persisted in the editor's data).
*
* @readonly
* @member {Boolean}
*/
this.affectsData = affectsData;
/**
* Marker collection on which change should be executed.

@@ -76,3 +87,3 @@ *

clone() {
return new MarkerOperation( this.name, this.oldRange, this.newRange, this._markers, this.baseVersion );
return new MarkerOperation( this.name, this.oldRange, this.newRange, this._markers, this.baseVersion, this.affectsData );
}

@@ -86,3 +97,3 @@

getReversed() {
return new MarkerOperation( this.name, this.newRange, this.oldRange, this._markers, this.baseVersion + 1 );
return new MarkerOperation( this.name, this.newRange, this.oldRange, this._markers, this.baseVersion + 1, this.affectsData );
}

@@ -96,3 +107,3 @@

this._markers[ type ]( this.name, this.newRange, true );
this._markers[ type ]( this.name, this.newRange, true, this.affectsData );
}

@@ -119,3 +130,3 @@

/**
* Creates `MarkerOperation` object from deserilized object, i.e. from parsed JSON string.
* Creates `MarkerOperation` object from deserialized object, i.e. from parsed JSON string.
*

@@ -132,5 +143,6 @@ * @param {Object} json Deserialized JSON object.

document.model.markers,
json.baseVersion
json.baseVersion,
json.affectsData
);
}
}

@@ -40,3 +40,3 @@ /**

/**
* Creates concrete `Operation` object from deserilized object, i.e. from parsed JSON string.
* Creates concrete `Operation` object from deserialized object, i.e. from parsed JSON string.
*

@@ -43,0 +43,0 @@ * @param {Object} json Deserialized JSON object.

@@ -336,3 +336,4 @@ /**

* Returns `true` if the given item is defined to be
* a limit element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isLimit` property.
* a limit element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isLimit` or `isObject` property
* (all objects are also limits).
*

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

* schema.isLimit( editor.model.document.getRoot() ); // -> true
* schema.isLimit( 'image' ); // -> true
*

@@ -349,3 +351,7 @@ * @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item

return !!( def && def.isLimit );
if ( !def ) {
return false;
}
return !!( def.isLimit || def.isObject );
}

@@ -381,4 +387,9 @@

*
* Note: When verifying whether the given node can be a child of the given context, the
* schema also verifies the entire context &mdash; from its root to its last element. Therefore, it is possible
* for `checkChild()` to return `false` even though the context's last element can contain the checked child.
* It happens if one of the context's elements does not allow its child.
*
* @fires checkChild
* @param {module:engine/model/schema~SchemaContextDefinition} context Context in which the child will be checked.
* @param {module:engine/model/schema~SchemaContextDefinition} context The context in which the child will be checked.
* @param {module:engine/model/node~Node|String} def The child to check.

@@ -407,3 +418,3 @@ */

* @fires checkAttribute
* @param {module:engine/model/schema~SchemaContextDefinition} context Context in which the attribute will be checked.
* @param {module:engine/model/schema~SchemaContextDefinition} context The context in which the attribute will be checked.
* @param {String} attributeName

@@ -424,3 +435,3 @@ */

*
* In other words – whether `elementToMerge`'s children {@link #checkChild are allowed} in the `positionOrBaseElement`.
* In other words &mdash; whether `elementToMerge`'s children {@link #checkChild are allowed} in the `positionOrBaseElement`.
*

@@ -430,8 +441,8 @@ * This check ensures that elements merged with {@link module:engine/model/writer~Writer#merge `Writer#merge()`}

*
* Instead of elements, you can pass the instance of {@link module:engine/model/position~Position} class as the `positionOrBaseElement`.
* It means that the elements before and after the position will be checked whether they can be merged.
* Instead of elements, you can pass the instance of the {@link module:engine/model/position~Position} class as the
* `positionOrBaseElement`. It means that the elements before and after the position will be checked whether they can be merged.
*
* @param {module:engine/model/position~Position|module:engine/model/element~Element} positionOrBaseElement The position or base
* element to which the `elementToMerge` will be merged.
* @param {module:engine/model/element~Element} elementToMerge The element to merge. Required if `positionOrBaseElement` is a element.
* @param {module:engine/model/element~Element} elementToMerge The element to merge. Required if `positionOrBaseElement` is an element.
* @returns {Boolean}

@@ -752,4 +763,4 @@ */

// Do not split limit elements and objects.
if ( this.isLimit( parent ) || this.isObject( parent ) ) {
// Do not split limit elements.
if ( this.isLimit( parent ) ) {
return null;

@@ -973,10 +984,10 @@ }

*
* * `allowIn` – a string or an array of strings. Defines in which other items this item will be allowed.
* * `allowAttributes` – a string or an array of strings. Defines allowed attributes of the given item.
* * `allowContentOf` – a string or an array of strings. Inherit "allowed children" from other items.
* * `allowWhere` – a string or an array of strings. Inherit "allowed in" from other items.
* * `allowAttributesOf` – a string or an array of strings. Inherit attributes from other items.
* * `inheritTypesFrom` – a string or an array of strings. Inherit `is*` properties of other items.
* * `inheritAllFrom` – a string. A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
* * additionall, you can define the following `is*` properties: `isBlock`, `isLimit`, `isObject`. Read about them below.
* * `allowIn` &ndash; A string or an array of strings. Defines in which other items this item will be allowed.
* * `allowAttributes` &ndash; A string or an array of strings. Defines allowed attributes of the given item.
* * `allowContentOf` &ndash; A string or an array of strings. Inherits "allowed children" from other items.
* * `allowWhere` &ndash; A string or an array of strings. Inherits "allowed in" from other items.
* * `allowAttributesOf` &ndash; A string or an array of strings. Inherits attributes from other items.
* * `inheritTypesFrom` &ndash; A string or an array of strings. Inherits `is*` properties of other items.
* * `inheritAllFrom` &ndash; A string. A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
* * Additionally, you can define the following `is*` properties: `isBlock`, `isLimit`, `isObject`. Read about them below.
*

@@ -988,11 +999,13 @@ * # The is* properties

*
* * `isBlock` – whether this item is paragraph-like. Generally speaking, a content is usually made out of blocks
* * `isBlock` &ndash; Whether this item is paragraph-like. Generally speaking, content is usually made out of blocks
* like paragraphs, list items, images, headings, etc. All these elements are marked as blocks. A block
* should not allow another block inside. Note: there's also the `$block` generic item which has `isBlock` set to `true`.
* should not allow another block inside. Note: There is also the `$block` generic item which has `isBlock` set to `true`.
* Most block type items will inherit from `$block` (through `inheritAllFrom`).
* * `isLimit` – can be understood as whether this element should not be split by <kbd>Enter</kbd>.
* Examples of limit elements – `$root`, table cell, image caption, etc. In other words, all actions which happen inside
* a limit element are limitted to its content.
* * `isObject` – whether item is "self-contained" and should be treated as a whole. Examples of object elements –
* `image`, `table`, `video`, etc.
* * `isLimit` &ndash; It can be understood as whether this element should not be split by <kbd>Enter</kbd>.
* Examples of limit elements: `$root`, table cell, image caption, etc. In other words, all actions that happen inside
* a limit element are limited to its content. **Note:** All objects (`isObject`) are treated as limit elements, too.
* * `isObject` &ndash; Whether an item is "self-contained" and should be treated as a whole. Examples of object elements:
* `image`, `table`, `video`, etc. **Note:** An object is also a limit, so
* {@link module:engine/model/schema~Schema#isLimit `isLimit()`}
* returns `true` for object elements automatically.
*

@@ -1015,3 +1028,3 @@ * # Generic items

*
* They reflect a typical editor content which is contained within one root, consists of several blocks
* They reflect typical editor content that is contained within one root, consists of several blocks
* (paragraphs, lists items, headings, images) which, in turn, may contain text inside.

@@ -1039,3 +1052,3 @@ *

* Make `image` a block object, which is allowed everywhere where `$block` is.
* Also, allow `src` and `alt` attributes on it:
* Also, allow `src` and `alt` attributes in it:
*

@@ -1050,3 +1063,3 @@ * schema.register( 'image', {

* Make `caption` allowed in `image` and make it allow all the content of `$block`s (usually, `$text`).
* Also, mark it as a limit element so it can't be split:
* Also, mark it as a limit element so it cannot be split:
*

@@ -1063,3 +1076,3 @@ * schema.register( 'caption', {

* inheritAllFrom: '$block',
* allowAttributes: [ 'type', 'indent' ]
* allowAttributes: [ 'listType', 'listIndent' ]
* } );

@@ -1074,3 +1087,3 @@ *

* inheritTypesFrom: '$block',
* allowAttributes: [ 'type', 'indent' ]
* allowAttributes: [ 'listType', 'listIndent' ]
* } );

@@ -1083,5 +1096,5 @@ *

* generic items as much as possible.
* * Keep your model clean – limit it to the actual data and store information in an normalized way.
* * Remember about definining the `is*` properties. They don't affect the allowed structures, but they can
* affect how editor features treat your elements.
* * Keep your model clean. Limit it to the actual data and store information in a normalized way.
* * Remember about definining the `is*` properties. They do not affect the allowed structures, but they can
* affect how the editor features treat your elements.
*

@@ -1094,4 +1107,4 @@ * @typedef {Object} module:engine/model/schema~SchemaItemDefinition

* compilation by the {@link module:engine/model/schema~Schema schema}.
* Rules feed to the schema by {@link module:engine/model/schema~Schema#register}
* and {@link module:engine/model/schema~Schema#extend} are defined in the
* Rules fed to the schema by {@link module:engine/model/schema~Schema#register}
* and {@link module:engine/model/schema~Schema#extend} methods are defined in the
* {@link module:engine/model/schema~SchemaItemDefinition} format.

@@ -1103,6 +1116,6 @@ * Later on, they are compiled to `SchemaCompiledItemDefition` so when you use e.g.

*
* * `name` property,
* * `is*` properties,
* * `allowIn` array,
* * `allowAttributes` array.
* * The `name` property,
* * The `is*` properties,
* * The `allowIn` array,
* * The `allowAttributes` array.
*

@@ -1113,5 +1126,5 @@ * @typedef {Object} module:engine/model/schema~SchemaCompiledItemDefinition

/**
* A schema context – a list of ancestors of a given position in the document.
* A schema context &mdash; a list of ancestors of a given position in the document.
*
* Considering such a position:
* Considering such position:
*

@@ -1164,3 +1177,3 @@ * <$root>

/**
* Number of items.
* The number of items.
*

@@ -1194,3 +1207,3 @@ * @type {Number}

/**
* Returns new SchemaContext instance with additional item
* Returns a new schema context instance with an additional item.
*

@@ -1214,5 +1227,5 @@ * Item can be added as:

*
* @param {String|module:engine/model/node~Node|Array<String|module:engine/model/node~Node>} item Item that will be added
* to current context.
* @returns {module:engine/model/schema~SchemaContext} New SchemaContext instance with additional item.
* @param {String|module:engine/model/node~Node|Array<String|module:engine/model/node~Node>} item An item that will be added
* to the current context.
* @returns {module:engine/model/schema~SchemaContext} A new schema context instance with an additional item.
*/

@@ -1219,0 +1232,0 @@ push( item ) {

@@ -188,3 +188,3 @@ /**

for ( const value of rangeToCheck.getWalker() ) {
if ( schema.isObject( value.item ) || schema.isLimit( value.item ) ) {
if ( schema.isLimit( value.item ) ) {
return false;

@@ -191,0 +191,0 @@ }

@@ -148,3 +148,3 @@ /**

// TMP this will become a postfixer.
// TMP this will become a post-fixer.
this.schema.removeDisallowedAttributes( this._filterAttributesOf, this.writer );

@@ -151,0 +151,0 @@ this._filterAttributesOf = [];

@@ -55,3 +55,3 @@ /**

*
* Note that the should never be stored and used outside of the `change()` or
* Note that the writer should never be stored and used outside of the `change()` and
* `enqueueChange()` blocks.

@@ -66,4 +66,4 @@ *

*
* **Note:** It is not recommended to use it directly. Use {@link module:engine/model/model~Model#change} or
* {@link module:engine/model/model~Model#enqueueChange} instead.
* **Note:** It is not recommended to use it directly. Use {@link module:engine/model/model~Model#change `Model#change()`} or
* {@link module:engine/model/model~Model#enqueueChange `Model#enqueueChange()`} instead.
*

@@ -76,2 +76,4 @@ * @protected

/**
* Instance of the model on which this writer operates.
*
* @readonly

@@ -83,2 +85,4 @@ * @type {module:engine/model/model~Model}

/**
* The batch to which this writer will add changes.
*
* @readonly

@@ -94,3 +98,3 @@ * @type {module:engine/model/batch~Batch}

* writer.createText( 'foo' );
* writer.createText( 'foo', { 'bold': true } );
* writer.createText( 'foo', { bold: true } );
*

@@ -109,3 +113,3 @@ * @param {String} data Text data.

* writer.createElement( 'paragraph' );
* writer.createElement( 'paragraph', { 'alignment': 'center' } );
* writer.createElement( 'paragraph', { alignment: 'center' } );
*

@@ -137,3 +141,3 @@ * @param {String} name Name of the element.

*
* const text = writer.createText( 'foo' );
* const text = writer.createText( 'foo' );
* writer.insert( text, paragraph, 5 );

@@ -143,3 +147,3 @@ *

*
* const text = writer.createText( 'foo' );
* const text = writer.createText( 'foo' );
* writer.insert( text, paragraph, 'end' );

@@ -149,14 +153,14 @@ *

*
* const paragraph = writer.createElement( 'paragraph' );
* const paragraph = writer.createElement( 'paragraph' );
* writer.insert( paragraph, anotherParagraph, 'after' );
*
* These parameters works the same way as {@link module:engine/model/position~Position.createAt}.
* These parameters works the same way as {@link module:engine/model/position~Position.createAt `Position.createAt()`}.
*
* Note that if the item already has parent it will be removed from the previous parent.
*
* Note that you cannot re-insert a node from a document to a different document or document fragment. In this case,
* Note that you cannot re-insert a node from a document to a different document or a document fragment. In this case,
* `model-writer-insert-forbidden-move` is thrown.
*
* If you want to move {@link module:engine/model/range~Range range} instead of an
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move move}.
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.
*

@@ -226,3 +230,3 @@ * @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment} item Item or document

* writer.insertText( 'foo', position );
* writer.insertText( 'foo', { 'bold': true }, position );
* writer.insertText( 'foo', { bold: true }, position );
*

@@ -232,7 +236,10 @@ * Instead of using position you can use parent and offset or define that text should be inserted at the end

*
* writer.insertText( 'foo', paragraph, 5 ); // inserts in paragraph, at offset 5
* writer.insertText( 'foo', paragraph, 'end' ); // inserts at the end of the paragraph
* writer.insertText( 'foo', image, 'after' ); // inserts after image
* // Inserts 'foo' in paragraph, at offset 5:
* writer.insertText( 'foo', paragraph, 5 );
* // Inserts 'foo' at the end of a paragraph:
* writer.insertText( 'foo', paragraph, 'end' );
* // Inserts 'foo' after an image:
* writer.insertText( 'foo', image, 'after' );
*
* These parameters works the same way as {@link module:engine/model/position~Position.createAt}.
* These parameters work in the same way as {@link module:engine/model/position~Position.createAt `Position.createAt()`}.
*

@@ -257,3 +264,3 @@ * @param {String} data Text data.

* writer.insertElement( 'paragraph', position );
* writer.insertElement( 'paragraph', { 'alignment': 'center' }, position );
* writer.insertElement( 'paragraph', { alignment: 'center' }, position );
*

@@ -263,7 +270,10 @@ * Instead of using position you can use parent and offset or define that text should be inserted at the end

*
* writer.insertElement( 'paragraph', paragraph, 5 ); // inserts in paragraph, at offset 5
* writer.insertElement( 'paragraph', blockquote, 'end' ); // insets at the end of the blockquote
* writer.insertElement( 'paragraph', image, 'after' ); // inserts after image
* // Inserts paragraph in the root at offset 5:
* writer.insertElement( 'paragraph', root, 5 );
* // Inserts paragraph at the end of a blockquote:
* writer.insertElement( 'paragraph', blockquote, 'end' );
* // Inserts after an image:
* writer.insertElement( 'paragraph', image, 'after' );
*
* These parameters works the same way as {@link module:engine/model/position~Position.createAt}.
* These parameters works the same way as {@link module:engine/model/position~Position.createAt `Position.createAt()`}.
*

@@ -293,3 +303,3 @@ * @param {String} name Name of the element.

* If you want to move {@link module:engine/model/range~Range range} instead of an
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move move}.
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.
*

@@ -308,3 +318,3 @@ * @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment}

* writer.appendText( 'foo', paragraph );
* writer.appendText( 'foo', { 'bold': true }, paragraph );
* writer.appendText( 'foo', { bold: true }, paragraph );
*

@@ -327,3 +337,3 @@ * @param {String} text Text data.

* writer.appendElement( 'paragraph', root );
* writer.appendElement( 'paragraph', { 'alignment': 'center' }, root );
* writer.appendElement( 'paragraph', { alignment: 'center' }, root );
*

@@ -366,4 +376,4 @@ * @param {String} name Name of the element.

* writer.setAttributes( {
* 'bold': true,
* 'italic': true
* bold: true,
* italic: true
* }, range );

@@ -431,7 +441,10 @@ *

*
* writer.move( sourceRange, paragraph, 5 ); // moves all items in the range to the paragraph at offset 5
* writer.move( sourceRange, blockquote, 'end' ); // moves all items in the range at the end of the blockquote
* writer.move( sourceRange, image, 'after' ); // moves all items in the range after the image
* // Moves all items in the range to the paragraph at offset 5:
* writer.move( sourceRange, paragraph, 5 );
* // Moves all items in the range to the end of a blockquote:
* writer.move( sourceRange, blockquote, 'end' );
* // Moves all items in the range to a position after an image:
* writer.move( sourceRange, image, 'after' );
*
* These parameters works the same way as {@link module:engine/model/position~Position.createAt}.
* These parameters works the same way as {@link module:engine/model/position~Position.createAt `Position.createAt()`}.
*

@@ -575,3 +588,3 @@ * Note that items can be moved only within the same tree. It means that you can move items within the same root

/**
* Renames given element.
* Renames the given element.
*

@@ -606,6 +619,6 @@ * @param {module:engine/model/element~Element} element The element to rename.

/**
* Splits elements start from the given position and goes to the top of the model tree as long as given
* `limitElement` won't be reached. When limitElement is not defined then only a parent of given position will be split.
* Splits elements starting from the given position and going to the top of the model tree as long as given
* `limitElement` is reached. When `limitElement` is not defined then only the parent of the given position will be split.
*
* The element needs to have a parent. It cannot be a root element nor document fragment.
* The element needs to have a parent. It cannot be a root element nor a document fragment.
* The `writer-split-element-no-parent` error will be thrown if you try to split an element with no parent.

@@ -694,5 +707,6 @@ *

/**
* Wraps given range with given element or with a new element with specified name, if string has been passed.
* Wraps the given range with the given element or with a new element (if a string was passed).
*
* **Note:** range to wrap should be a "flat range" (see {@link module:engine/model/range~Range#isFlat}). If not, error will be thrown.
* **Note:** range to wrap should be a "flat range" (see {@link module:engine/model/range~Range#isFlat `Range#isFlat`}).
* If not, an error will be thrown.
*

@@ -804,10 +818,20 @@ * @param {module:engine/model/range~Range} range Range to wrap.

*
* The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be
* `true` when the marker change changes the data returned by the
* {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.
* When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.
* When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.
*
* Create marker directly base on marker's name:
*
* addMarker( markerName, { range, usingOperation: false } );
* addMarker( markerName, { range, usingOperation: false } );
*
* Create marker using operation:
*
* addMarker( markerName, { range, usingOperation: true } );
* addMarker( markerName, { range, usingOperation: true } );
*
* Create marker that affects the editor data:
*
* addMarker( markerName, { range, usingOperation: false, affectsData: true } );
*
* Note: For efficiency reasons, it's best to create and keep as little markers as possible.

@@ -818,5 +842,6 @@ *

* @param {Object} options
* @param {Boolean} options.usingOperation Flag indicated whether the marker should be added by MarkerOperation.
* @param {Boolean} options.usingOperation Flag indicating that the marker should be added by MarkerOperation.
* See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
* @param {module:engine/model/range~Range} options.range Marker range.
* @param {Boolean} [options.affectsData=false] Flag indicating that the marker changes the editor data.
* @returns {module:engine/model/markercollection~Marker} Marker that was set.

@@ -829,3 +854,3 @@ */

/**
* The options.usingOperations parameter is required when adding a new marker.
* The `options.usingOperations` parameter is required when adding a new marker.
*

@@ -841,2 +866,3 @@ * @error writer-addMarker-no-usingOperations

const range = options.range;
const affectsData = options.affectsData === undefined ? false : options.affectsData;

@@ -862,6 +888,6 @@ if ( this.model.markers.has( name ) ) {

if ( !usingOperation ) {
return this.model.markers._set( name, range, usingOperation );
return this.model.markers._set( name, range, usingOperation, affectsData );
}
applyMarkerOperation( this, name, null, range );
applyMarkerOperation( this, name, null, range, affectsData );

@@ -882,17 +908,26 @@ return this.model.markers.get( name );

* markers managed by operations and not-managed by operations. It is possible to change this option for an existing marker.
* This is useful when a marker have been created earlier and then later, it needs to be added to the document history.
*
* The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be
* `true` when the marker change changes the data returned by
* the {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.
* When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.
* When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.
*
* Update marker directly base on marker's name:
*
* updateMarker( markerName, { range } );
* updateMarker( markerName, { range } );
*
* Update marker using operation:
*
* updateMarker( marker, { range, usingOperation: true } );
* updateMarker( markerName, { range, usingOperation: true } );
* updateMarker( marker, { range, usingOperation: true } );
* updateMarker( markerName, { range, usingOperation: true } );
*
* Change marker's option (start using operations to manage it):
*
* updateMarker( marker, { usingOperation: true } );
* updateMarker( marker, { usingOperation: true } );
*
* Change marker's option (inform the engine, that the marker does not affect the data anymore):
*
* updateMarker( markerName, { affectsData: false } );
*
* @see module:engine/model/markercollection~Marker

@@ -904,8 +939,8 @@ * @param {String} markerOrName Name of a marker to update, or a marker instance.

* See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
* @param {Boolean} [options.affectsData] Flag indicating that the marker changes the editor data.
*/
updateMarker( markerOrName, options ) {
updateMarker( markerOrName, options = {} ) {
this._assertWriterUsedCorrectly();
const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
const currentMarker = this.model.markers.get( markerName );

@@ -922,14 +957,22 @@

const newRange = options && options.range;
const hasUsingOperationDefined = !!options && typeof options.usingOperation == 'boolean';
const hasUsingOperationDefined = typeof options.usingOperation == 'boolean';
const affectsDataDefined = typeof options.affectsData == 'boolean';
if ( !hasUsingOperationDefined && !newRange ) {
// Use previously defined marker's affectsData if the property is not provided.
const affectsData = affectsDataDefined ? options.affectsData : currentMarker.affectsData;
if ( !hasUsingOperationDefined && !options.range && !affectsDataDefined ) {
/**
* One of options is required - provide range or usingOperations.
* One of the options is required - provide range, usingOperations or affectsData.
*
* @error writer-updateMarker-wrong-options
*/
throw new CKEditorError( 'writer-updateMarker-wrong-options: One of options is required - provide range or usingOperations.' );
throw new CKEditorError(
'writer-updateMarker-wrong-options: One of the options is required - provide range, usingOperations or affectsData.'
);
}
const currentRange = currentMarker.getRange();
const updatedRange = options.range ? options.range : currentRange;
if ( hasUsingOperationDefined && options.usingOperation !== currentMarker.managedUsingOperations ) {

@@ -939,13 +982,11 @@ // The marker type is changed so it's necessary to create proper operations.

// If marker changes to a managed one treat this as synchronizing existing marker.
// If marker changes to a managed one treat this as synchronizing existing marker.
// Create `MarkerOperation` with `oldRange` set to `null`, so reverse operation will remove the marker.
applyMarkerOperation( this, markerName, null, newRange ? newRange : currentMarker.getRange() );
applyMarkerOperation( this, markerName, null, updatedRange, affectsData );
} else {
// If marker changes to a marker that do not use operations then we need to create additional operation
// that removes that marker first.
const currentRange = currentMarker.getRange();
applyMarkerOperation( this, markerName, currentRange, null );
applyMarkerOperation( this, markerName, currentRange, null, affectsData );
// Although not managed the marker itself should stay in model and its range should be preserver or changed to passed range.
this.model.markers._set( markerName, newRange ? newRange : currentRange );
this.model.markers._set( markerName, updatedRange, undefined, affectsData );
}

@@ -958,5 +999,5 @@

if ( currentMarker.managedUsingOperations ) {
applyMarkerOperation( this, markerName, currentMarker.getRange(), newRange );
applyMarkerOperation( this, markerName, currentRange, updatedRange, affectsData );
} else {
this.model.markers._set( markerName, newRange );
this.model.markers._set( markerName, updatedRange, undefined, affectsData );
}

@@ -996,3 +1037,3 @@ }

applyMarkerOperation( this, name, oldRange, null );
applyMarkerOperation( this, name, oldRange, null, marker.affectsData );
}

@@ -1011,3 +1052,3 @@

* // Sets selection to given ranges.
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
* writer.setSelection( range );

@@ -1019,11 +1060,11 @@ *

*
* // Sets selection to the given document selection.
* // Sets selection to the given document selection.
* const documentSelection = new DocumentSelection( doc );
* writer.setSelection( documentSelection );
*
* // Sets collapsed selection at the given position.
* // Sets collapsed selection at the given position.
* const position = new Position( root, path );
* writer.setSelection( position );
*
* // Sets collapsed selection at the position of the given node and an offset.
* // Sets collapsed selection at the position of the given node and an offset.
* writer.setSelection( paragraph, offset );

@@ -1040,3 +1081,3 @@ *

*
* // Removes all selection's ranges.
* // Removes all selection's ranges.
* writer.setSelection( null );

@@ -1046,3 +1087,3 @@ *

*
* // Sets selection as backward.
* // Sets selection as backward.
* writer.setSelection( range, { backward: true } );

@@ -1068,3 +1109,4 @@ *

*
* The location can be specified in the same form as {@link module:engine/model/position~Position.createAt} parameters.
* The location can be specified in the same form as
* {@link module:engine/model/position~Position.createAt `Position.createAt()`} parameters.
*

@@ -1097,3 +1139,3 @@ * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition

* @param {String|Object|Iterable.<*>} keyOrObjectOrIterable Key of the attribute to set
* or object / iterable of key - value attribute pairs.
* or object / iterable of key => value attribute pairs.
* @param {*} [value] Attribute value.

@@ -1116,9 +1158,9 @@ */

*
* Using key
* Remove one attribute:
*
* writer.removeSelectionAttribute( 'italic' );
* writer.removeSelectionAttribute( 'italic' );
*
* Using iterable of keys
* Remove multiple attributes:
*
* writer.removeSelectionAttribute( [ 'italic', 'bold' ] );
* writer.removeSelectionAttribute( [ 'italic', 'bold' ] );
*

@@ -1352,3 +1394,4 @@ * @param {String|Iterable.<String>} keyOrIterableOfKeys Key of the attribute to remove or an iterable of attribute keys to remove.

// @param {module:engine/model/range~Range} newRange Marker range after the change.
function applyMarkerOperation( writer, name, oldRange, newRange ) {
// @param {Boolean} affectsData
function applyMarkerOperation( writer, name, oldRange, newRange, affectsData ) {
const model = writer.model;

@@ -1358,3 +1401,3 @@ const doc = model.document;

const operation = new MarkerOperation( name, oldRange, newRange, model.markers, doc.version );
const operation = new MarkerOperation( name, oldRange, newRange, model.markers, doc.version, affectsData );

@@ -1361,0 +1404,0 @@ writer.batch.addDelta( delta );

@@ -223,5 +223,5 @@ /**

// @param {module:engine/view/element~Element} element
// @return {Number}
// @returns {Number}
function nonUiChildrenCount( element ) {
return Array.from( element.getChildren() ).filter( element => !element.is( 'uiElement' ) ).length;
}

@@ -83,3 +83,11 @@ /**

function getFillerOffset() {
for ( const child of this.getChildren() ) {
const children = [ ...this.getChildren() ];
const lastChild = children[ this.childCount - 1 ];
// Block filler is required after a `<br>` if it's the last element in its container. See #1422.
if ( lastChild && lastChild.is( 'element', 'br' ) ) {
return this.childCount;
}
for ( const child of children ) {
// If there's any non-UI element – don't render the bogus.

@@ -86,0 +94,0 @@ if ( !child.is( 'uiElement' ) ) {

@@ -209,3 +209,3 @@ /**

// @param {String|module:engine/view/item~Item|Iterable.<String|module:engine/view/item~Item>}
// @return {Iterable.<module:engine/view/node~Node>}
// @returns {Iterable.<module:engine/view/node~Node>}
function normalize( nodes ) {

@@ -212,0 +212,0 @@ // Separate condition because string is iterable.

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

import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
import isElement from '@ckeditor/ckeditor5-utils/src/lib/lodash/isElement';

@@ -816,3 +817,3 @@ /**

* @param {Node} domNode
* @return {module:engine/view/uielement~UIElement|null}
* @returns {module:engine/view/uielement~UIElement|null}
*/

@@ -953,6 +954,7 @@ getParentUIElement( domNode ) {

* Following changes are done:
*
* * multiple whitespaces are replaced to a single space,
* * space at the beginning of the text node is removed, if it is a first text node in it's container
* element or if previous text node ends by space character,
* * space at the end of the text node is removed, if it is a last text node in it's container.
* * space at the beginning of a text node is removed if it is the first text node in its container
* element or if the previous text node ends with a space character,
* * space at the end of the text node is removed, if it is the last text node in its container.
*

@@ -976,13 +978,16 @@ * @param {Node} node DOM text node to process.

const prevNode = this._getTouchingDomTextNode( node, false );
const nextNode = this._getTouchingDomTextNode( node, true );
const prevNode = this._getTouchingInlineDomNode( node, false );
const nextNode = this._getTouchingInlineDomNode( node, true );
// If previous dom text node does not exist or it ends by whitespace character, remove space character from the beginning
const shouldLeftTrim = this._checkShouldLeftTrimDomText( prevNode );
const shouldRightTrim = this._checkShouldRightTrimDomText( node, nextNode );
// If the previous dom text node does not exist or it ends by whitespace character, remove space character from the beginning
// of this text node. Such space character is treated as a whitespace.
if ( !prevNode || /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) ) ) {
if ( shouldLeftTrim ) {
data = data.replace( /^ /, '' );
}
// If next text node does not exist remove space character from the end of this text node.
if ( !nextNode && !startsWithFiller( node ) ) {
// If the next text node does not exist remove space character from the end of this text node.
if ( shouldRightTrim ) {
data = data.replace( / $/, '' );

@@ -1008,3 +1013,3 @@ }

// We do that replacement only if this is the first node or the previous node ends on whitespace character.
if ( !prevNode || /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) ) ) {
if ( shouldLeftTrim ) {
data = data.replace( /^\u00A0/, ' ' );

@@ -1016,4 +1021,4 @@ }

// `x_ _ _` -> `x_ `. Find &nbsp; at the end of string (can be followed only by spaces).
// We do that replacement only if this is the last node or the next node starts by &nbsp;.
if ( !nextNode || nextNode.data.charAt( 0 ) == '\u00A0' ) {
// We do that replacement only if this is the last node or the next node starts with &nbsp; or is a <br>.
if ( isText( nextNode ) ? nextNode.data.charAt( 0 ) == '\u00A0' : true ) {
data = data.replace( /\u00A0( *)$/, ' $1' );

@@ -1028,2 +1033,35 @@ }

/**
* Helper function which checks if a DOM text node, preceded by the given `prevNode` should
* be trimmed from the left side.
*
* @param {Node} prevNode
*/
_checkShouldLeftTrimDomText( prevNode ) {
if ( !prevNode ) {
return true;
}
if ( isElement( prevNode ) ) {
return true;
}
return /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) );
}
/**
* Helper function which checks if a DOM text node, succeeded by the given `nextNode` should
* be trimmed from the right side.
*
* @param {Node} node
* @param {Node} prevNode
*/
_checkShouldRightTrimDomText( node, nextNode ) {
if ( nextNode ) {
return false;
}
return !startsWithFiller( node );
}
/**
* Helper function. For given {@link module:engine/view/text~Text view text node}, it finds previous or next sibling

@@ -1043,8 +1081,13 @@ * that is contained in the same container element. If there is no such sibling, `null` is returned.

for ( const value of treeWalker ) {
// ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
// text node in its container element.
if ( value.item.is( 'containerElement' ) ) {
// ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
// text node in its container element.
return null;
} else if ( value.item.is( 'textProxy' ) ) {
// Found a text node in the same container element.
}
// <br> found – it works like a block boundary, so do not scan further.
else if ( value.item.is( 'br' ) ) {
return null;
}
// Found a text node in the same container element.
else if ( value.item.is( 'textProxy' ) ) {
return value.item;

@@ -1058,11 +1101,23 @@ }

/**
* Helper function. For given `Text` node, it finds previous or next sibling that is contained in the same block element.
* If there is no such sibling, `null` is returned.
* Helper function. For the given text node, it finds the closest touching node which is either
* a text node or a `<br>`. The search is terminated at block element boundaries and if a matching node
* wasn't found so far, `null` is returned.
*
* In the following DOM structure:
*
* <p>foo<b>bar</b><br>bom</p>
*
* * `foo` doesn't have its previous touching inline node (`null` is returned),
* * `foo`'s next touching inline node is `bar`
* * `bar`'s next touching inline node is `<br>`
*
* This method returns text nodes and `<br>` elements because these types of nodes affect how
* spaces in the given text node need to be converted.
*
* @private
* @param {Text} node
* @param {Boolean} getNext
* @returns {Text|null}
* @returns {Text|Element|null}
*/
_getTouchingDomTextNode( node, getNext ) {
_getTouchingInlineDomNode( node, getNext ) {
if ( !node.parentNode ) {

@@ -1076,4 +1131,16 @@ return null;

const treeWalker = document.createTreeWalker( topmostParent, NodeFilter.SHOW_TEXT );
const treeWalker = document.createTreeWalker( topmostParent, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
acceptNode( node ) {
if ( isText( node ) ) {
return NodeFilter.FILTER_ACCEPT;
}
if ( node.tagName == 'BR' ) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
}
} );
treeWalker.currentNode = node;

@@ -1080,0 +1147,0 @@

@@ -70,3 +70,3 @@ /**

* @readonly
* @return {module:engine/view/document~Document}
* @returns {module:engine/view/document~Document}
*/

@@ -73,0 +73,0 @@ get document() {

@@ -882,3 +882,3 @@ /**

// @param {String|module:engine/view/item~Item|Iterable.<String|module:engine/view/item~Item>}
// @return {Iterable.<module:engine/view/node~Node>}
// @returns {Iterable.<module:engine/view/node~Node>}
function normalize( nodes ) {

@@ -885,0 +885,0 @@ // Separate condition because string is iterable.

@@ -32,3 +32,3 @@ /**

// Create view post fixer that will add placeholder where needed.
// Create view post-fixer that will add placeholder where needed.
document.registerPostFixer( writer => updateAllPlaceholders( document, writer ) );

@@ -35,0 +35,0 @@ }

@@ -21,15 +21,17 @@ /**

import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
import isNode from '@ckeditor/ckeditor5-utils/src/dom/isnode';
import fastDiff from '@ckeditor/ckeditor5-utils/src/fastdiff';
/**
* Renderer updates DOM structure and selection, to make them a reflection of the view structure and selection.
* Renderer is responsible for updating the DOM structure and the DOM selection based on
* the {@link module:engine/view/renderer~Renderer#markToSync information about updated view nodes}.
* In other words, it renders the view to the DOM.
*
* View nodes which may need to be rendered needs to be {@link module:engine/view/renderer~Renderer#markToSync marked}.
* Then, on {@link module:engine/view/renderer~Renderer#render render}, renderer compares view nodes with DOM nodes
* in order to check which ones really need to be refreshed. Finally, it creates DOM nodes from these view nodes,
* {@link module:engine/view/domconverter~DomConverter#bindElements binds} them and inserts into the DOM tree.
* Its main responsibility is to make only the necessary, minimal changes to the DOM. However, unlike in many
* virtual DOM implementations, the primary reason for doing minimal changes is not the performance but ensuring
* that native editing features such as text composition, autocompletion, spell checking, selection's x-index are
* affected as little as possible.
*
* Every time {@link module:engine/view/renderer~Renderer#render render} is called, renderer additionally checks if
* {@link module:engine/view/renderer~Renderer#selection selection} needs update and updates it if so.
*
* Renderer uses {@link module:engine/view/domconverter~DomConverter} to transform and bind nodes.
* Renderer uses {@link module:engine/view/domconverter~DomConverter} to transform view nodes and positions
* to and from the DOM.
*/

@@ -93,2 +95,10 @@ export default class Renderer {

/**
* Indicates if the view document is focused and selection can be rendered. Selection will not be rendered if
* this is set to `false`.
*
* @member {Boolean}
*/
this.isFocused = false;
/**
* The text node in which the inline filler was rendered.

@@ -102,10 +112,2 @@ *

/**
* Indicates if the view document is focused and selection can be rendered. Selection will not be rendered if
* this is set to `false`.
*
* @member {Boolean}
*/
this.isFocused = false;
/**
* DOM element containing fake selection.

@@ -120,5 +122,5 @@ *

/**
* Mark node to be synchronized.
* Marks a view node to be updated in the DOM by {@link #render `render()`}.
*
* Note that only view nodes which parents have corresponding DOM elements need to be marked to be synchronized.
* Note that only view nodes whose parents have corresponding DOM elements need to be marked to be synchronized.
*

@@ -160,24 +162,11 @@ * @see #markedAttributes

/**
* Render method checks {@link #markedAttributes},
* {@link #markedChildren} and {@link #markedTexts} and updates all
* nodes which need to be updated. Then it clears all three sets. Also, every time render is called it compares and
* if needed updates the selection.
* Renders all buffered changes ({@link #markedAttributes}, {@link #markedChildren} and {@link #markedTexts}) and
* the current view selection (if needed) to the DOM by applying a minimal set of changes to it.
*
* Renderer tries not to break text composition (e.g. IME) and x-index of the selection,
* Renderer tries not to break the text composition (e.g. IME) and x-index of the selection,
* so it does as little as it is needed to update the DOM.
*
* For attributes it adds new attributes to DOM elements, updates values and removes
* attributes which do not exist in the view element.
*
* For text nodes it updates the text string if it is different. Note that if parent element is marked as an element
* which changed child list, text node update will not be done, because it may not be possible to
* {@link module:engine/view/domconverter~DomConverter#findCorrespondingDomText find a corresponding DOM text}.
* The change will be handled in the parent element.
*
* For elements, which child lists have changed, it calculates a {@link module:utils/diff~diff} and adds or removes children which have
* changed.
*
* Rendering also handles {@link module:engine/view/filler fillers}. Especially, it checks if the inline filler is needed
* at selection position and adds or removes it. To prevent breaking text composition inline filler will not be
* removed as long selection is in the text node which needed it at first.
* Renderer also handles {@link module:engine/view/filler fillers}. Especially, it checks if the inline filler is needed
* at the selection position and adds or removes it. To prevent breaking text composition inline filler will not be
* removed as long as the selection is in the text node which needed it at first.
*/

@@ -187,2 +176,7 @@ render() {

// Refresh mappings.
for ( const element of this.markedChildren ) {
this._updateChildrenMappings( element );
}
// There was inline filler rendered in the DOM but it's not

@@ -252,6 +246,84 @@ // at the selection position any more, so we can remove it

/**
* Adds inline filler at given position.
* Updates mappings of view element's children.
*
* Children that were replaced in the view structure by similar elements (same tag name) are treated as 'replaced'.
* This means that their mappings can be updated so the new view elements are mapped to the existing DOM elements.
* Thanks to that these elements do not need to be re-rendered completely.
*
* @private
* @param {module:engine/view/node~Node} viewElement The view element whose children mappings will be updated.
*/
_updateChildrenMappings( viewElement ) {
const domElement = this.domConverter.mapViewToDom( viewElement );
if ( !domElement ) {
// If there is no `domElement` it means that it was already removed from DOM and there is no need to process it.
return;
}
const diff = this._diffChildren( viewElement );
const actions = this._findReplaceActions( diff.actions, diff.actualDomChildren, diff.expectedDomChildren );
if ( actions.indexOf( 'replace' ) !== -1 ) {
const counter = { equal: 0, insert: 0, delete: 0 };
for ( const action of actions ) {
if ( action === 'replace' ) {
const insertIndex = counter.equal + counter.insert;
const deleteIndex = counter.equal + counter.delete;
const viewChild = viewElement.getChild( insertIndex );
// The 'uiElement' is a special one and its children are not stored in a view (#799),
// so we cannot use it with replacing flow (since it uses view children during rendering
// which will always result in rendering empty element).
if ( viewChild && !viewChild.is( 'uiElement' ) ) {
this._updateElementMappings( viewChild, diff.actualDomChildren[ deleteIndex ] );
}
remove( diff.expectedDomChildren[ insertIndex ] );
counter.equal++;
} else {
counter[ action ]++;
}
}
}
}
/**
* Updates mappings of a given view element.
*
* @private
* @param {module:engine/view/node~Node} viewElement The view element whose mappings will be updated.
* @param {Node} domElement The DOM element representing the given view element.
*/
_updateElementMappings( viewElement, domElement ) {
// Because we replace new view element mapping with the existing one, the corresponding DOM element
// will not be rerendered. The new view element may have different attributes than the previous one.
// Since its corresponding DOM element will not be rerendered, new attributes will not be added
// to the DOM, so we need to mark it here to make sure its attributes gets updated.
// Such situations may happen if only new view element was added to `this.markedAttributes`
// or none of the elements were added (relying on 'this._updateChildren()' which by rerendering the element
// also rerenders its attributes). See #1427 for more detailed case study.
const newViewChild = this.domConverter.mapDomToView( domElement );
// It may also happen that 'newViewChild' mapping is not present since its parent mapping
// was already removed (the 'domConverter.unbindDomElement()' method also unbinds children
// mappings) so we also check for '!newViewChild'.
if ( !newViewChild || newViewChild && !newViewChild.isSimilar( viewElement ) ) {
this.markedAttributes.add( viewElement );
}
// Remap 'DomConverter' bindings.
this.domConverter.unbindDomElement( domElement );
this.domConverter.bindElements( domElement, viewElement );
// View element may have children which needs to be updated, but are not marked, mark them to update.
this.markedChildren.add( viewElement );
}
/**
* Adds inline filler at a given position.
*
* The position can be given as an array of DOM nodes and an offset in that array,
* or a DOM parent element and offset in that element.
* or a DOM parent element and an offset in that element.
*

@@ -262,3 +334,3 @@ * @private

* @param {Number} offset
* @returns {Text} The DOM text node that contains inline filler.
* @returns {Text} The DOM text node that contains an inline filler.
*/

@@ -289,7 +361,7 @@ _addInlineFiller( domDocument, domParentOrArray, offset ) {

* Here, we assume that we know that the filler is needed and
* {@link #_isSelectionInInlineFiller is at the selection position}, and, since it's needed,
* it's somewhere at the selection postion.
* {@link #_isSelectionInInlineFiller is at the selection position}, and, since it is needed,
* it is somewhere at the selection position.
*
* Note: we cannot restore the filler position based on the filler's DOM text node, because
* when this method is called (before rendering) the bindings will often be broken. View to DOM
* Note: The filler position cannot be restored based on the filler's DOM text node, because
* when this method is called (before rendering), the bindings will often be broken. View-to-DOM
* bindings are only dependable after rendering.

@@ -311,8 +383,8 @@ *

/**
* Returns `true` if the selection hasn't left the inline filler's text node.
* If it is `true` it means that the filler had been added for a reason and the selection does not
* left the filler's text node. E.g. the user can be in the middle of a composition so it should not be touched.
* Returns `true` if the selection has not left the inline filler's text node.
* If it is `true`, it means that the filler had been added for a reason and the selection did not
* leave the filler's text node. For example, the user can be in the middle of a composition so it should not be touched.
*
* @private
* @returns {Boolean} True if the inline filler and selection are in the same place.
* @returns {Boolean} `true` if the inline filler and selection are in the same place.
*/

@@ -375,3 +447,3 @@ _isSelectionInInlineFiller() {

* @private
* @returns {Boolean} True if the inline fillers should be added.
* @returns {Boolean} `true` if the inline filler should be added.
*/

@@ -423,3 +495,3 @@ _needsInlineFillerAtSelection() {

* @param {Object} options
* @param {module:engine/view/position~Position} options.inlineFillerPosition The position on which the inline
* @param {module:engine/view/position~Position} options.inlineFillerPosition The position where the inline
* filler should be rendered.

@@ -441,3 +513,11 @@ */

if ( actualText != expectedText ) {
domText.data = expectedText;
const actions = fastDiff( actualText, expectedText );
for ( const action of actions ) {
if ( action.type === 'insert' ) {
domText.insertData( action.index, action.values.join( '' ) );
} else { // 'delete'
domText.deleteData( action.index, action.howMany );
}
}
}

@@ -447,9 +527,18 @@ }

/**
* Checks if attributes list needs to be updated and possibly updates it.
* Checks if attribute list needs to be updated and possibly updates it.
*
* @private
* @param {module:engine/view/element~Element} viewElement View element to update.
* @param {module:engine/view/element~Element} viewElement The view element to update.
*/
_updateAttrs( viewElement ) {
const domElement = this.domConverter.mapViewToDom( viewElement );
if ( !domElement ) {
// If there is no `domElement` it means that 'viewElement' is outdated as its mapping was updated
// in 'this._updateChildrenMappings()'. There is no need to process it as new view element which
// replaced old 'viewElement' mapping was also added to 'this.markedAttributes'
// in 'this._updateChildrenMappings()' so it will be processed separately.
return;
}
const domAttrKeys = Array.from( domElement.attributes ).map( attr => attr.name );

@@ -477,33 +566,24 @@ const viewAttrKeys = viewElement.getAttributeKeys();

* @param {Object} options
* @param {module:engine/view/position~Position} options.inlineFillerPosition The position on which the inline
* @param {module:engine/view/position~Position} options.inlineFillerPosition The position where the inline
* filler should be rendered.
*/
_updateChildren( viewElement, options ) {
const domConverter = this.domConverter;
const domElement = domConverter.mapViewToDom( viewElement );
const domElement = this.domConverter.mapViewToDom( viewElement );
if ( !domElement ) {
// If there is no `domElement` it means that it was already removed from DOM.
// There is no need to update it. It will be updated when re-inserted.
// There is no need to process it. It will be processed when re-inserted.
return;
}
const domDocument = domElement.ownerDocument;
const filler = options.inlineFillerPosition;
const actualDomChildren = domElement.childNodes;
const expectedDomChildren = Array.from( domConverter.viewChildrenToDom( viewElement, domDocument, { bind: true } ) );
const inlineFillerPosition = options.inlineFillerPosition;
// As binding may change actual DOM children we need to do this before diffing.
const expectedDomChildren = this._getElementExpectedChildren( viewElement, domElement, { bind: true, inlineFillerPosition } );
const diff = this._diffChildren( viewElement, inlineFillerPosition );
const actualDomChildren = diff.actualDomChildren;
// Inline filler element has to be created during children update because we need it to diff actual dom
// elements with expected dom elements. We need inline filler in expected dom elements so we won't re-render
// text node if it is not necessary.
if ( filler && filler.parent == viewElement ) {
this._addInlineFiller( domDocument, expectedDomChildren, filler.offset );
}
const actions = diff( actualDomChildren, expectedDomChildren, sameNodes );
let i = 0;
const nodesToUnbind = new Set();
for ( const action of actions ) {
for ( const action of diff.actions ) {
if ( action === 'insert' ) {

@@ -517,3 +597,3 @@ insertAt( domElement, i, expectedDomChildren[ i ] );

// Force updating text nodes inside elements which did not change and do not need to be re-rendered (#1125).
this._markDescendantTextToSync( domConverter.domToView( expectedDomChildren[ i ] ) );
this._markDescendantTextToSync( this.domConverter.domToView( expectedDomChildren[ i ] ) );
i++;

@@ -531,26 +611,103 @@ }

}
}
function sameNodes( actualDomChild, expectedDomChild ) {
// Elements.
if ( actualDomChild === expectedDomChild ) {
return true;
}
// Texts.
else if ( isText( actualDomChild ) && isText( expectedDomChild ) ) {
return actualDomChild.data === expectedDomChild.data;
}
// Block fillers.
else if ( isBlockFiller( actualDomChild, domConverter.blockFiller ) &&
isBlockFiller( expectedDomChild, domConverter.blockFiller ) ) {
return true;
}
/**
* Compares view element's actual and expected children and returns an action sequence which can be used to transform
* actual children into expected ones.
*
* @private
* @param {module:engine/view/node~Node} viewElement The view element whose children will be compared.
* @param {module:engine/view/position~Position} [inlineFillerPosition=null] The position where the inline
* filler should be rendered.
* @returns {Object|null} result
* @returns {Array.<String>} result.actions List of actions based on {@link module:utils/diff~diff} function.
* @returns {Array.<Node>} result.actualDomChildren Current view element's DOM children.
* @returns {Array.<Node>} result.expectedDomChildren Expected view element's DOM children.
*/
_diffChildren( viewElement, inlineFillerPosition = null ) {
const domElement = this.domConverter.mapViewToDom( viewElement );
const actualDomChildren = domElement.childNodes;
const expectedDomChildren = this._getElementExpectedChildren( viewElement, domElement,
{ withChildren: false, inlineFillerPosition } );
// Not matching types.
return false;
return {
actions: diff( actualDomChildren, expectedDomChildren, sameNodes.bind( null, this.domConverter.blockFiller ) ),
actualDomChildren,
expectedDomChildren
};
}
/**
* Returns expected DOM children for a given view element.
*
* @private
* @param {module:engine/view/node~Node} viewElement View element whose children will be returned.
* @param {Node} domElement The DOM representation of a given view element.
* @param {Object} options See the {@link module:engine/view/domconverter~DomConverter#viewToDom} options parameter.
* @param {module:engine/view/position~Position} [options.inlineFillerPosition=null] The position where
* the inline filler should be rendered.
* @returns {Array.<Node>} The view element's expected children.
*/
_getElementExpectedChildren( viewElement, domElement, options ) {
const expectedDomChildren = Array.from( this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, options ) );
const filler = options.inlineFillerPosition;
// Inline filler element has to be created as it is present in a DOM, but not in a view. It is required
// during diffing so text nodes could be compared correctly and also during rendering to maintain
// proper order and indexes while updating the DOM.
if ( filler && filler.parent === viewElement ) {
this._addInlineFiller( domElement.ownerDocument, expectedDomChildren, filler.offset );
}
return expectedDomChildren;
}
/**
* Marks text nodes to be synced.
* Finds DOM nodes that were replaced with the similar nodes (same tag name) in the view. All nodes are compared
* within one `insert`/`delete` action group, for example:
*
* Actual DOM: <p><b>Foo</b>Bar<i>Baz</i><b>Bax</b></p>
* Expected DOM: <p>Bar<b>123</b><i>Baz</i><b>456</b></p>
* Input actions: [ insert, insert, delete, delete, equal, insert, delete ]
* Output actions: [ insert, replace, delete, equal, replace ]
*
* @private
* @param {Array.<String>} actions Actions array which is a result of the {@link module:utils/diff~diff} function.
* @param {Array.<Node>} actualDom Actual DOM children
* @param {Array.<Node>} expectedDom Expected DOM children.
* @returns {Array.<String>} Actions array modified with the `replace` actions.
*/
_findReplaceActions( actions, actualDom, expectedDom ) {
// If there is no both 'insert' and 'delete' actions, no need to check for replaced elements.
if ( actions.indexOf( 'insert' ) === -1 || actions.indexOf( 'delete' ) === -1 ) {
return actions;
}
let newActions = [];
let actualSlice = [];
let expectedSlice = [];
const counter = { equal: 0, insert: 0, delete: 0 };
for ( const action of actions ) {
if ( action === 'insert' ) {
expectedSlice.push( expectedDom[ counter.equal + counter.insert ] );
} else if ( action === 'delete' ) {
actualSlice.push( actualDom[ counter.equal + counter.delete ] );
} else { // equal
newActions = newActions.concat( diff( actualSlice, expectedSlice, areSimilar ).map( x => x === 'equal' ? 'replace' : x ) );
newActions.push( 'equal' );
// Reset stored elements on 'equal'.
actualSlice = [];
expectedSlice = [];
}
counter[ action ]++;
}
return newActions.concat( diff( actualSlice, expectedSlice, areSimilar ).map( x => x === 'equal' ? 'replace' : x ) );
}
/**
* Marks text nodes to be synchronized.
*
* If a text node is passed, it will be marked. If an element is passed, all descendant text nodes inside it will be marked.

@@ -576,3 +733,3 @@ *

/**
* Checks if selection needs to be updated and possibly updates it.
* Checks if the selection needs to be updated and possibly updates it.
*

@@ -607,6 +764,6 @@ * @private

/**
* Updates fake selection.
* Updates the fake selection.
*
* @private
* @param {HTMLElement} domRoot Valid DOM root where fake selection container should be added.
* @param {HTMLElement} domRoot A valid DOM root where the fake selection container should be added.
*/

@@ -654,6 +811,6 @@ _updateFakeSelection( domRoot ) {

/**
* Updates DOM selection.
* Updates the DOM selection.
*
* @private
* @param {HTMLElement} domRoot Valid DOM root where DOM selection should be rendered.
* @param {HTMLElement} domRoot A valid DOM root where the DOM selection should be rendered.
*/

@@ -685,6 +842,6 @@ _updateDomSelection( domRoot ) {

/**
* Checks whether given DOM selection needs to be updated.
* Checks whether a given DOM selection needs to be updated.
*
* @private
* @param {Selection} domSelection DOM selection to check.
* @param {Selection} domSelection The DOM selection to check.
* @returns {Boolean}

@@ -715,3 +872,3 @@ */

/**
* Removes DOM selection.
* Removes the DOM selection.
*

@@ -736,3 +893,3 @@ * @private

/**
* Removes fake selection.
* Removes the fake selection.
*

@@ -781,1 +938,45 @@ * @private

}
// Whether two DOM nodes should be considered as similar.
// Nodes are considered similar if they have the same tag name.
//
// @private
// @param {Node} node1
// @param {Node} node2
// @returns {Boolean}
function areSimilar( node1, node2 ) {
return isNode( node1 ) && isNode( node2 ) &&
!isText( node1 ) && !isText( node2 ) &&
node1.tagName.toLowerCase() === node2.tagName.toLowerCase();
}
// Whether two dom nodes should be considered as the same.
// Two nodes which are considered the same are:
//
// * Text nodes with the same text.
// * Element nodes represented by the same object.
// * Two block filler elements.
//
// @private
// @param {Function} blockFiller Block filler creator function, see {@link module:engine/view/domconverter~DomConverter#blockFiller}.
// @param {Node} node1
// @param {Node} node2
// @returns {Boolean}
function sameNodes( blockFiller, actualDomChild, expectedDomChild ) {
// Elements.
if ( actualDomChild === expectedDomChild ) {
return true;
}
// Texts.
else if ( isText( actualDomChild ) && isText( expectedDomChild ) ) {
return actualDomChild.data === expectedDomChild.data;
}
// Block fillers.
else if ( isBlockFiller( actualDomChild, blockFiller ) &&
isBlockFiller( expectedDomChild, blockFiller ) ) {
return true;
}
// Not matching types.
return false;
}

@@ -84,3 +84,3 @@ /**

* @param {Document} domDocument
* @return {HTMLElement}
* @returns {HTMLElement}
*/

@@ -87,0 +87,0 @@ render( domDocument ) {

@@ -328,9 +328,10 @@ /**

* This may be caused by:
* * calling {@link #change} or {@link #render} during rendering process,
* * calling {@link #change} or {@link #render} inside of
* {@link module:engine/view/document~Document#registerPostFixer post fixer function}.
*
* * calling {@link #change} or {@link #render} during rendering process,
* * calling {@link #change} or {@link #render} inside of
* {@link module:engine/view/document~Document#registerPostFixer post-fixer function}.
*/
throw new CKEditorError(
'cannot-change-view-tree: ' +
'Attempting to make changes to the view when it is in incorrect state: rendering or post fixers are in progress. ' +
'Attempting to make changes to the view when it is in incorrect state: rendering or post-fixers are in progress. ' +
'This may cause some unexpected behaviour and inconsistency between the DOM and the view.'

@@ -353,3 +354,3 @@ );

// Execute all document post fixers after the change.
// Execute all document post-fixers after the change.
this._postFixersInProgress = true;

@@ -400,3 +401,3 @@ this.document._callPostFixers( this._writer );

* Fired after a topmost {@link module:engine/view/view~View#change change block} and all
* {@link module:engine/view/document~Document#registerPostFixer post fixers} are executed.
* {@link module:engine/view/document~Document#registerPostFixer post-fixers} are executed.
*

@@ -411,3 +412,3 @@ * Actual rendering is performed as a first listener on 'normal' priority.

* balloon panel. If you wants to change view structure use
* {@link module:engine/view/document~Document#registerPostFixer post fixers}.
* {@link module:engine/view/document~Document#registerPostFixer post-fixers}.
*

@@ -414,0 +415,0 @@ * @event module:engine/view/view~View#event:render

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc