8
0
Просмотр исходного кода

Merge branch 'master' into t/ckeditor5-table/163

Piotrek Koszuliński 7 лет назад
Родитель
Сommit
bff3c8237f
100 измененных файлов с 694 добавлено и 357 удалено
  1. 1 1
      packages/ckeditor5-engine/.eslintrc.js
  2. 1 1
      packages/ckeditor5-engine/LICENSE.md
  3. 8 4
      packages/ckeditor5-engine/package.json
  4. 18 2
      packages/ckeditor5-engine/src/controller/datacontroller.js
  5. 3 4
      packages/ckeditor5-engine/src/controller/editingcontroller.js
  6. 70 78
      packages/ckeditor5-engine/src/conversion/conversion.js
  7. 40 0
      packages/ckeditor5-engine/src/conversion/conversionhelpers.js
  8. 1 1
      packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
  9. 3 3
      packages/ckeditor5-engine/src/conversion/downcasthelpers.js
  10. 1 1
      packages/ckeditor5-engine/src/conversion/mapper.js
  11. 1 1
      packages/ckeditor5-engine/src/conversion/modelconsumable.js
  12. 0 0
      packages/ckeditor5-engine/src/conversion/upcast-converters.js
  13. 2 2
      packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
  14. 3 3
      packages/ckeditor5-engine/src/conversion/upcasthelpers.js
  15. 1 1
      packages/ckeditor5-engine/src/conversion/viewconsumable.js
  16. 1 1
      packages/ckeditor5-engine/src/dataprocessor/basichtmlwriter.js
  17. 1 1
      packages/ckeditor5-engine/src/dataprocessor/dataprocessor.jsdoc
  18. 1 1
      packages/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js
  19. 1 1
      packages/ckeditor5-engine/src/dataprocessor/htmlwriter.js
  20. 1 1
      packages/ckeditor5-engine/src/dataprocessor/xmldataprocessor.js
  21. 1 1
      packages/ckeditor5-engine/src/dev-utils/enableenginedebug.js
  22. 1 1
      packages/ckeditor5-engine/src/dev-utils/model.js
  23. 1 1
      packages/ckeditor5-engine/src/dev-utils/operationreplayer.js
  24. 1 1
      packages/ckeditor5-engine/src/dev-utils/view.js
  25. 1 1
      packages/ckeditor5-engine/src/model/batch.js
  26. 27 1
      packages/ckeditor5-engine/src/model/differ.js
  27. 58 32
      packages/ckeditor5-engine/src/model/document.js
  28. 1 1
      packages/ckeditor5-engine/src/model/documentfragment.js
  29. 41 15
      packages/ckeditor5-engine/src/model/documentselection.js
  30. 3 3
      packages/ckeditor5-engine/src/model/element.js
  31. 1 1
      packages/ckeditor5-engine/src/model/history.js
  32. 1 1
      packages/ckeditor5-engine/src/model/item.jsdoc
  33. 1 1
      packages/ckeditor5-engine/src/model/liveposition.js
  34. 1 1
      packages/ckeditor5-engine/src/model/liverange.js
  35. 1 5
      packages/ckeditor5-engine/src/model/markercollection.js
  36. 5 2
      packages/ckeditor5-engine/src/model/model.js
  37. 1 1
      packages/ckeditor5-engine/src/model/node.js
  38. 1 1
      packages/ckeditor5-engine/src/model/nodelist.js
  39. 1 1
      packages/ckeditor5-engine/src/model/operation/attributeoperation.js
  40. 1 1
      packages/ckeditor5-engine/src/model/operation/detachoperation.js
  41. 1 1
      packages/ckeditor5-engine/src/model/operation/insertoperation.js
  42. 1 1
      packages/ckeditor5-engine/src/model/operation/markeroperation.js
  43. 1 1
      packages/ckeditor5-engine/src/model/operation/mergeoperation.js
  44. 1 1
      packages/ckeditor5-engine/src/model/operation/moveoperation.js
  45. 1 1
      packages/ckeditor5-engine/src/model/operation/nooperation.js
  46. 1 1
      packages/ckeditor5-engine/src/model/operation/operation.js
  47. 1 1
      packages/ckeditor5-engine/src/model/operation/operationfactory.js
  48. 1 1
      packages/ckeditor5-engine/src/model/operation/renameoperation.js
  49. 1 1
      packages/ckeditor5-engine/src/model/operation/rootattributeoperation.js
  50. 1 1
      packages/ckeditor5-engine/src/model/operation/splitoperation.js
  51. 30 7
      packages/ckeditor5-engine/src/model/operation/transform.js
  52. 1 1
      packages/ckeditor5-engine/src/model/operation/utils.js
  53. 1 1
      packages/ckeditor5-engine/src/model/position.js
  54. 1 1
      packages/ckeditor5-engine/src/model/range.js
  55. 1 1
      packages/ckeditor5-engine/src/model/rootelement.js
  56. 32 6
      packages/ckeditor5-engine/src/model/schema.js
  57. 18 1
      packages/ckeditor5-engine/src/model/selection.js
  58. 1 1
      packages/ckeditor5-engine/src/model/text.js
  59. 1 1
      packages/ckeditor5-engine/src/model/textproxy.js
  60. 1 1
      packages/ckeditor5-engine/src/model/treewalker.js
  61. 1 1
      packages/ckeditor5-engine/src/model/utils/deletecontent.js
  62. 1 1
      packages/ckeditor5-engine/src/model/utils/getselectedcontent.js
  63. 1 1
      packages/ckeditor5-engine/src/model/utils/insertcontent.js
  64. 1 1
      packages/ckeditor5-engine/src/model/utils/modifyselection.js
  65. 1 1
      packages/ckeditor5-engine/src/model/utils/selection-post-fixer.js
  66. 5 8
      packages/ckeditor5-engine/src/model/writer.js
  67. 1 1
      packages/ckeditor5-engine/src/utils/bindtwostepcarettoattribute.js
  68. 1 1
      packages/ckeditor5-engine/src/view/attributeelement.js
  69. 1 1
      packages/ckeditor5-engine/src/view/containerelement.js
  70. 4 1
      packages/ckeditor5-engine/src/view/document.js
  71. 1 1
      packages/ckeditor5-engine/src/view/documentfragment.js
  72. 19 1
      packages/ckeditor5-engine/src/view/documentselection.js
  73. 1 1
      packages/ckeditor5-engine/src/view/domconverter.js
  74. 1 1
      packages/ckeditor5-engine/src/view/downcastwriter.js
  75. 1 1
      packages/ckeditor5-engine/src/view/editableelement.js
  76. 10 8
      packages/ckeditor5-engine/src/view/element.js
  77. 1 1
      packages/ckeditor5-engine/src/view/elementdefinition.jsdoc
  78. 1 1
      packages/ckeditor5-engine/src/view/emptyelement.js
  79. 1 1
      packages/ckeditor5-engine/src/view/filler.js
  80. 1 1
      packages/ckeditor5-engine/src/view/item.jsdoc
  81. 1 1
      packages/ckeditor5-engine/src/view/matcher.js
  82. 2 3
      packages/ckeditor5-engine/src/view/node.js
  83. 1 1
      packages/ckeditor5-engine/src/view/observer/clickobserver.js
  84. 1 1
      packages/ckeditor5-engine/src/view/observer/compositionobserver.js
  85. 1 1
      packages/ckeditor5-engine/src/view/observer/domeventdata.js
  86. 1 1
      packages/ckeditor5-engine/src/view/observer/domeventobserver.js
  87. 1 1
      packages/ckeditor5-engine/src/view/observer/fakeselectionobserver.js
  88. 3 3
      packages/ckeditor5-engine/src/view/observer/focusobserver.js
  89. 1 1
      packages/ckeditor5-engine/src/view/observer/keyobserver.js
  90. 1 1
      packages/ckeditor5-engine/src/view/observer/mouseobserver.js
  91. 3 3
      packages/ckeditor5-engine/src/view/observer/mutationobserver.js
  92. 3 3
      packages/ckeditor5-engine/src/view/observer/observer.js
  93. 2 2
      packages/ckeditor5-engine/src/view/observer/selectionobserver.js
  94. 192 85
      packages/ckeditor5-engine/src/view/placeholder.js
  95. 1 1
      packages/ckeditor5-engine/src/view/position.js
  96. 1 1
      packages/ckeditor5-engine/src/view/range.js
  97. 1 1
      packages/ckeditor5-engine/src/view/renderer.js
  98. 1 1
      packages/ckeditor5-engine/src/view/rooteditableelement.js
  99. 18 1
      packages/ckeditor5-engine/src/view/selection.js
  100. 1 1
      packages/ckeditor5-engine/src/view/text.js

+ 1 - 1
packages/ckeditor5-engine/.eslintrc.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/LICENSE.md

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 ==========================
 
 
 **CKEditor 5 Editing Engine** – https://github.com/ckeditor/ckeditor5-engine <br>
 **CKEditor 5 Editing Engine** – https://github.com/ckeditor/ckeditor5-engine <br>
-Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2019, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
 
 Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
 Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
 
 

+ 8 - 4
packages/ckeditor5-engine/package.json

@@ -40,7 +40,7 @@
     "@ckeditor/ckeditor5-widget": "^10.3.1",
     "@ckeditor/ckeditor5-widget": "^10.3.1",
     "eslint": "^5.5.0",
     "eslint": "^5.5.0",
     "eslint-config-ckeditor5": "^1.0.9",
     "eslint-config-ckeditor5": "^1.0.9",
-    "husky": "^0.14.3",
+    "husky": "^1.3.1",
     "lint-staged": "^7.0.0"
     "lint-staged": "^7.0.0"
   },
   },
   "engines": {
   "engines": {
@@ -61,8 +61,7 @@
     "theme"
     "theme"
   ],
   ],
   "scripts": {
   "scripts": {
-    "lint": "eslint --quiet '**/*.js'",
-    "precommit": "lint-staged"
+    "lint": "eslint --quiet '**/*.js'"
   },
   },
   "lint-staged": {
   "lint-staged": {
     "**/*.js": [
     "**/*.js": [
@@ -72,5 +71,10 @@
   "eslintIgnore": [
   "eslintIgnore": [
     "src/lib/**",
     "src/lib/**",
     "packages/**"
     "packages/**"
-  ]
+  ],
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  }
 }
 }

+ 18 - 2
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -103,6 +103,12 @@ export default class DataController {
 		this.upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
 		this.upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
 
 
 		this.decorate( 'init' );
 		this.decorate( 'init' );
+
+		// Fire `ready` event when initialisation has completed. Such low level listener gives possibility
+		// to plug into initialisation pipeline without interrupting the initialisation flow.
+		this.on( 'init', () => {
+			this.fire( 'ready' );
+		}, { priority: 'lowest' } );
 	}
 	}
 
 
 	/**
 	/**
@@ -373,7 +379,17 @@ export default class DataController {
 	}
 	}
 
 
 	/**
 	/**
-	 * Event fired by decorated {@link #init} method.
+	 * Event fired once data initialisation has finished.
+	 *
+	 * @event ready
+	 */
+
+	/**
+	 * Event fired after {@link #init init() method} has been run. It can be {@link #listenTo listened to} to adjust/modify
+	 * the initialisation flow. However, if the `init` event is stopped or prevented, the {@link #event:ready ready event}
+	 * should be fired manually.
+	 *
+	 * The `init` event is fired by decorated {@link #init} method.
 	 * See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.
 	 * See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.
 	 *
 	 *
 	 * @event init
 	 * @event init

+ 3 - 4
packages/ckeditor5-engine/src/controller/editingcontroller.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -75,12 +75,11 @@ export default class EditingController {
 		//
 		//
 		// See  https://github.com/ckeditor/ckeditor5-engine/issues/1528
 		// See  https://github.com/ckeditor/ckeditor5-engine/issues/1528
 		this.listenTo( this.model, '_beforeChanges', () => {
 		this.listenTo( this.model, '_beforeChanges', () => {
-			this.view._renderingDisabled = true;
+			this.view._disableRendering( true );
 		}, { priority: 'highest' } );
 		}, { priority: 'highest' } );
 
 
 		this.listenTo( this.model, '_afterChanges', () => {
 		this.listenTo( this.model, '_afterChanges', () => {
-			this.view._renderingDisabled = false;
-			this.view.render();
+			this.view._disableRendering( false );
 		}, { priority: 'lowest' } );
 		}, { priority: 'lowest' } );
 
 
 		// Whenever model document is changed, convert those changes to the view (using model.Document#differ).
 		// Whenever model document is changed, convert those changes to the view (using model.Document#differ).

+ 70 - 78
packages/ckeditor5-engine/src/conversion/conversion.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -8,6 +8,8 @@
  */
  */
 
 
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import UpcastHelpers from './upcasthelpers';
+import DowncastHelpers from './downcasthelpers';
 
 
 /**
 /**
  * A utility class that helps add converters to upcast and downcast dispatchers.
  * A utility class that helps add converters to upcast and downcast dispatchers.
@@ -15,7 +17,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  * We recommend reading the {@glink framework/guides/architecture/editing-engine Editing engine architecture} guide first to
  * We recommend reading the {@glink framework/guides/architecture/editing-engine Editing engine architecture} guide first to
  * understand the core concepts of the conversion mechanisms.
  * understand the core concepts of the conversion mechanisms.
  *
  *
- * The instance of the conversion manager is available in the
+ * An instance of the conversion manager is available in the
  * {@link module:core/editor/editor~Editor#conversion `editor.conversion`} property
  * {@link module:core/editor/editor~Editor#conversion `editor.conversion`} property
  * and by default has the following groups of dispatchers (i.e. directions of conversion):
  * and by default has the following groups of dispatchers (i.e. directions of conversion):
  *
  *
@@ -56,44 +58,68 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 export default class Conversion {
 export default class Conversion {
 	/**
 	/**
 	 * Creates a new conversion instance.
 	 * Creates a new conversion instance.
+	 *
+	 * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
+	 * Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher>} downcastDispatchers
+	 * @param {module:engine/conversion/upcastdispatcher~UpcastDispatcher|
+	 * Array.<module:engine/conversion/upcastdispatcher~UpcastDispatcher>} upcastDispatchers
 	 */
 	 */
-	constructor() {
+	constructor( downcastDispatchers, upcastDispatchers ) {
 		/**
 		/**
+		 * Maps dispatchers group name to ConversionHelpers instances.
+		 *
 		 * @private
 		 * @private
-		 * @member {Map}
+		 * @member {Map.<String,module:engine/conversion/conversionhelpers~ConversionHelpers>}
 		 */
 		 */
-		this._conversionHelpers = new Map();
+		this._helpers = new Map();
+
+		// Define default 'downcast' & 'upcast' dispatchers groups. Those groups are always available as two-way converters needs them.
+		this._downcast = Array.isArray( downcastDispatchers ) ? downcastDispatchers : [ downcastDispatchers ];
+		this._createConversionHelpers( { name: 'downcast', dispatchers: this._downcast, isDowncast: true } );
+
+		this._upcast = Array.isArray( upcastDispatchers ) ? upcastDispatchers : [ upcastDispatchers ];
+		this._createConversionHelpers( { name: 'upcast', dispatchers: this._upcast, isDowncast: false } );
 	}
 	}
 
 
 	/**
 	/**
-	 * 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.
+	 * Define an alias for registered dispatcher.
+	 *
+	 *		const conversion = new Conversion(
+	 *			[ dataDowncastDispatcher, editingDowncastDispatcher ],
+	 *			upcastDispatcher
+	 *		);
 	 *
 	 *
-	 * If a given group name is used for the second time, the
-	 * {@link module:utils/ckeditorerror~CKEditorError `conversion-register-group-exists` error} is thrown.
+	 *		conversion.addAlias( 'dataDowncast', dataDowncastDispatcher );
 	 *
 	 *
-	 * @param {String} name The name for dispatchers group.
-	 * @param {module:engine/conversion/downcasthelpers~DowncastHelpers|
-	 * module:engine/conversion/upcasthelpers~UpcastHelpers} conversionHelpers
+	 * @param {String} alias An alias of a dispatcher.
+	 * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
+	 * module:engine/conversion/upcastdispatcher~UpcastDispatcher} dispatcher Dispatcher which should have an alias.
 	 */
 	 */
-	register( name, conversionHelpers ) {
-		if ( this._conversionHelpers.has( name ) ) {
+	addAlias( alias, dispatcher ) {
+		const isDowncast = this._downcast.includes( dispatcher );
+		const isUpcast = this._upcast.includes( dispatcher );
+
+		if ( !isUpcast && !isDowncast ) {
 			/**
 			/**
-			 * Trying to register a group name that was already registered.
+			 * Trying to register and alias for a dispatcher that nas not been registered.
 			 *
 			 *
-			 * @error conversion-register-group-exists
+			 * @error conversion-add-alias-dispatcher-not-registered
 			 */
 			 */
-			throw new CKEditorError( 'conversion-register-group-exists: Trying to register a group name that was already registered.' );
+			throw new CKEditorError( 'conversion-add-alias-dispatcher-not-registered: ' +
+				'Trying to register and alias for a dispatcher that nas not been registered.' );
 		}
 		}
 
 
-		this._conversionHelpers.set( name, conversionHelpers );
+		this._createConversionHelpers( { name: alias, dispatchers: [ dispatcher ], isDowncast } );
 	}
 	}
 
 
 	/**
 	/**
-	 * Provides a chainable API to assign converters to conversion dispatchers.
+	 * Provides a chainable API to assign converters to conversion dispatchers group.
+	 *
+	 * If the given group name has not been registered, the
+	 * {@link module:utils/ckeditorerror~CKEditorError `conversion-for-unknown-group` error} is thrown.
 	 *
 	 *
 	 * You can use conversion helpers available directly in the `for()` chain or your custom ones via
 	 * You can use conversion helpers available directly in the `for()` chain or your custom ones via
-	 * the {@link module:engine/conversion/conversion~ConversionHelpers#add `add()`} method.
+	 * the {@link module:engine/conversion/conversionhelpers~ConversionHelpers#add `add()`} method.
 	 *
 	 *
 	 * # Using bulit-in conversion helpers
 	 * # Using bulit-in conversion helpers
 	 *
 	 *
@@ -148,7 +174,16 @@ export default class Conversion {
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 */
 	 */
 	for( groupName ) {
 	for( groupName ) {
-		return this._getConversionHelpers( groupName );
+		if ( !this._helpers.has( groupName ) ) {
+			/**
+			 * Trying to add a converter to an unknown dispatchers group.
+			 *
+			 * @error conversion-for-unknown-group
+			 */
+			throw new CKEditorError( 'conversion-for-unknown-group: Trying to add a converter to an unknown dispatchers group.' );
+		}
+
+		return this._helpers.get( groupName );
 	}
 	}
 
 
 	/**
 	/**
@@ -534,26 +569,28 @@ export default class Conversion {
 	}
 	}
 
 
 	/**
 	/**
-	 * Returns conversion helpers registered under a given name.
-	 *
-	 * If the given group name has not been registered, the
-	 * {@link module:utils/ckeditorerror~CKEditorError `conversion-for-unknown-group` error} is thrown.
+	 * Creates and caches conversion helpers for given dispatchers group.
 	 *
 	 *
 	 * @private
 	 * @private
-	 * @param {String} groupName
-	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
+	 * @param {Object} options
+	 * @param {String} options.name Group name.
+	 * @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
+	 * module:engine/conversion/upcastdispatcher~UpcastDispatcher>} options.dispatchers
+	 * @param {Boolean} options.isDowncast
 	 */
 	 */
-	_getConversionHelpers( groupName ) {
-		if ( !this._conversionHelpers.has( groupName ) ) {
+	_createConversionHelpers( { name, dispatchers, isDowncast } ) {
+		if ( this._helpers.has( name ) ) {
 			/**
 			/**
-			 * Trying to add a converter to an unknown dispatchers group.
+			 * Trying to register a group name that has already been registered.
 			 *
 			 *
-			 * @error conversion-for-unknown-group
+			 * @error conversion-group-exists
 			 */
 			 */
-			throw new CKEditorError( 'conversion-for-unknown-group: Trying to add a converter to an unknown dispatchers group.' );
+			throw new CKEditorError( 'conversion-group-exists: Trying to register a group name that has already been registered.' );
 		}
 		}
 
 
-		return this._conversionHelpers.get( groupName );
+		const helpers = isDowncast ? new DowncastHelpers( dispatchers ) : new UpcastHelpers( dispatchers );
+
+		this._helpers.set( name, helpers );
 	}
 	}
 }
 }
 
 
@@ -604,48 +641,3 @@ function* _getUpcastDefinition( model, view, upcastAlso ) {
 		}
 		}
 	}
 	}
 }
 }
-
-/**
- * Base class for conversion helpers.
- */
-export class ConversionHelpers {
-	/**
-	 * Creates `ConversionHelpers` instance.
-	 *
-	 * @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
-	 * module:engine/conversion/upcastdispatcher~UpcastDispatcher>} dispatcher
-	 */
-	constructor( dispatcher ) {
-		this._dispatchers = Array.isArray( dispatcher ) ? dispatcher : [ dispatcher ];
-	}
-
-	/**
-	 * Registers a conversion helper.
-	 *
-	 * **Note**: See full usage example in the `{@link module:engine/conversion/conversion~Conversion#for conversion.for()}`
-	 * method description.
-	 *
-	 * @param {Function} conversionHelper The function to be called on event.
-	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
-	 */
-	add( conversionHelper ) {
-		this._addToDispatchers( conversionHelper );
-
-		return this;
-	}
-
-	/**
-	 * Helper function for the `Conversion` `.add()` method.
-	 *
-	 * Calls `conversionHelper` on each dispatcher from the group specified earlier in the `.for()` call, effectively
-	 * adding converters to all specified dispatchers.
-	 *
-	 * @private
-	 * @param {Function} conversionHelper
-	 */
-	_addToDispatchers( conversionHelper ) {
-		for ( const dispatcher of this._dispatchers ) {
-			conversionHelper( dispatcher );
-		}
-	}
-}

+ 40 - 0
packages/ckeditor5-engine/src/conversion/conversionhelpers.js

@@ -0,0 +1,40 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module engine/conversion/conversionhelpers
+ */
+
+/**
+ * Base class for conversion helpers.
+ */
+export default class ConversionHelpers {
+	/**
+	 * Creates a conversion helpers instance.
+	 *
+	 * @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
+	 * module:engine/conversion/upcastdispatcher~UpcastDispatcher>} dispatchers
+	 */
+	constructor( dispatchers ) {
+		this._dispatchers = dispatchers;
+	}
+
+	/**
+	 * Registers a conversion helper.
+	 *
+	 * **Note**: See full usage example in the `{@link module:engine/conversion/conversion~Conversion#for conversion.for()}`
+	 * method description.
+	 *
+	 * @param {Function} conversionHelper The function to be called on event.
+	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
+	 */
+	add( conversionHelper ) {
+		for ( const dispatcher of this._dispatchers ) {
+			conversionHelper( dispatcher );
+		}
+
+		return this;
+	}
+}

+ 1 - 1
packages/ckeditor5-engine/src/conversion/downcastdispatcher.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 3 - 3
packages/ckeditor5-engine/src/conversion/downcasthelpers.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -9,7 +9,7 @@ import ModelElement from '../model/element';
 
 
 import ViewAttributeElement from '../view/attributeelement';
 import ViewAttributeElement from '../view/attributeelement';
 import DocumentSelection from '../model/documentselection';
 import DocumentSelection from '../model/documentselection';
-import { ConversionHelpers } from './conversion';
+import ConversionHelpers from './conversionhelpers';
 
 
 import log from '@ckeditor/ckeditor5-utils/src/log';
 import log from '@ckeditor/ckeditor5-utils/src/log';
 import { cloneDeep } from 'lodash-es';
 import { cloneDeep } from 'lodash-es';
@@ -23,7 +23,7 @@ import { cloneDeep } from 'lodash-es';
 /**
 /**
  * Downcast conversion helper functions.
  * Downcast conversion helper functions.
  *
  *
- * @extends module:engine/conversion/conversion~ConversionHelpers
+ * @extends module:engine/conversion/conversionhelpers~ConversionHelpers
  */
  */
 export default class DowncastHelpers extends ConversionHelpers {
 export default class DowncastHelpers extends ConversionHelpers {
 	/**
 	/**

+ 1 - 1
packages/ckeditor5-engine/src/conversion/mapper.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/conversion/modelconsumable.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 0 - 0
packages/ckeditor5-engine/src/conversion/upcast-converters.js


+ 2 - 2
packages/ckeditor5-engine/src/conversion/upcastdispatcher.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -263,7 +263,7 @@ export default class UpcastDispatcher {
 	 */
 	 */
 	_splitToAllowedParent( node, modelCursor ) {
 	_splitToAllowedParent( node, modelCursor ) {
 		// Try to find allowed parent.
 		// Try to find allowed parent.
-		const allowedParent = this.conversionApi.schema.findAllowedParent( node, modelCursor );
+		const allowedParent = this.conversionApi.schema.findAllowedParent( modelCursor, node );
 
 
 		// When there is no parent that allows to insert node then return `null`.
 		// When there is no parent that allows to insert node then return `null`.
 		if ( !allowedParent ) {
 		if ( !allowedParent ) {

+ 3 - 3
packages/ckeditor5-engine/src/conversion/upcasthelpers.js

@@ -1,11 +1,11 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
 import Matcher from '../view/matcher';
 import Matcher from '../view/matcher';
 import ModelRange from '../model/range';
 import ModelRange from '../model/range';
-import { ConversionHelpers } from './conversion';
+import ConversionHelpers from './conversionhelpers';
 
 
 import { cloneDeep } from 'lodash-es';
 import { cloneDeep } from 'lodash-es';
 import ModelSelection from '../model/selection';
 import ModelSelection from '../model/selection';
@@ -20,7 +20,7 @@ import ModelSelection from '../model/selection';
 /**
 /**
  * Upcast conversion helper functions.
  * Upcast conversion helper functions.
  *
  *
- * @extends module:engine/conversion/conversion~ConversionHelpers
+ * @extends module:engine/conversion/conversionhelpers~ConversionHelpers
  */
  */
 export default class UpcastHelpers extends ConversionHelpers {
 export default class UpcastHelpers extends ConversionHelpers {
 	/**
 	/**

+ 1 - 1
packages/ckeditor5-engine/src/conversion/viewconsumable.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dataprocessor/basichtmlwriter.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dataprocessor/dataprocessor.jsdoc

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dataprocessor/htmlwriter.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dataprocessor/xmldataprocessor.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dev-utils/enableenginedebug.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dev-utils/operationreplayer.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/dev-utils/view.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/batch.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 27 - 1
packages/ckeditor5-engine/src/model/differ.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -149,6 +149,15 @@ export default class Differ {
 			case 'remove':
 			case 'remove':
 			case 'move':
 			case 'move':
 			case 'reinsert': {
 			case 'reinsert': {
+				// When range is moved to the same position then not mark it as a change.
+				// See: https://github.com/ckeditor/ckeditor5-engine/issues/1664.
+				if (
+					operation.sourcePosition.isEqual( operation.targetPosition ) ||
+					operation.sourcePosition.getShiftedBy( operation.howMany ).isEqual( operation.targetPosition )
+				) {
+					return;
+				}
+
 				const sourceParentInserted = this._isInInsertedElement( operation.sourcePosition.parent );
 				const sourceParentInserted = this._isInInsertedElement( operation.sourcePosition.parent );
 				const targetParentInserted = this._isInInsertedElement( operation.targetPosition.parent );
 				const targetParentInserted = this._isInInsertedElement( operation.targetPosition.parent );
 
 
@@ -292,6 +301,23 @@ export default class Differ {
 		return result;
 		return result;
 	}
 	}
 
 
+	/**
+	 * Returns all markers which changed.
+	 *
+	 * @returns {Array.<Object>}
+	 */
+	getChangedMarkers() {
+		return Array.from( this._changedMarkers ).map( item => (
+			{
+				name: item[ 0 ],
+				data: {
+					oldRange: item[ 1 ].oldRange,
+					newRange: item[ 1 ].newRange
+				}
+			}
+		) );
+	}
+
 	/**
 	/**
 	 * Checks whether some of the buffered changes affect the editor data.
 	 * Checks whether some of the buffered changes affect the editor data.
 	 *
 	 *

+ 58 - 32
packages/ckeditor5-engine/src/model/document.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -46,7 +46,7 @@ export default class Document {
 		 * The {@link module:engine/model/model~Model model} that the document is a part of.
 		 * The {@link module:engine/model/model~Model model} that the document is a part of.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {module:engine/model/model~Model}
+		 * @type {module:engine/model/model~Model}
 		 */
 		 */
 		this.model = model;
 		this.model = model;
 
 
@@ -58,7 +58,7 @@ export default class Document {
 		 * a {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.
 		 * a {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {Number}
+		 * @type {Number}
 		 */
 		 */
 		this.version = 0;
 		this.version = 0;
 
 
@@ -66,7 +66,7 @@ export default class Document {
 		 * The document's history.
 		 * The document's history.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {module:engine/model/history~History}
+		 * @type {module:engine/model/history~History}
 		 */
 		 */
 		this.history = new History( this );
 		this.history = new History( this );
 
 
@@ -74,7 +74,7 @@ export default class Document {
 		 * The selection in this document.
 		 * The selection in this document.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {module:engine/model/documentselection~DocumentSelection}
+		 * @type {module:engine/model/documentselection~DocumentSelection}
 		 */
 		 */
 		this.selection = new DocumentSelection( this );
 		this.selection = new DocumentSelection( this );
 
 
@@ -83,7 +83,7 @@ export default class Document {
 		 * {@link #getRoot} to manipulate it.
 		 * {@link #getRoot} to manipulate it.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {module:utils/collection~Collection}
+		 * @type {module:utils/collection~Collection}
 		 */
 		 */
 		this.roots = new Collection( { idProperty: 'rootName' } );
 		this.roots = new Collection( { idProperty: 'rootName' } );
 
 
@@ -91,7 +91,7 @@ export default class Document {
 		 * The model differ object. Its role is to buffer changes done on the model document and then calculate a diff of those changes.
 		 * The model differ object. Its role is to buffer changes done on the model document and then calculate a diff of those changes.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @member {module:engine/model/differ~Differ}
+		 * @type {module:engine/model/differ~Differ}
 		 */
 		 */
 		this.differ = new Differ( model.markers );
 		this.differ = new Differ( model.markers );
 
 
@@ -99,10 +99,18 @@ export default class Document {
 		 * Post-fixer callbacks registered to the model document.
 		 * Post-fixer callbacks registered to the model document.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @member {Set}
+		 * @type {Set.<Function>}
 		 */
 		 */
 		this._postFixers = new Set();
 		this._postFixers = new Set();
 
 
+		/**
+		 * A boolean indicates whether the selection has changed until
+		 *
+		 * @private
+		 * @type {Boolean}
+		 */
+		this._hasSelectionChangedFromTheLastChangeBlock = false;
+
 		// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
 		// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
 		this.createRoot( '$root', graveyardName );
 		this.createRoot( '$root', graveyardName );
 
 
@@ -144,29 +152,8 @@ export default class Document {
 		}, { priority: 'low' } );
 		}, { priority: 'low' } );
 
 
 		// Listen to selection changes. If selection changed, mark it.
 		// Listen to selection changes. If selection changed, mark it.
-		let hasSelectionChanged = false;
-
 		this.listenTo( this.selection, 'change', () => {
 		this.listenTo( this.selection, 'change', () => {
-			hasSelectionChanged = true;
-		} );
-
-		// 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,
-		// 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 ) => {
-			if ( !this.differ.isEmpty || hasSelectionChanged ) {
-				this._callPostFixers( writer );
-
-				if ( this.differ.hasDataChanges() ) {
-					this.fire( 'change:data', writer.batch );
-				} else {
-					this.fire( 'change', writer.batch );
-				}
-
-				this.differ.reset();
-				hasSelectionChanged = false;
-			}
+			this._hasSelectionChangedFromTheLastChangeBlock = true;
 		} );
 		} );
 
 
 		// Buffer marker changes.
 		// Buffer marker changes.
@@ -253,8 +240,8 @@ export default class Document {
 	}
 	}
 
 
 	/**
 	/**
-	 * Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features that listen to
-	 * the {@link module:engine/model/model~Model#event:_change model's change event} will operate on a correct model state.
+	 * Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
+	 * will operate on a correct model state.
 	 *
 	 *
 	 * An execution of a feature may lead to an incorrect document tree state. The callbacks are used to fix the document tree after
 	 * An execution of a feature may lead to an incorrect document tree state. The callbacks are used to fix the document tree after
 	 * it has changed. Post-fixers are fired just after all changes from the outermost change block were applied but
 	 * it has changed. Post-fixers are fired just after all changes from the outermost change block were applied but
@@ -306,6 +293,44 @@ export default class Document {
 		return json;
 		return json;
 	}
 	}
 
 
+	/**
+	 * 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.
+	 *
+	 * @protected
+	 * @fires change
+	 * @fires change:data
+	 * @param {module:engine/model/writer~Writer} writer The writer on which post-fixers will be called.
+	 */
+	_handleChangeBlock( writer ) {
+		if ( this._hasDocumentChangedFromTheLastChangeBlock() ) {
+			this._callPostFixers( writer );
+
+			if ( this.differ.hasDataChanges() ) {
+				this.fire( 'change:data', writer.batch );
+			} else {
+				this.fire( 'change', writer.batch );
+			}
+
+			this.differ.reset();
+		}
+
+		this._hasSelectionChangedFromTheLastChangeBlock = false;
+	}
+
+	/**
+	 * Returns whether there is a buffered change or if the selection has changed from the last
+	 * {@link module:engine/model/model~Model#enqueueChange `enqueueChange()` block}
+	 * or {@link module:engine/model/model~Model#change `change()` block}.
+	 *
+	 * @protected
+	 * @returns {Boolean} Returns `true` if document has changed from the last `change()` or `enqueueChange()` block.
+	 */
+	_hasDocumentChangedFromTheLastChangeBlock() {
+		return !this.differ.isEmpty || this._hasSelectionChangedFromTheLastChangeBlock;
+	}
+
 	/**
 	/**
 	 * Returns the default root for this document which is either the first root that was added to the document using
 	 * Returns the default root for this document which is either the first root that was added to the document using
 	 * {@link #createRoot} or the {@link #graveyard graveyard root} if no other roots were created.
 	 * {@link #createRoot} or the {@link #graveyard graveyard root} if no other roots were created.
@@ -359,6 +384,7 @@ export default class Document {
 	 * Performs post-fixer loops. Executes post-fixer callbacks as long as none of them has done any changes to the model.
 	 * Performs post-fixer loops. Executes post-fixer callbacks as long as none of them has done any changes to the model.
 	 *
 	 *
 	 * @private
 	 * @private
+	 * @param {module:engine/model/writer~Writer} writer The writer on which post-fixer callbacks will be called.
 	 */
 	 */
 	_callPostFixers( writer ) {
 	_callPostFixers( writer ) {
 		let wasFixed = false;
 		let wasFixed = false;

+ 1 - 1
packages/ckeditor5-engine/src/model/documentfragment.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 41 - 15
packages/ckeditor5-engine/src/model/documentselection.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -359,6 +359,24 @@ export default class DocumentSelection {
 		return this._selection.hasAttribute( key );
 		return this._selection.hasAttribute( key );
 	}
 	}
 
 
+	/**
+	 * Checks whether object is of given type following the convention set by
+	 * {@link module:engine/model/node~Node#is `Node#is()`}.
+	 *
+	 *		const selection = new DocumentSelection( ... );
+	 *
+	 *		selection.is( 'selection' ); // true
+	 *		selection.is( 'documentSelection' ); // true
+	 *		selection.is( 'node' ); // false
+	 *		selection.is( 'element' ); // false
+	 *
+	 * @param {String} type
+	 * @returns {Boolean}
+	 */
+	is( type ) {
+		return type == 'selection' || type == 'documentSelection';
+	}
+
 	/**
 	/**
 	 * Moves {@link module:engine/model/documentselection~DocumentSelection#focus} to the specified location.
 	 * Moves {@link module:engine/model/documentselection~DocumentSelection#focus} to the specified location.
 	 * Should be used only within the {@link module:engine/model/writer~Writer#setSelectionFocus} method.
 	 * Should be used only within the {@link module:engine/model/writer~Writer#setSelectionFocus} method.
@@ -580,7 +598,7 @@ class LiveSelection extends Selection {
 		// @type {Set}
 		// @type {Set}
 		this._overriddenGravityRegister = new Set();
 		this._overriddenGravityRegister = new Set();
 
 
-		// Add events that will ensure selection correctness.
+		// Ensure selection is correct and up to date after each range change.
 		this.on( 'change:range', () => {
 		this.on( 'change:range', () => {
 			for ( const range of this.getRanges() ) {
 			for ( const range of this.getRanges() ) {
 				if ( !this._document._validateSelectionRange( range ) ) {
 				if ( !this._document._validateSelectionRange( range ) ) {
@@ -597,20 +615,22 @@ class LiveSelection extends Selection {
 					);
 					);
 				}
 				}
 			}
 			}
-		} );
 
 
-		this.listenTo( this._document, 'change', ( evt, batch ) => {
-			// Update selection's markers.
 			this._updateMarkers();
 			this._updateMarkers();
-
-			// Update selection's attributes.
 			this._updateAttributes( false );
 			this._updateAttributes( false );
-
-			// Clear selection attributes from element if no longer empty.
-			clearAttributesStoredInElement( this._model, batch );
 		} );
 		} );
 
 
-		this.listenTo( this._model, 'applyOperation', () => {
+		// Update markers data stored by the selection after each marker change.
+		this.listenTo( this._model.markers, 'update', () => this._updateMarkers() );
+
+		// Ensure selection is correct and up to date after each operation.
+		this.listenTo( this._model, 'applyOperation', ( evt, args ) => {
+			const operation = args[ 0 ];
+
+			if ( !operation.isDocumentOperation || operation.type == 'marker' || operation.type == 'rename' || operation.type == 'noop' ) {
+				return;
+			}
+
 			while ( this._fixGraveyardRangesData.length ) {
 			while ( this._fixGraveyardRangesData.length ) {
 				const { liveRange, sourcePosition } = this._fixGraveyardRangesData.shift();
 				const { liveRange, sourcePosition } = this._fixGraveyardRangesData.shift();
 
 
@@ -619,10 +639,17 @@ class LiveSelection extends Selection {
 
 
 			if ( this._hasChangedRange ) {
 			if ( this._hasChangedRange ) {
 				this._hasChangedRange = false;
 				this._hasChangedRange = false;
-
 				this.fire( 'change:range', { directChange: false } );
 				this.fire( 'change:range', { directChange: false } );
 			}
 			}
+
+			this._updateMarkers();
+			this._updateAttributes( false );
 		}, { priority: 'lowest' } );
 		}, { priority: 'lowest' } );
+
+		// Clear selection attributes from element if no longer empty.
+		this.listenTo( this._document, 'change', ( evt, batch ) => {
+			clearAttributesStoredInElement( this._model, batch );
+		} );
 	}
 	}
 
 
 	get isCollapsed() {
 	get isCollapsed() {
@@ -1017,10 +1044,9 @@ class LiveSelection extends Selection {
 					break;
 					break;
 				}
 				}
 
 
-				// This is not an optimal solution because of https://github.com/ckeditor/ckeditor5-engine/issues/454.
-				// It can be done better by using `break;` instead of checking `attrs === null`.
-				if ( value.type == 'text' && attrs === null ) {
+				if ( value.type == 'text' ) {
 					attrs = value.item.getAttributes();
 					attrs = value.item.getAttributes();
+					break;
 				}
 				}
 			}
 			}
 		} else {
 		} else {

+ 3 - 3
packages/ckeditor5-engine/src/model/element.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -89,7 +89,7 @@ export default class Element extends Node {
 	}
 	}
 
 
 	/**
 	/**
-	 * Checks whether given model tree object is of given type.
+	 * Checks whether this model object is of the given type.
 	 *
 	 *
 	 *		obj.name; // 'listItem'
 	 *		obj.name; // 'listItem'
 	 *		obj instanceof Element; // true
 	 *		obj instanceof Element; // true
@@ -100,7 +100,7 @@ export default class Element extends Node {
 	 *		obj.is( 'text' ); // false
 	 *		obj.is( 'text' ); // false
 	 *		obj.is( 'element', 'image' ); // false
 	 *		obj.is( 'element', 'image' ); // false
 	 *
 	 *
-	 * Read more in {@link module:engine/model/node~Node#is}.
+	 * Read more in {@link module:engine/model/node~Node#is `Node#is()`}.
 	 *
 	 *
 	 * @param {String} type Type to check when `name` parameter is present.
 	 * @param {String} type Type to check when `name` parameter is present.
 	 * Otherwise, it acts like the `name` parameter.
 	 * Otherwise, it acts like the `name` parameter.

+ 1 - 1
packages/ckeditor5-engine/src/model/history.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/item.jsdoc

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/liveposition.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/liverange.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 5
packages/ckeditor5-engine/src/model/markercollection.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -305,10 +305,6 @@ mix( MarkerCollection, EmitterMixin );
  * Then, it can be upcasted back to a marker. Again, use {@link module:engine/conversion/upcasthelpers upcast converters} or
  * Then, it can be upcasted back to a marker. Again, use {@link module:engine/conversion/upcasthelpers upcast converters} or
  * attach a custom converter to {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element}.
  * attach a custom converter to {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element}.
  *
  *
- * Another upside of markers is that finding marked part of document is fast and easy. Using attributes to mark some nodes
- * and then trying to find that part of document would require traversing whole document tree. Marker gives instant access
- * to the range which it is marking at the moment.
- *
  * `Marker` instances are created and destroyed only by {@link ~MarkerCollection MarkerCollection}.
  * `Marker` instances are created and destroyed only by {@link ~MarkerCollection MarkerCollection}.
  */
  */
 class Marker {
 class Marker {

+ 5 - 2
packages/ckeditor5-engine/src/model/model.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -695,9 +695,11 @@ export default class Model {
 			const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter );
 			const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter );
 			ret.push( callbackReturnValue );
 			ret.push( callbackReturnValue );
 
 
-			// Fire internal `_change` event.
+			// Fire '_change' event before resetting differ.
 			this.fire( '_change', this._currentWriter );
 			this.fire( '_change', this._currentWriter );
 
 
+			this.document._handleChangeBlock( this._currentWriter );
+
 			this._pendingChanges.shift();
 			this._pendingChanges.shift();
 			this._currentWriter = null;
 			this._currentWriter = null;
 		}
 		}
@@ -713,6 +715,7 @@ export default class Model {
 	 *
 	 *
 	 * **Note:** This is an internal event! Use {@link module:engine/model/document~Document#event:change} instead.
 	 * **Note:** This is an internal event! Use {@link module:engine/model/document~Document#event:change} instead.
 	 *
 	 *
+	 * @deprecated
 	 * @protected
 	 * @protected
 	 * @event _change
 	 * @event _change
 	 * @param {module:engine/model/writer~Writer} writer `Writer` instance that has been used in the change block.
 	 * @param {module:engine/model/writer~Writer} writer `Writer` instance that has been used in the change block.

+ 1 - 1
packages/ckeditor5-engine/src/model/node.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/nodelist.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/attributeoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/detachoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/insertoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/markeroperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/mergeoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/moveoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/nooperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/operation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/operationfactory.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/renameoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/rootattributeoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/splitoperation.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 30 - 7
packages/ckeditor5-engine/src/model/operation/transform.js

@@ -1,3 +1,8 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
 import InsertOperation from './insertoperation';
 import InsertOperation from './insertoperation';
 import AttributeOperation from './attributeoperation';
 import AttributeOperation from './attributeoperation';
 import RenameOperation from './renameoperation';
 import RenameOperation from './renameoperation';
@@ -536,7 +541,7 @@ class ContextFactory {
 						if ( ( affectedLeft || affectedRight ) && !movedRange.containsRange( markerRange ) ) {
 						if ( ( affectedLeft || affectedRight ) && !movedRange.containsRange( markerRange ) ) {
 							this._setRelation( opA, opB, {
 							this._setRelation( opA, opB, {
 								side: affectedLeft ? 'left' : 'right',
 								side: affectedLeft ? 'left' : 'right',
-								offset: affectedLeft ? markerRange.start.offset : markerRange.end.offset
+								path: affectedLeft ? markerRange.start.path.slice() : markerRange.end.path.slice()
 							} );
 							} );
 						}
 						}
 
 
@@ -545,10 +550,17 @@ class ContextFactory {
 
 
 					case MergeOperation: {
 					case MergeOperation: {
 						const wasInLeftElement = markerRange.start.isEqual( opB.targetPosition );
 						const wasInLeftElement = markerRange.start.isEqual( opB.targetPosition );
+						const wasStartBeforeMergedElement = markerRange.start.isEqual( opB.deletionPosition );
+						const wasEndBeforeMergedElement = markerRange.end.isEqual( opB.deletionPosition );
 						const wasInRightElement = markerRange.end.isEqual( opB.sourcePosition );
 						const wasInRightElement = markerRange.end.isEqual( opB.sourcePosition );
 
 
-						if ( wasInLeftElement || wasInRightElement ) {
-							this._setRelation( opA, opB, { wasInLeftElement, wasInRightElement } );
+						if ( wasInLeftElement || wasStartBeforeMergedElement || wasEndBeforeMergedElement || wasInRightElement ) {
+							this._setRelation( opA, opB, {
+								wasInLeftElement,
+								wasStartBeforeMergedElement,
+								wasEndBeforeMergedElement,
+								wasInRightElement
+							} );
 						}
 						}
 
 
 						break;
 						break;
@@ -1117,13 +1129,16 @@ setTransformation( MarkerOperation, MoveOperation, ( a, b, context ) => {
 
 
 	if ( a.newRange ) {
 	if ( a.newRange ) {
 		if ( context.abRelation ) {
 		if ( context.abRelation ) {
+			const aNewRange = Range._createFromRanges( a.newRange._getTransformedByMoveOperation( b ) );
+
 			if ( context.abRelation.side == 'left' && b.targetPosition.isEqual( a.newRange.start ) ) {
 			if ( context.abRelation.side == 'left' && b.targetPosition.isEqual( a.newRange.start ) ) {
-				a.newRange.start.offset = context.abRelation.offset;
-				a.newRange.end.offset += b.howMany;
+				a.newRange.start.path = context.abRelation.path;
+				a.newRange.end = aNewRange.end;
 
 
 				return [ a ];
 				return [ a ];
 			} else if ( context.abRelation.side == 'right' && b.targetPosition.isEqual( a.newRange.end ) ) {
 			} else if ( context.abRelation.side == 'right' && b.targetPosition.isEqual( a.newRange.end ) ) {
-				a.newRange.end.offset = context.abRelation.offset;
+				a.newRange.start = aNewRange.start;
+				a.newRange.end.path = context.abRelation.path;
 
 
 				return [ a ];
 				return [ a ];
 			}
 			}
@@ -1142,12 +1157,20 @@ setTransformation( MarkerOperation, SplitOperation, ( a, b, context ) => {
 
 
 	if ( a.newRange ) {
 	if ( a.newRange ) {
 		if ( context.abRelation ) {
 		if ( context.abRelation ) {
-			if ( a.newRange.start.isEqual( b.splitPosition ) && !context.abRelation.wasInLeftElement ) {
+			const aNewRange = a.newRange._getTransformedBySplitOperation( b );
+
+			if ( a.newRange.start.isEqual( b.splitPosition ) && context.abRelation.wasStartBeforeMergedElement ) {
+				a.newRange.start = Position._createAt( b.insertionPosition );
+			} else if ( a.newRange.start.isEqual( b.splitPosition ) && !context.abRelation.wasInLeftElement ) {
 				a.newRange.start = Position._createAt( b.moveTargetPosition );
 				a.newRange.start = Position._createAt( b.moveTargetPosition );
 			}
 			}
 
 
 			if ( a.newRange.end.isEqual( b.splitPosition ) && context.abRelation.wasInRightElement ) {
 			if ( a.newRange.end.isEqual( b.splitPosition ) && context.abRelation.wasInRightElement ) {
 				a.newRange.end = Position._createAt( b.moveTargetPosition );
 				a.newRange.end = Position._createAt( b.moveTargetPosition );
+			} else if ( a.newRange.end.isEqual( b.splitPosition ) && context.abRelation.wasEndBeforeMergedElement ) {
+				a.newRange.end = Position._createAt( b.insertionPosition );
+			} else {
+				a.newRange.end = aNewRange.end;
 			}
 			}
 
 
 			return [ a ];
 			return [ a ];

+ 1 - 1
packages/ckeditor5-engine/src/model/operation/utils.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/position.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/range.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/rootelement.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 32 - 6
packages/ckeditor5-engine/src/model/schema.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -64,7 +64,25 @@ export default class Schema {
 	 */
 	 */
 	register( itemName, definition ) {
 	register( itemName, definition ) {
 		if ( this._sourceDefinitions[ itemName ] ) {
 		if ( this._sourceDefinitions[ itemName ] ) {
-			// TODO docs
+			/**
+			 * A single item cannot be registered twice in the schema.
+			 *
+			 * This situation may happen when:
+			 *
+			 * * Two or more plugins called {@link #register `register()`} with the same name. This will usually mean that
+			 * there is a collision between plugins which try to use the same element in the model. Unfortunately,
+			 * the only way to solve this is by modifying one of these plugins to use a unique model element name.
+			 * * A single plugin was loaded twice. This happens when it is installed by npm/yarn in two versions
+			 * and usually means one or more of the following issues:
+			 *     * a version mismatch (two of your dependencies require two different versions of this plugin),
+			 *     * incorrect imports (this plugin is somehow imported twice in a way which confuses webpack),
+			 *     * mess in `node_modules/` (`rm -rf node_modules/` may help).
+			 *
+			 * **Note:** Check the logged `itemName` to better understand which plugin was duplicated/conflicting.
+			 *
+			 * @param itemName The name of the model element that is being registered twice.
+			 * @error schema-cannot-register-item-twice
+			 */
 			throw new CKEditorError( 'schema-cannot-register-item-twice: A single item cannot be registered twice in the schema.', {
 			throw new CKEditorError( 'schema-cannot-register-item-twice: A single item cannot be registered twice in the schema.', {
 				itemName
 				itemName
 			} );
 			} );
@@ -103,7 +121,15 @@ export default class Schema {
 	 */
 	 */
 	extend( itemName, definition ) {
 	extend( itemName, definition ) {
 		if ( !this._sourceDefinitions[ itemName ] ) {
 		if ( !this._sourceDefinitions[ itemName ] ) {
-			// TODO docs
+			/**
+			 * Cannot extend an item which was not registered yet.
+			 *
+			 * This error happens when a plugin tries to extend the schema definition of an item which was not
+			 * {@link #register registered} yet.
+			 *
+			 * @param itemName The name of the model element which is being extended.
+			 * @error schema-cannot-register-item-twice
+			 */
 			throw new CKEditorError( 'schema-cannot-extend-missing-item: Cannot extend an item which was not registered yet.', {
 			throw new CKEditorError( 'schema-cannot-extend-missing-item: Cannot extend an item which was not registered yet.', {
 				itemName
 				itemName
 			} );
 			} );
@@ -395,7 +421,7 @@ export default class Schema {
 	 * Example:
 	 * Example:
 	 *
 	 *
 	 *		// Disallow bold on $text inside heading1.
 	 *		// Disallow bold on $text inside heading1.
-	 *		schema.addChildCheck( ( context, attributeName ) => {
+	 *		schema.addAttributeCheck( ( context, attributeName ) => {
 	 *			if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
 	 *			if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
 	 *				return false;
 	 *				return false;
 	 *			}
 	 *			}
@@ -588,11 +614,11 @@ export default class Schema {
 	 * as long as {@link module:engine/model/schema~Schema#isLimit limit element},
 	 * as long as {@link module:engine/model/schema~Schema#isLimit limit element},
 	 * {@link module:engine/model/schema~Schema#isObject object element} or top-most ancestor won't be reached.
 	 * {@link module:engine/model/schema~Schema#isObject object element} or top-most ancestor won't be reached.
 	 *
 	 *
-	 * @params {module:engine/model/node~Node|String} node Node for which allowed parent should be found or its name.
 	 * @params {module:engine/model/position~Position} position Position from searching will start.
 	 * @params {module:engine/model/position~Position} position Position from searching will start.
+	 * @params {module:engine/model/node~Node|String} node Node for which allowed parent should be found or its name.
 	 * @returns {module:engine/model/element~Element|null} element Allowed parent or null if nothing was found.
 	 * @returns {module:engine/model/element~Element|null} element Allowed parent or null if nothing was found.
 	 */
 	 */
-	findAllowedParent( node, position ) {
+	findAllowedParent( position, node ) {
 		let parent = position.parent;
 		let parent = position.parent;
 
 
 		while ( parent ) {
 		while ( parent ) {

+ 18 - 1
packages/ckeditor5-engine/src/model/selection.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -608,6 +608,23 @@ export default class Selection {
 		return ( nodeAfterStart instanceof Element && nodeAfterStart == nodeBeforeEnd ) ? nodeAfterStart : null;
 		return ( nodeAfterStart instanceof Element && nodeAfterStart == nodeBeforeEnd ) ? nodeAfterStart : null;
 	}
 	}
 
 
+	/**
+	 * Checks whether object is of given type following the convention set by
+	 * {@link module:engine/model/node~Node#is `Node#is()`}.
+	 *
+	 *		const selection = new Selection( ... );
+	 *
+	 *		selection.is( 'selection' ); // true
+	 *		selection.is( 'node' ); // false
+	 *		selection.is( 'element' ); // false
+	 *
+	 * @param {String} type
+	 * @returns {Boolean}
+	 */
+	is( type ) {
+		return type == 'selection';
+	}
+
 	/**
 	/**
 	 * Gets elements of type "block" touched by the selection.
 	 * Gets elements of type "block" touched by the selection.
 	 *
 	 *

+ 1 - 1
packages/ckeditor5-engine/src/model/text.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/textproxy.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/treewalker.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/deletecontent.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/getselectedcontent.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/insertcontent.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/modifyselection.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/selection-post-fixer.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 5 - 8
packages/ckeditor5-engine/src/model/writer.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -504,13 +504,12 @@ export default class Writer {
 		this._assertWriterUsedCorrectly();
 		this._assertWriterUsedCorrectly();
 
 
 		const rangeToRemove = itemOrRange instanceof Range ? itemOrRange : Range._createOn( itemOrRange );
 		const rangeToRemove = itemOrRange instanceof Range ? itemOrRange : Range._createOn( itemOrRange );
-
-		// If part of the marker is removed, create additional marker operation for undo purposes.
-		this._addOperationForAffectedMarkers( 'move', rangeToRemove );
-
 		const ranges = rangeToRemove.getMinimalFlatRanges().reverse();
 		const ranges = rangeToRemove.getMinimalFlatRanges().reverse();
 
 
 		for ( const flat of ranges ) {
 		for ( const flat of ranges ) {
+			// If part of the marker is removed, create additional marker operation for undo purposes.
+			this._addOperationForAffectedMarkers( 'move', flat );
+
 			applyRemoveOperation( flat.start, flat.end.offset - flat.start.offset, this.batch, this.model );
 			applyRemoveOperation( flat.start, flat.end.offset - flat.start.offset, this.batch, this.model );
 		}
 		}
 	}
 	}
@@ -1318,13 +1317,11 @@ export default class Writer {
 			let isAffected = false;
 			let isAffected = false;
 
 
 			if ( type == 'move' ) {
 			if ( type == 'move' ) {
-				const intersecting =
+				isAffected =
 					positionOrRange.containsPosition( markerRange.start ) ||
 					positionOrRange.containsPosition( markerRange.start ) ||
 					positionOrRange.start.isEqual( markerRange.start ) ||
 					positionOrRange.start.isEqual( markerRange.start ) ||
 					positionOrRange.containsPosition( markerRange.end ) ||
 					positionOrRange.containsPosition( markerRange.end ) ||
 					positionOrRange.end.isEqual( markerRange.end );
 					positionOrRange.end.isEqual( markerRange.end );
-
-				isAffected = intersecting && !positionOrRange.containsRange( markerRange );
 			} else {
 			} else {
 				// if type == 'merge'.
 				// if type == 'merge'.
 				const elementBefore = positionOrRange.nodeBefore;
 				const elementBefore = positionOrRange.nodeBefore;

+ 1 - 1
packages/ckeditor5-engine/src/utils/bindtwostepcarettoattribute.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/attributeelement.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/containerelement.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 4 - 1
packages/ckeditor5-engine/src/view/document.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -115,6 +115,9 @@ export default class Document {
 	 * As a parameter, a post-fixer callback receives a {@link module:engine/view/downcastwriter~DowncastWriter downcast writer}
 	 * As a parameter, a post-fixer callback receives a {@link module:engine/view/downcastwriter~DowncastWriter downcast writer}
 	 * instance connected with the executed changes block.
 	 * instance connected with the executed changes block.
 	 *
 	 *
+	 * Note that registering a post-fixer won't re-render the editor's view. If the view should change after registering the post-fixer then
+	 * it should be done manually calling `view.forceRender();`.
+	 *
 	 * @param {Function} postFixer
 	 * @param {Function} postFixer
 	 */
 	 */
 	registerPostFixer( postFixer ) {
 	registerPostFixer( postFixer ) {

+ 1 - 1
packages/ckeditor5-engine/src/view/documentfragment.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 19 - 1
packages/ckeditor5-engine/src/view/documentselection.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -274,6 +274,24 @@ export default class DocumentSelection {
 		return this._selection.isSimilar( otherSelection );
 		return this._selection.isSimilar( otherSelection );
 	}
 	}
 
 
+	/**
+	 * Checks whether object is of given type following the convention set by
+	 * {@link module:engine/view/node~Node#is `Node#is()`}.
+	 *
+	 *		const selection = new DocumentSelection( ... );
+	 *
+	 *		selection.is( 'selection' ); // true
+	 *		selection.is( 'documentSelection' ); // true
+	 *		selection.is( 'node' ); // false
+	 *		selection.is( 'element' ); // false
+	 *
+	 * @param {String} type
+	 * @returns {Boolean}
+	 */
+	is( type ) {
+		return type == 'selection' || type == 'documentSelection';
+	}
+
 	/**
 	/**
 	 * Sets this selection's ranges and direction to the specified location based on the given
 	 * Sets this selection's ranges and direction to the specified location based on the given
 	 * {@link module:engine/view/selection~Selectable selectable}.
 	 * {@link module:engine/view/selection~Selectable selectable}.

+ 1 - 1
packages/ckeditor5-engine/src/view/domconverter.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/downcastwriter.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/editableelement.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 10 - 8
packages/ckeditor5-engine/src/view/element.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -147,9 +147,15 @@ export default class Element extends Node {
 	}
 	}
 
 
 	/**
 	/**
-	 * Checks whether given view tree object is of given type.
+	 * Checks whether this view object is of the given type.
 	 *
 	 *
-	 * Read more in {@link module:engine/view/node~Node#is}.
+	 *		obj.is( 'element' ); // true
+	 *		obj.is( 'li' ); // true
+	 *		obj.is( 'element', 'li' ); // true
+	 *		obj.is( 'text' ); // false
+	 *		obj.is( 'element', 'img' ); // false
+	 *
+	 * Read more in {@link module:engine/view/node~Node#is `Node#is()`}.
 	 *
 	 *
 	 * @param {String} type
 	 * @param {String} type
 	 * @param {String} [name] Element name.
 	 * @param {String} [name] Element name.
@@ -206,11 +212,7 @@ export default class Element extends Node {
 			yield 'style';
 			yield 'style';
 		}
 		}
 
 
-		// This is not an optimal solution because of https://github.com/ckeditor/ckeditor5-engine/issues/454.
-		// It can be simplified to `yield* this._attrs.keys();`.
-		for ( const key of this._attrs.keys() ) {
-			yield key;
-		}
+		yield* this._attrs.keys();
 	}
 	}
 
 
 	/**
 	/**

+ 1 - 1
packages/ckeditor5-engine/src/view/elementdefinition.jsdoc

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/emptyelement.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/filler.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/item.jsdoc

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/matcher.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 2 - 3
packages/ckeditor5-engine/src/view/node.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -287,7 +287,7 @@ export default class Node {
 	}
 	}
 
 
 	/**
 	/**
-	 * Checks whether given view tree object is of given type.
+	 * Checks whether this view object is of the given type.
 	 *
 	 *
 	 * This method is useful when processing view tree objects that are of unknown type. For example, a function
 	 * This method is useful when processing view tree objects that are of unknown type. For example, a function
 	 * may return {@link module:engine/view/documentfragment~DocumentFragment} or {@link module:engine/view/node~Node}
 	 * may return {@link module:engine/view/documentfragment~DocumentFragment} or {@link module:engine/view/node~Node}
@@ -300,7 +300,6 @@ export default class Node {
 	 *		obj.is( 'p' ); // shortcut for obj.is( 'element', 'p' )
 	 *		obj.is( 'p' ); // shortcut for obj.is( 'element', 'p' )
 	 *		obj.is( 'text' ); // true for text node, false for element and document fragment
 	 *		obj.is( 'text' ); // true for text node, false for element and document fragment
 	 *
 	 *
-	 * @method #is
 	 * @param {'element'|'containerElement'|'attributeElement'|'emptyElement'|'uiElement'|
 	 * @param {'element'|'containerElement'|'attributeElement'|'emptyElement'|'uiElement'|
 	 * 'rootElement'|'documentFragment'|'text'|'textProxy'} type
 	 * 'rootElement'|'documentFragment'|'text'|'textProxy'} type
 	 * @returns {Boolean}
 	 * @returns {Boolean}

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/clickobserver.js

@@ -1,6 +1,6 @@
 
 
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/compositionobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/domeventdata.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/domeventobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/fakeselectionobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 3 - 3
packages/ckeditor5-engine/src/view/observer/focusobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -37,7 +37,7 @@ export default class FocusObserver extends DomEventObserver {
 			// overwrite new DOM selection with selection from the view.
 			// overwrite new DOM selection with selection from the view.
 			// See https://github.com/ckeditor/ckeditor5-engine/issues/795 for more details.
 			// See https://github.com/ckeditor/ckeditor5-engine/issues/795 for more details.
 			// Long timeout is needed to solve #676 and https://github.com/ckeditor/ckeditor5-engine/issues/1157 issues.
 			// Long timeout is needed to solve #676 and https://github.com/ckeditor/ckeditor5-engine/issues/1157 issues.
-			this._renderTimeoutId = setTimeout( () => view.render(), 50 );
+			this._renderTimeoutId = setTimeout( () => view.forceRender(), 50 );
 		} );
 		} );
 
 
 		document.on( 'blur', ( evt, data ) => {
 		document.on( 'blur', ( evt, data ) => {
@@ -47,7 +47,7 @@ export default class FocusObserver extends DomEventObserver {
 				document.isFocused = false;
 				document.isFocused = false;
 
 
 				// Re-render the document to update view elements.
 				// Re-render the document to update view elements.
-				view.render();
+				view.forceRender();
 			}
 			}
 		} );
 		} );
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/keyobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/observer/mouseobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 3 - 3
packages/ckeditor5-engine/src/view/observer/mutationobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -55,7 +55,7 @@ export default class MutationObserver extends Observer {
 		this.domConverter = view.domConverter;
 		this.domConverter = view.domConverter;
 
 
 		/**
 		/**
-		 * Reference to the {@link module:engine/view/view~View#renderer}.
+		 * Reference to the {@link module:engine/view/view~View#_renderer}.
 		 *
 		 *
 		 * @member {module:engine/view/renderer~Renderer}
 		 * @member {module:engine/view/renderer~Renderer}
 		 */
 		 */
@@ -248,7 +248,7 @@ export default class MutationObserver extends Observer {
 
 
 		// If nothing changes on `mutations` event, at this point we have "dirty DOM" (changed) and de-synched
 		// If nothing changes on `mutations` event, at this point we have "dirty DOM" (changed) and de-synched
 		// view (which has not been changed). In order to "reset DOM" we render the view again.
 		// view (which has not been changed). In order to "reset DOM" we render the view again.
-		this.view.render();
+		this.view.forceRender();
 
 
 		function sameNodes( child1, child2 ) {
 		function sameNodes( child1, child2 ) {
 			// First level of comparison (array of children vs array of children) – use the Lodash's default behavior.
 			// First level of comparison (array of children vs array of children) – use the Lodash's default behavior.

+ 3 - 3
packages/ckeditor5-engine/src/view/observer/observer.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -52,7 +52,7 @@ export default class Observer {
 
 
 	/**
 	/**
 	 * Enables the observer. This method is called when the observer is registered to the
 	 * Enables the observer. This method is called when the observer is registered to the
-	 * {@link module:engine/view/view~View} and after {@link module:engine/view/view~View#render rendering}
+	 * {@link module:engine/view/view~View} and after {@link module:engine/view/view~View#forceRender rendering}
 	 * (all observers are {@link #disable disabled} before rendering).
 	 * (all observers are {@link #disable disabled} before rendering).
 	 *
 	 *
 	 * A typical use case for disabling observers is that mutation observers need to be disabled for the rendering.
 	 * A typical use case for disabling observers is that mutation observers need to be disabled for the rendering.
@@ -66,7 +66,7 @@ export default class Observer {
 
 
 	/**
 	/**
 	 * Disables the observer. This method is called before
 	 * Disables the observer. This method is called before
-	 * {@link module:engine/view/view~View#render rendering} to prevent firing events during rendering.
+	 * {@link module:engine/view/view~View#forceRender rendering} to prevent firing events during rendering.
 	 *
 	 *
 	 * @see module:engine/view/observer/observer~Observer#enable
 	 * @see module:engine/view/observer/observer~Observer#enable
 	 */
 	 */

+ 2 - 2
packages/ckeditor5-engine/src/view/observer/selectionobserver.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -166,7 +166,7 @@ export default class SelectionObserver extends Observer {
 		if ( this.selection.isSimilar( newViewSelection ) ) {
 		if ( this.selection.isSimilar( newViewSelection ) ) {
 			// If selection was equal and we are at this point of algorithm, it means that it was incorrect.
 			// If selection was equal and we are at this point of algorithm, it means that it was incorrect.
 			// Just re-render it, no need to fire any events, etc.
 			// Just re-render it, no need to fire any events, etc.
-			this.view.render();
+			this.view.forceRender();
 		} else {
 		} else {
 			const data = {
 			const data = {
 				oldSelection: this.selection,
 				oldSelection: this.selection,

+ 192 - 85
packages/ckeditor5-engine/src/view/placeholder.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -13,135 +13,242 @@ import '../../theme/placeholder.css';
 const documentPlaceholders = new WeakMap();
 const documentPlaceholders = new WeakMap();
 
 
 /**
 /**
- * Attaches placeholder to provided element and updates it's visibility. To change placeholder simply call this method
- * once again with new parameters.
+ * A helper that enables a placeholder on the provided view element (also updates its visibility).
+ * The placeholder is a CSS pseudo–element (with a text content) attached to the element.
  *
  *
- * @param {module:engine/view/view~View} view View controller.
- * @param {module:engine/view/element~Element} element Element to attach placeholder to.
- * @param {String} placeholderText Placeholder text to use.
- * @param {Function} [checkFunction] If provided it will be called before checking if placeholder should be displayed.
- * If function returns `false` placeholder will not be showed.
+ * To change the placeholder text, simply call this method again with new options.
+ *
+ * To disable the placeholder, use {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} helper.
+ *
+ * @param {Object} [options] Configuration options of the placeholder.
+ * @param {module:engine/view/view~View} options.view Editing view instance.
+ * @param {module:engine/view/element~Element} options.element Element that will gain a placeholder.
+ * See `options.isDirectHost` to learn more.
+ * @param {String} options.text Placeholder text.
+ * @param {Boolean} [options.isDirectHost=true] If set `false`, the placeholder will not be enabled directly
+ * in the passed `element` but in one of its children (selected automatically, i.e. a first empty child element).
+ * Useful when attaching placeholders to elements that can host other elements (not just text), for instance,
+ * editable root elements.
  */
  */
-export function attachPlaceholder( view, element, placeholderText, checkFunction ) {
-	const document = view.document;
+export function enablePlaceholder( options ) {
+	const { view, element, text, isDirectHost = true } = options;
+	const doc = view.document;
 
 
-	// Single listener per document.
-	if ( !documentPlaceholders.has( document ) ) {
-		documentPlaceholders.set( document, new Map() );
+	// Use a single a single post fixer per—document to update all placeholders.
+	if ( !documentPlaceholders.has( doc ) ) {
+		documentPlaceholders.set( doc, new Map() );
 
 
-		// Create view post-fixer that will add placeholder where needed.
-		document.registerPostFixer( writer => updateAllPlaceholders( document, writer ) );
+		// If a post-fixer callback makes a change, it should return `true` so other post–fixers
+		// can re–evaluate the document again.
+		doc.registerPostFixer( writer => updateDocumentPlaceholders( doc, writer ) );
 	}
 	}
 
 
-	// Store information about element with placeholder.
-	documentPlaceholders.get( document ).set( element, {
-		placeholderText,
-		checkFunction
+	// Store information about the element placeholder under its document.
+	documentPlaceholders.get( doc ).set( element, {
+		text,
+		isDirectHost
 	} );
 	} );
 
 
-	// Update view right away.
-	view.render();
+	// Update the placeholders right away.
+	view.change( writer => updateDocumentPlaceholders( doc, writer ) );
 }
 }
 
 
 /**
 /**
- * Removes placeholder functionality from given element.
+ * Disables the placeholder functionality from a given element.
+ *
+ * See {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} to learn more.
  *
  *
  * @param {module:engine/view/view~View} view
  * @param {module:engine/view/view~View} view
  * @param {module:engine/view/element~Element} element
  * @param {module:engine/view/element~Element} element
  */
  */
-export function detachPlaceholder( view, element ) {
+export function disablePlaceholder( view, element ) {
 	const doc = element.document;
 	const doc = element.document;
 
 
 	view.change( writer => {
 	view.change( writer => {
-		if ( documentPlaceholders.has( doc ) ) {
-			documentPlaceholders.get( doc ).delete( element );
+		if ( !documentPlaceholders.has( doc ) ) {
+			return;
 		}
 		}
 
 
-		writer.removeClass( 'ck-placeholder', element );
-		writer.removeAttribute( 'data-placeholder', element );
+		const placeholders = documentPlaceholders.get( doc );
+		const config = placeholders.get( element );
+
+		writer.removeAttribute( 'data-placeholder', config.hostElement );
+		hidePlaceholder( writer, config.hostElement );
+
+		placeholders.delete( element );
 	} );
 	} );
 }
 }
 
 
-// Updates all placeholders of given document.
+/**
+ * Shows a placeholder in the provided element by changing related attributes and CSS classes.
+ *
+ * **Note**: This helper will not update the placeholder visibility nor manage the
+ * it in any way in the future. What it does is a one–time state change of an element. Use
+ * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} and
+ * {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} for full
+ * placeholder functionality.
+ *
+ * **Note**: This helper will blindly show the placeholder directly in the root editable element if
+ * one is passed, which could result in a visual clash if the editable element has some children
+ * (for instance, an empty paragraph). Use {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`}
+ * in that case or make sure the correct element is passed to the helper.
+ *
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer
+ * @param {module:engine/view/element~Element} element
+ * @returns {Boolean} `true`, if any changes were made to the `element`.
+ */
+export function showPlaceholder( writer, element ) {
+	if ( !element.hasClass( 'ck-placeholder' ) ) {
+		writer.addClass( 'ck-placeholder', element );
+
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Hides a placeholder in the element by changing related attributes and CSS classes.
+ *
+ * **Note**: This helper will not update the placeholder visibility nor manage the
+ * it in any way in the future. What it does is a one–time state change of an element. Use
+ * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} and
+ * {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} for full
+ * placeholder functionality.
+ *
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer
+ * @param {module:engine/view/element~Element} element
+ * @returns {Boolean} `true`, if any changes were made to the `element`.
+ */
+export function hidePlaceholder( writer, element ) {
+	if ( element.hasClass( 'ck-placeholder' ) ) {
+		writer.removeClass( 'ck-placeholder', element );
+
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Checks if a placeholder should be displayed in the element.
+ *
+ * **Note**: This helper will blindly check the possibility of showing a placeholder directly in the
+ * root editable element if one is passed, which may not be the expected result. If an element can
+ * host other elements (not just text), most likely one of its children should be checked instead
+ * because it will be the final host for the placeholder. Use
+ * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} in that case or make
+ * sure the correct element is passed to the helper.
+ *
+ * @param {module:engine/view/downcastwriter~DowncastWriter} writer
+ * @param {module:engine/view/element~Element} element
+ * @param {String} text
+ * @returns {Boolean}
+ */
+export function needsPlaceholder( element ) {
+	const doc = element.document;
+
+	// The element was removed from document.
+	if ( !doc ) {
+		return false;
+	}
+
+	// The element is empty only as long as it contains nothing but uiElements.
+	const isEmptyish = !Array.from( element.getChildren() )
+		.some( element => !element.is( 'uiElement' ) );
+
+	// If the element is empty and the document is blurred.
+	if ( !doc.isFocused && isEmptyish ) {
+		return true;
+	}
+
+	const viewSelection = doc.selection;
+	const selectionAnchor = viewSelection.anchor;
+
+	// If document is focused and the element is empty but the selection is not anchored inside it.
+	if ( isEmptyish && selectionAnchor && selectionAnchor.parent !== element ) {
+		return true;
+	}
+
+	return false;
+}
+
+// Updates all placeholders associated with a document in a post–fixer callback.
 //
 //
 // @private
 // @private
-// @param {module:engine/view/document~Document} view
+// @param { module:engine/model/document~Document} doc
 // @param {module:engine/view/downcastwriter~DowncastWriter} writer
 // @param {module:engine/view/downcastwriter~DowncastWriter} writer
-function updateAllPlaceholders( document, writer ) {
-	const placeholders = documentPlaceholders.get( document );
-	let changed = false;
-
-	for ( const [ element, info ] of placeholders ) {
-		if ( updateSinglePlaceholder( writer, element, info ) ) {
-			changed = true;
+// @returns {Boolean} True if any changes were made to the view document.
+function updateDocumentPlaceholders( doc, writer ) {
+	const placeholders = documentPlaceholders.get( doc );
+	let wasViewModified = false;
+
+	for ( const [ element, config ] of placeholders ) {
+		if ( updatePlaceholder( writer, element, config ) ) {
+			wasViewModified = true;
 		}
 		}
 	}
 	}
 
 
-	return changed;
+	return wasViewModified;
 }
 }
 
 
-// Updates placeholder class of given element.
+// Updates a single placeholder in a post–fixer callback.
 //
 //
 // @private
 // @private
 // @param {module:engine/view/downcastwriter~DowncastWriter} writer
 // @param {module:engine/view/downcastwriter~DowncastWriter} writer
 // @param {module:engine/view/element~Element} element
 // @param {module:engine/view/element~Element} element
-// @param {Object} info
-function updateSinglePlaceholder( writer, element, info ) {
-	const document = element.document;
-	const text = info.placeholderText;
-	let changed = false;
-
-	// Element was removed from document.
-	if ( !document ) {
+// @param {Object} config Configuration of the placeholder
+// @param {String} config.text
+// @param {Boolean} config.isDirectHost
+// @returns {Boolean} True if any changes were made to the view document.
+function updatePlaceholder( writer, element, config ) {
+	const { text, isDirectHost } = config;
+	const hostElement = isDirectHost ? element : getChildPlaceholderHostSubstitute( element );
+	let wasViewModified = false;
+
+	// When not a direct host, it could happen that there is no child element
+	// capable of displaying a placeholder.
+	if ( !hostElement ) {
 		return false;
 		return false;
 	}
 	}
 
 
-	// Update data attribute if needed.
-	if ( element.getAttribute( 'data-placeholder' ) !== text ) {
-		writer.setAttribute( 'data-placeholder', text, element );
-		changed = true;
-	}
-
-	const viewSelection = document.selection;
-	const anchor = viewSelection.anchor;
-	const checkFunction = info.checkFunction;
-
-	// If checkFunction is provided and returns false - remove placeholder.
-	if ( checkFunction && !checkFunction() ) {
-		if ( element.hasClass( 'ck-placeholder' ) ) {
-			writer.removeClass( 'ck-placeholder', element );
-			changed = true;
-		}
+	// Cache the host element. It will be necessary for disablePlaceholder() to know
+	// which element should have class and attribute removed because, depending on
+	// the config.isDirectHost value, it could be the element or one of its descendants.
+	config.hostElement = hostElement;
 
 
-		return changed;
+	// This may be necessary when updating the placeholder text to something else.
+	if ( hostElement.getAttribute( 'data-placeholder' ) !== text ) {
+		writer.setAttribute( 'data-placeholder', text, hostElement );
+		wasViewModified = true;
 	}
 	}
 
 
-	// Element is empty for placeholder purposes when it has no children or only ui elements.
-	// This check is taken from `view.ContainerElement#getFillerOffset`.
-	const isEmptyish = !Array.from( element.getChildren() ).some( element => !element.is( 'uiElement' ) );
-
-	// If element is empty and editor is blurred.
-	if ( !document.isFocused && isEmptyish ) {
-		if ( !element.hasClass( 'ck-placeholder' ) ) {
-			writer.addClass( 'ck-placeholder', element );
-			changed = true;
+	if ( needsPlaceholder( hostElement ) ) {
+		if ( showPlaceholder( writer, hostElement ) ) {
+			wasViewModified = true;
 		}
 		}
-
-		return changed;
+	} else if ( hidePlaceholder( writer, hostElement ) ) {
+		wasViewModified = true;
 	}
 	}
 
 
-	// It there are no child elements and selection is not placed inside element.
-	if ( isEmptyish && anchor && anchor.parent !== element ) {
-		if ( !element.hasClass( 'ck-placeholder' ) ) {
-			writer.addClass( 'ck-placeholder', element );
-			changed = true;
-		}
-	} else {
-		if ( element.hasClass( 'ck-placeholder' ) ) {
-			writer.removeClass( 'ck-placeholder', element );
-			changed = true;
+	return wasViewModified;
+}
+
+// Gets a child element capable of displaying a placeholder if a parent element can host more
+// than just text (for instance, when it is a root editable element). The child element
+// can then be used in other placeholder helpers as a substitute of its parent.
+//
+// @private
+// @param {module:engine/view/element~Element} parent
+// @returns {module:engine/view/element~Element|null}
+function getChildPlaceholderHostSubstitute( parent ) {
+	if ( parent.childCount === 1 ) {
+		const firstChild = parent.getChild( 0 );
+
+		if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) ) {
+			return firstChild;
 		}
 		}
 	}
 	}
 
 
-	return changed;
+	return null;
 }
 }

+ 1 - 1
packages/ckeditor5-engine/src/view/position.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/range.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/renderer.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 1 - 1
packages/ckeditor5-engine/src/view/rooteditableelement.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

+ 18 - 1
packages/ckeditor5-engine/src/view/selection.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
@@ -577,6 +577,23 @@ export default class Selection {
 		this.fire( 'change' );
 		this.fire( 'change' );
 	}
 	}
 
 
+	/**
+	 * Checks whether object is of given type following the convention set by
+	 * {@link module:engine/view/node~Node#is `Node#is()`}.
+	 *
+	 *		const selection = new Selection( ... );
+	 *
+	 *		selection.is( 'selection' ); // true
+	 *		selection.is( 'node' ); // false
+	 *		selection.is( 'element' ); // false
+	 *
+	 * @param {String} type
+	 * @returns {Boolean}
+	 */
+	is( type ) {
+		return type == 'selection';
+	}
+
 	/**
 	/**
 	 * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
 	 * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
 	 * is treated like the last added range and is used to set {@link #anchor anchor} and {@link #focus focus}.
 	 * is treated like the last added range and is used to set {@link #anchor anchor} and {@link #focus focus}.

+ 1 - 1
packages/ckeditor5-engine/src/view/text.js

@@ -1,5 +1,5 @@
 /**
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 

Некоторые файлы не были показаны из-за большого количества измененных файлов