Przeglądaj źródła

Merge pull request #128 from ckeditor/t/120

T/120
Piotr Jasiun 10 lat temu
rodzic
commit
d432e34621
100 zmienionych plików z 5429 dodań i 5318 usunięć
  1. 1 5
      packages/ckeditor5-engine/.jshintrc
  2. 12 3
      packages/ckeditor5-engine/gruntfile.js
  3. 1 1
      packages/ckeditor5-engine/package.json
  4. 58 68
      packages/ckeditor5-engine/src/ckeditor.js
  5. 27 31
      packages/ckeditor5-engine/src/ckeditorerror.js
  6. 213 213
      packages/ckeditor5-engine/src/collection.js
  7. 154 155
      packages/ckeditor5-engine/src/config.js
  8. 6 8
      packages/ckeditor5-engine/src/creator.js
  9. 119 156
      packages/ckeditor5-engine/src/editor.js
  10. 16 24
      packages/ckeditor5-engine/src/editorconfig.js
  11. 247 246
      packages/ckeditor5-engine/src/emittermixin.js
  12. 30 32
      packages/ckeditor5-engine/src/eventinfo.js
  13. 5 0
      packages/ckeditor5-engine/src/lib/lodash/array.js
  14. 28 0
      packages/ckeditor5-engine/src/lib/lodash/array/last.js
  15. 5 0
      packages/ckeditor5-engine/src/lib/lodash/function.js
  16. 58 0
      packages/ckeditor5-engine/src/lib/lodash/function/restParam.js
  17. 20 0
      packages/ckeditor5-engine/src/lib/lodash/internal/arrayCopy.js
  18. 22 0
      packages/ckeditor5-engine/src/lib/lodash/internal/arrayEach.js
  19. 23 0
      packages/ckeditor5-engine/src/lib/lodash/internal/arraySome.js
  20. 32 0
      packages/ckeditor5-engine/src/lib/lodash/internal/assignWith.js
  21. 19 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseAssign.js
  22. 128 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseClone.js
  23. 23 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseCopy.js
  24. 17 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseFor.js
  25. 17 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseForIn.js
  26. 17 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseForOwn.js
  27. 28 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseIsEqual.js
  28. 102 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseIsEqualDeep.js
  29. 14 0
      packages/ckeditor5-engine/src/lib/lodash/internal/baseProperty.js
  30. 39 0
      packages/ckeditor5-engine/src/lib/lodash/internal/bindCallback.js
  31. 22 0
      packages/ckeditor5-engine/src/lib/lodash/internal/bufferClone.js
  32. 41 0
      packages/ckeditor5-engine/src/lib/lodash/internal/createAssigner.js
  33. 27 0
      packages/ckeditor5-engine/src/lib/lodash/internal/createBaseFor.js
  34. 51 0
      packages/ckeditor5-engine/src/lib/lodash/internal/equalArrays.js
  35. 48 0
      packages/ckeditor5-engine/src/lib/lodash/internal/equalByTag.js
  36. 67 0
      packages/ckeditor5-engine/src/lib/lodash/internal/equalObjects.js
  37. 15 0
      packages/ckeditor5-engine/src/lib/lodash/internal/getLength.js
  38. 16 0
      packages/ckeditor5-engine/src/lib/lodash/internal/getNative.js
  39. 26 0
      packages/ckeditor5-engine/src/lib/lodash/internal/initCloneArray.js
  40. 63 0
      packages/ckeditor5-engine/src/lib/lodash/internal/initCloneByTag.js
  41. 16 0
      packages/ckeditor5-engine/src/lib/lodash/internal/initCloneObject.js
  42. 15 0
      packages/ckeditor5-engine/src/lib/lodash/internal/isArrayLike.js
  43. 24 0
      packages/ckeditor5-engine/src/lib/lodash/internal/isIndex.js
  44. 28 0
      packages/ckeditor5-engine/src/lib/lodash/internal/isIterateeCall.js
  45. 20 0
      packages/ckeditor5-engine/src/lib/lodash/internal/isLength.js
  46. 12 0
      packages/ckeditor5-engine/src/lib/lodash/internal/isObjectLike.js
  47. 30 0
      packages/ckeditor5-engine/src/lib/lodash/internal/root.js
  48. 41 0
      packages/ckeditor5-engine/src/lib/lodash/internal/shimKeys.js
  49. 14 0
      packages/ckeditor5-engine/src/lib/lodash/internal/toObject.js
  50. 23 0
      packages/ckeditor5-engine/src/lib/lodash/lang.js
  51. 78 0
      packages/ckeditor5-engine/src/lib/lodash/lang/clone.js
  52. 2 0
      packages/ckeditor5-engine/src/lib/lodash/lang/eq.js
  53. 34 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isArguments.js
  54. 48 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isArray.js
  55. 62 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isEqual.js
  56. 38 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isFunction.js
  57. 48 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isNative.js
  58. 37 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isObject.js
  59. 79 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isPlainObject.js
  60. 74 0
      packages/ckeditor5-engine/src/lib/lodash/lang/isTypedArray.js
  61. 0 1650
      packages/ckeditor5-engine/src/lib/lodash/lodash-ckeditor.js
  62. 11 0
      packages/ckeditor5-engine/src/lib/lodash/object.js
  63. 51 0
      packages/ckeditor5-engine/src/lib/lodash/object/assign.js
  64. 2 0
      packages/ckeditor5-engine/src/lib/lodash/object/extend.js
  65. 45 0
      packages/ckeditor5-engine/src/lib/lodash/object/keys.js
  66. 64 0
      packages/ckeditor5-engine/src/lib/lodash/object/keysIn.js
  67. 5 0
      packages/ckeditor5-engine/src/lib/lodash/utility.js
  68. 20 0
      packages/ckeditor5-engine/src/lib/lodash/utility/identity.js
  69. 29 31
      packages/ckeditor5-engine/src/log.js
  70. 420 419
      packages/ckeditor5-engine/src/model.js
  71. 22 12
      packages/ckeditor5-engine/src/plugin.js
  72. 131 114
      packages/ckeditor5-engine/src/plugincollection.js
  73. 91 93
      packages/ckeditor5-engine/src/treemodel/attribute.js
  74. 87 89
      packages/ckeditor5-engine/src/treemodel/attributelist.js
  75. 18 14
      packages/ckeditor5-engine/src/treemodel/batch.js
  76. 20 22
      packages/ckeditor5-engine/src/treemodel/character.js
  77. 133 137
      packages/ckeditor5-engine/src/treemodel/delta/attributedelta.js
  78. 99 101
      packages/ckeditor5-engine/src/treemodel/delta/batch-base.js
  79. 35 39
      packages/ckeditor5-engine/src/treemodel/delta/delta.js
  80. 28 32
      packages/ckeditor5-engine/src/treemodel/delta/insertdelta.js
  81. 57 61
      packages/ckeditor5-engine/src/treemodel/delta/mergedelta.js
  82. 47 51
      packages/ckeditor5-engine/src/treemodel/delta/movedelta.js
  83. 4 5
      packages/ckeditor5-engine/src/treemodel/delta/register.js
  84. 41 45
      packages/ckeditor5-engine/src/treemodel/delta/removedelta.js
  85. 50 54
      packages/ckeditor5-engine/src/treemodel/delta/splitdelta.js
  86. 50 55
      packages/ckeditor5-engine/src/treemodel/delta/unwrapdelta.js
  87. 47 51
      packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js
  88. 60 64
      packages/ckeditor5-engine/src/treemodel/delta/wrapdelta.js
  89. 188 193
      packages/ckeditor5-engine/src/treemodel/document.js
  90. 89 93
      packages/ckeditor5-engine/src/treemodel/element.js
  91. 151 155
      packages/ckeditor5-engine/src/treemodel/liveposition.js
  92. 114 118
      packages/ckeditor5-engine/src/treemodel/liverange.js
  93. 159 163
      packages/ckeditor5-engine/src/treemodel/node.js
  94. 121 125
      packages/ckeditor5-engine/src/treemodel/nodelist.js
  95. 108 112
      packages/ckeditor5-engine/src/treemodel/operation/attributeoperation.js
  96. 48 55
      packages/ckeditor5-engine/src/treemodel/operation/insertoperation.js
  97. 105 109
      packages/ckeditor5-engine/src/treemodel/operation/moveoperation.js
  98. 22 26
      packages/ckeditor5-engine/src/treemodel/operation/nooperation.js
  99. 57 61
      packages/ckeditor5-engine/src/treemodel/operation/operation.js
  100. 20 27
      packages/ckeditor5-engine/src/treemodel/operation/reinsertoperation.js

+ 1 - 5
packages/ckeditor5-engine/.jshintrc

@@ -11,9 +11,5 @@
 	"strict": true,
 	"varstmt": true,
 
-	"globalstrict": true,
-
-	"globals": {
-		"CKEDITOR": false
-	}
+	"globalstrict": true
 }

+ 12 - 3
packages/ckeditor5-engine/gruntfile.js

@@ -18,14 +18,23 @@ module.exports = ( grunt ) => {
 
 		lodash: {
 			build: {
-				dest: 'src/lib/lodash/lodash-ckeditor.js',
+				dest: 'src/lib/lodash',
 				options: {
 					modifier: 'modern',
-					exports: 'amd',
+					modularize: true,
+					exports: 'es',
 					flags: [
 						'development'
 					],
-					include: require( './src/utils-lodash' )
+					include: [
+						'clone',
+						'extend',
+						'isPlainObject',
+						'isObject',
+						'isArray',
+						'last',
+						'isEqual'
+					]
 				}
 			}
 		},

+ 1 - 1
packages/ckeditor5-engine/package.json

@@ -13,7 +13,7 @@
     "grunt-githooks": "^0",
     "shelljs": "^0",
     "lodash-cli": "^3",
-    "grunt-lodash": "^0.4.0"
+    "grunt-lodash": "^0.5.0"
   },
   "author": "CKSource (http://cksource.com/)",
   "license": "See LICENSE.md",

+ 58 - 68
packages/ckeditor5-engine/src/ckeditor.js

@@ -5,6 +5,10 @@
 
 'use strict';
 
+import Editor from './editor.js';
+import Collection from './collection.js';
+import Config from './config.js';
+
 /**
  * This is the API entry point. The entire CKEditor code runs under this object.
  *
@@ -12,81 +16,67 @@
  * @singleton
  */
 
-CKEDITOR.define( [ 'editor', 'collection', 'config' ], ( Editor, Collection, Config ) => {
-	const CKEDITOR = {
-		/**
-		 * A collection containing all editor instances created.
-		 *
-		 * @readonly
-		 * @property {Collection}
-		 */
-		instances: new Collection(),
+const CKEDITOR = {
+	/**
+	 * A collection containing all editor instances created.
+	 *
+	 * @readonly
+	 * @property {Collection}
+	 */
+	instances: new Collection(),
 
-		/**
-		 * Creates an editor instance for the provided DOM element.
-		 *
-		 * The creation of editor instances is an asynchronous operation, therefore a promise is returned by this
-		 * method.
-		 *
-		 *		CKEDITOR.create( '#content' );
-		 *
-		 *		CKEDITOR.create( '#content' ).then( ( editor ) => {
-		 *			// Manipulate "editor" here.
-		 *		} );
-		 *
-		 * @param {String|HTMLElement} element An element selector or a DOM element, which will be the source for the
-		 * created instance.
-		 * @returns {Promise} A promise, which will be fulfilled with the created editor.
-		 */
-		create( element, config ) {
-			return new Promise( ( resolve, reject ) => {
-				// If a query selector has been passed, transform it into a real element.
-				if ( typeof element == 'string' ) {
-					element = document.querySelector( element );
+	/**
+	 * Creates an editor instance for the provided DOM element.
+	 *
+	 * The creation of editor instances is an asynchronous operation, therefore a promise is returned by this
+	 * method.
+	 *
+	 *		CKEDITOR.create( '#content' );
+	 *
+	 *		CKEDITOR.create( '#content' ).then( ( editor ) => {
+	 *			// Manipulate "editor" here.
+	 *		} );
+	 *
+	 * @param {String|HTMLElement} element An element selector or a DOM element, which will be the source for the
+	 * created instance.
+	 * @returns {Promise} A promise, which will be fulfilled with the created editor.
+	 */
+	create( element, config ) {
+		return new Promise( ( resolve, reject ) => {
+			// If a query selector has been passed, transform it into a real element.
+			if ( typeof element == 'string' ) {
+				element = document.querySelector( element );
 
-					if ( !element ) {
-						return reject( new Error( 'Element not found' ) );
-					}
+				if ( !element ) {
+					return reject( new Error( 'Element not found' ) );
 				}
+			}
 
-				const editor = new Editor( element, config );
-
-				this.instances.add( editor );
+			const editor = new Editor( element, config );
 
-				// Remove the editor from `instances` when destroyed.
-				editor.once( 'destroy', () => {
-					this.instances.remove( editor );
-				} );
+			this.instances.add( editor );
 
-				resolve(
-					// Initializes the editor, which returns a promise.
-					editor.init()
-						.then( () => {
-							// After initialization, return the created editor.
-							return editor;
-						} )
-				);
+			// Remove the editor from `instances` when destroyed.
+			editor.once( 'destroy', () => {
+				this.instances.remove( editor );
 			} );
-		},
 
-		/**
-		 * Holds global configuration defaults, which will be used by editor instances when such configurations are not
-		 * available on them directly.
-		 */
-		config: new Config(),
+			resolve(
+				// Initializes the editor, which returns a promise.
+				editor.init()
+					.then( () => {
+						// After initialization, return the created editor.
+						return editor;
+					} )
+			);
+		} );
+	},
 
-		/**
-		 * Gets the full URL path for the specified plugin.
-		 *
-		 * Note that the plugin is not checked to exist. It is a pure path computation.
-		 *
-		 * @param {String} name The plugin name.
-		 * @returns {String} The full URL path of the plugin.
-		 */
-		getPluginPath( name ) {
-			return this.basePath + 'plugins/' + name + '/';
-		}
-	};
+	/**
+	 * Holds global configuration defaults, which will be used by editor instances when such configurations are not
+	 * available on them directly.
+	 */
+	config: new Config()
+};
 
-	return CKEDITOR;
-} );
+export default CKEDITOR;

+ 27 - 31
packages/ckeditor5-engine/src/ckeditorerror.js

@@ -17,38 +17,34 @@
  * @extends Error
  */
 
-CKEDITOR.define( () => {
-	class CKEditorError extends Error {
-		/**
-		 * Creates an instance of the CKEditorError class.
-		 *
-		 * Read more about error logging in the {@link core.log} module.
-		 *
-		 * @constructor
-		 * @param {String} message The error message in an `error-name: Error message.` format.
-		 * During the minification process the "Error message" part will be removed to limit the code size
-		 * and a link to this error documentation will be added to the `message`.
-		 * @param {Object} [data] Additional data describing the error. A stringified version of this object
-		 * will be appended to the error {@link #message}, so the data are quickly visible in the console. The original
-		 * data object will also be later available under the {@link #data} property.
-		 */
-		constructor( message, data ) {
-			if ( data ) {
-				message += ' ' + JSON.stringify( data );
-			}
+export default class CKEditorError extends Error {
+	/**
+	 * Creates an instance of the CKEditorError class.
+	 *
+	 * Read more about error logging in the {@link core.log} module.
+	 *
+	 * @constructor
+	 * @param {String} message The error message in an `error-name: Error message.` format.
+	 * During the minification process the "Error message" part will be removed to limit the code size
+	 * and a link to this error documentation will be added to the `message`.
+	 * @param {Object} [data] Additional data describing the error. A stringified version of this object
+	 * will be appended to the error {@link #message}, so the data are quickly visible in the console. The original
+	 * data object will also be later available under the {@link #data} property.
+	 */
+	constructor( message, data ) {
+		if ( data ) {
+			message += ' ' + JSON.stringify( data );
+		}
 
-			super( message );
+		super( message );
 
-			this.name = 'CKEditorError';
+		this.name = 'CKEditorError';
 
-			/**
-			 * The additional error data passed to the constructor.
-			 *
-			 * @property {Object} data
-			 */
-			this.data = data;
-		}
+		/**
+		 * The additional error data passed to the constructor.
+		 *
+		 * @property {Object} data
+		 */
+		this.data = data;
 	}
-
-	return CKEditorError;
-} );
+}

+ 213 - 213
packages/ckeditor5-engine/src/collection.js

@@ -5,6 +5,10 @@
 
 'use strict';
 
+import EmitterMixin from './emittermixin.js';
+import CKEditorError from './ckeditorerror.js';
+import utilsObject from './lib/lodash/object.js';
+
 /**
  * Collections are ordered sets of objects. Items in the collection can be retrieved by their indexes
  * in the collection (like in an array) or by their ids.
@@ -19,261 +23,257 @@
  * @mixins EventEmitter
  */
 
-CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], ( EmitterMixin, CKEditorError, utils ) => {
-	class Collection {
+export default class Collection {
+	/**
+	 * Creates a new Collection instance.
+	 *
+	 * @constructor
+	 * @param {Iterale} [items] Items to be added to the collection.
+	 * @param {Object} options The options object.
+	 * @param {String} [options.idProperty='id'] The name of the property which is considered to identify an item.
+	 */
+	constructor( options ) {
 		/**
-		 * Creates a new Collection instance.
+		 * The internal list of items in the collection.
 		 *
-		 * @constructor
-		 * @param {Iterale} [items] Items to be added to the collection.
-		 * @param {Object} options The options object.
-		 * @param {String} [options.idProperty='id'] The name of the property which is considered to identify an item.
+		 * @private
+		 * @property {Object[]}
 		 */
-		constructor( options ) {
-			/**
-			 * The internal list of items in the collection.
-			 *
-			 * @private
-			 * @property {Object[]}
-			 */
-			this._items = [];
-
-			/**
-			 * The internal map of items in the collection.
-			 *
-			 * @private
-			 * @property {Map}
-			 */
-			this._itemMap = new Map();
-
-			/**
-			 * Next id which will be assigned to unidentified item while adding it to the collection.
-			 *
-			 * @private
-			 * @property
-			 */
-			this._nextId = 0;
-
-			/**
-			 * The name of the property which is considered to identify an item.
-			 *
-			 * @private
-			 * @property {String}
-			 */
-			this._idProperty = options && options.idProperty || 'id';
-		}
+		this._items = [];
 
 		/**
-		 * The number of items available in the collection.
+		 * The internal map of items in the collection.
 		 *
-		 * @property length
+		 * @private
+		 * @property {Map}
 		 */
-		get length() {
-			return this._items.length;
-		}
+		this._itemMap = new Map();
 
 		/**
-		 * Adds an item into the collection.
+		 * Next id which will be assigned to unidentified item while adding it to the collection.
 		 *
-		 * If the item does not have an id, then it will be automatically generated and set on the item.
+		 * @private
+		 * @property
+		 */
+		this._nextId = 0;
+
+		/**
+		 * The name of the property which is considered to identify an item.
 		 *
-		 * @chainable
-		 * @param {Object} item
-		 * @param {Number} [index] The position of the item in the collection. The item
-		 * is pushed to the collection when `index` not specified.
+		 * @private
+		 * @property {String}
 		 */
-		add( item, index ) {
-			let itemId;
-			const idProperty = this._idProperty;
-
-			if ( ( idProperty in item ) ) {
-				itemId = item[ idProperty ];
-
-				if ( typeof itemId != 'string' ) {
-					/**
-					 * This item's id should be a string.
-					 *
-					 * @error collection-add-invalid-id
-					 */
-					throw new CKEditorError( 'collection-add-invalid-id' );
-				}
-
-				if ( this.get( itemId ) ) {
-					/**
-					 * This item already exists in the collection.
-					 *
-					 * @error collection-add-item-already-exists
-					 */
-					throw new CKEditorError( 'collection-add-item-already-exists' );
-				}
-			} else {
-				itemId = this._getNextId();
-				item[ idProperty ] = itemId;
-			}
+		this._idProperty = options && options.idProperty || 'id';
+	}
+
+	/**
+	 * The number of items available in the collection.
+	 *
+	 * @property length
+	 */
+	get length() {
+		return this._items.length;
+	}
 
-			// TODO: Use ES6 default function argument.
-			if ( index === undefined ) {
-				index = this._items.length;
-			} else if ( index > this._items.length || index < 0 ) {
+	/**
+	 * Adds an item into the collection.
+	 *
+	 * If the item does not have an id, then it will be automatically generated and set on the item.
+	 *
+	 * @chainable
+	 * @param {Object} item
+	 * @param {Number} [index] The position of the item in the collection. The item
+	 * is pushed to the collection when `index` not specified.
+	 */
+	add( item, index ) {
+		let itemId;
+		const idProperty = this._idProperty;
+
+		if ( ( idProperty in item ) ) {
+			itemId = item[ idProperty ];
+
+			if ( typeof itemId != 'string' ) {
 				/**
-				 * The index number has invalid value.
+				 * This item's id should be a string.
 				 *
-				 * @error collection-add-item-bad-index
+				 * @error collection-add-invalid-id
 				 */
-				throw new CKEditorError( 'collection-add-item-invalid-index' );
+				throw new CKEditorError( 'collection-add-invalid-id' );
 			}
 
-			this._items.splice( index, 0, item );
-
-			this._itemMap.set( itemId, item );
-
-			this.fire( 'add', item, index );
-
-			return this;
-		}
-
-		/**
-		 * Gets item by its id or index.
-		 *
-		 * @param {String|Number} idOrIndex The item id or index in the collection.
-		 * @returns {Object} The requested item or `null` if such item does not exist.
-		 */
-		get( idOrIndex ) {
-			let item;
-
-			if ( typeof idOrIndex == 'string' ) {
-				item = this._itemMap.get( idOrIndex );
-			} else if ( typeof idOrIndex == 'number' ) {
-				item = this._items[ idOrIndex ];
-			} else {
+			if ( this.get( itemId ) ) {
 				/**
-				 * Index or id must be given.
+				 * This item already exists in the collection.
 				 *
-				 * @error collection-get-invalid-arg
+				 * @error collection-add-item-already-exists
 				 */
-				throw new CKEditorError( 'collection-get-invalid-arg: Index or id must be given.' );
+				throw new CKEditorError( 'collection-add-item-already-exists' );
 			}
+		} else {
+			itemId = this._getNextId();
+			item[ idProperty ] = itemId;
+		}
 
-			return item || null;
+		// TODO: Use ES6 default function argument.
+		if ( index === undefined ) {
+			index = this._items.length;
+		} else if ( index > this._items.length || index < 0 ) {
+			/**
+			 * The index number has invalid value.
+			 *
+			 * @error collection-add-item-bad-index
+			 */
+			throw new CKEditorError( 'collection-add-item-invalid-index' );
 		}
 
-		/**
-		 * Removes an item from the collection.
-		 *
-		 * @param {Object|Number|String} subject The item to remove, its id or index in the collection.
-		 * @returns {Object} The removed item.
-		 */
-		remove( subject ) {
-			let index, id, item;
-			let itemDoesNotExist = false;
-			const idProperty = this._idProperty;
-
-			if ( typeof subject == 'string' ) {
-				id = subject;
-				item = this._itemMap.get( id );
-				itemDoesNotExist = !item;
-
-				if ( item ) {
-					index = this._items.indexOf( item );
-				}
-			} else if ( typeof subject == 'number' ) {
-				index = subject;
-				item = this._items[ index ];
-				itemDoesNotExist = !item;
-
-				if ( item ) {
-					id = item[ idProperty ];
-				}
-			} else {
-				item = subject;
-				id = item[ idProperty ];
-				index = this._items.indexOf( item );
-				itemDoesNotExist = ( index == -1 || !this._itemMap.get( id ) );
-			}
+		this._items.splice( index, 0, item );
 
-			if ( itemDoesNotExist ) {
-				/**
-				 * Item not found.
-				 *
-				 * @error collection-remove-404
-				 */
-				throw new CKEditorError( 'collection-remove-404: Item not found.' );
-			}
+		this._itemMap.set( itemId, item );
 
-			this._items.splice( index, 1 );
-			this._itemMap.delete( id );
+		this.fire( 'add', item, index );
 
-			this.fire( 'remove', item );
+		return this;
+	}
 
-			return item;
+	/**
+	 * Gets item by its id or index.
+	 *
+	 * @param {String|Number} idOrIndex The item id or index in the collection.
+	 * @returns {Object} The requested item or `null` if such item does not exist.
+	 */
+	get( idOrIndex ) {
+		let item;
+
+		if ( typeof idOrIndex == 'string' ) {
+			item = this._itemMap.get( idOrIndex );
+		} else if ( typeof idOrIndex == 'number' ) {
+			item = this._items[ idOrIndex ];
+		} else {
+			/**
+			 * Index or id must be given.
+			 *
+			 * @error collection-get-invalid-arg
+			 */
+			throw new CKEditorError( 'collection-get-invalid-arg: Index or id must be given.' );
 		}
 
-		/**
-		 * Executes the callback for each item in the collection and composes an array or values returned by this callback.
-		 *
-		 * @param {Function} callback
-		 * @param {Item} callback.item
-		 * @param {Number} callback.index
-		 * @params {Object} ctx Context in which the `callback` will be called.
-		 * @returns {Array} The result of mapping.
-		 */
-		map( callback, ctx ) {
-			return this._items.map( callback, ctx );
-		}
+		return item || null;
+	}
 
-		/**
-		 * Finds the first item in the collection for which the `callback` returns a true value.
-		 *
-		 * @param {Function} callback
-		 * @param {Object} callback.item
-		 * @param {Number} callback.index
-		 * @returns {Object} The item for which `callback` returned a true value.
-		 * @params {Object} ctx Context in which the `callback` will be called.
-		 */
-		find( callback, ctx ) {
-			return this._items.find( callback, ctx );
-		}
+	/**
+	 * Removes an item from the collection.
+	 *
+	 * @param {Object|Number|String} subject The item to remove, its id or index in the collection.
+	 * @returns {Object} The removed item.
+	 */
+	remove( subject ) {
+		let index, id, item;
+		let itemDoesNotExist = false;
+		const idProperty = this._idProperty;
+
+		if ( typeof subject == 'string' ) {
+			id = subject;
+			item = this._itemMap.get( id );
+			itemDoesNotExist = !item;
+
+			if ( item ) {
+				index = this._items.indexOf( item );
+			}
+		} else if ( typeof subject == 'number' ) {
+			index = subject;
+			item = this._items[ index ];
+			itemDoesNotExist = !item;
 
-		/**
-		 * Returns an array with items for which the `callback` returned a true value.
-		 *
-		 * @param {Function} callback
-		 * @param {Object} callback.item
-		 * @param {Number} callback.index
-		 * @params {Object} ctx Context in which the `callback` will be called.
-		 * @returns {Object[]} The array with matching items.
-		 */
-		filter( callback, ctx ) {
-			return this._items.filter( callback, ctx );
+			if ( item ) {
+				id = item[ idProperty ];
+			}
+		} else {
+			item = subject;
+			id = item[ idProperty ];
+			index = this._items.indexOf( item );
+			itemDoesNotExist = ( index == -1 || !this._itemMap.get( id ) );
 		}
 
-		/**
-		 * Collection iterator.
-		 */
-		[ Symbol.iterator ]() {
-			return this._items[ Symbol.iterator ]();
+		if ( itemDoesNotExist ) {
+			/**
+			 * Item not found.
+			 *
+			 * @error collection-remove-404
+			 */
+			throw new CKEditorError( 'collection-remove-404: Item not found.' );
 		}
 
-		/**
-		 * Generates next (not yet used) id for unidentified item being add to the collection.
-		 *
-		 * @returns {String} The next id.
-		 */
-		_getNextId() {
-			let id;
+		this._items.splice( index, 1 );
+		this._itemMap.delete( id );
 
-			do {
-				id = String( this._nextId++ );
-			} while ( this._itemMap.has( id ) );
+		this.fire( 'remove', item );
 
-			return id;
-		}
+		return item;
 	}
 
-	utils.extend( Collection.prototype, EmitterMixin );
+	/**
+	 * Executes the callback for each item in the collection and composes an array or values returned by this callback.
+	 *
+	 * @param {Function} callback
+	 * @param {Item} callback.item
+	 * @param {Number} callback.index
+	 * @params {Object} ctx Context in which the `callback` will be called.
+	 * @returns {Array} The result of mapping.
+	 */
+	map( callback, ctx ) {
+		return this._items.map( callback, ctx );
+	}
+
+	/**
+	 * Finds the first item in the collection for which the `callback` returns a true value.
+	 *
+	 * @param {Function} callback
+	 * @param {Object} callback.item
+	 * @param {Number} callback.index
+	 * @returns {Object} The item for which `callback` returned a true value.
+	 * @params {Object} ctx Context in which the `callback` will be called.
+	 */
+	find( callback, ctx ) {
+		return this._items.find( callback, ctx );
+	}
+
+	/**
+	 * Returns an array with items for which the `callback` returned a true value.
+	 *
+	 * @param {Function} callback
+	 * @param {Object} callback.item
+	 * @param {Number} callback.index
+	 * @params {Object} ctx Context in which the `callback` will be called.
+	 * @returns {Object[]} The array with matching items.
+	 */
+	filter( callback, ctx ) {
+		return this._items.filter( callback, ctx );
+	}
+
+	/**
+	 * Collection iterator.
+	 */
+	[ Symbol.iterator ]() {
+		return this._items[ Symbol.iterator ]();
+	}
+
+	/**
+	 * Generates next (not yet used) id for unidentified item being add to the collection.
+	 *
+	 * @returns {String} The next id.
+	 */
+	_getNextId() {
+		let id;
+
+		do {
+			id = String( this._nextId++ );
+		} while ( this._itemMap.has( id ) );
+
+		return id;
+	}
+}
 
-	return Collection;
-} );
+utilsObject.extend( Collection.prototype, EmitterMixin );
 
 /**
  * Fired when an item is added to the collection.

+ 154 - 155
packages/ckeditor5-engine/src/config.js

@@ -5,6 +5,9 @@
 
 'use strict';
 
+import Model from './model.js';
+import utilsLang from './lib/lodash/lang.js';
+
 /**
  * Handles a configuration dictionary.
  *
@@ -12,182 +15,178 @@
  * @extends Model
  */
 
-CKEDITOR.define( [ 'model', 'utils' ], ( Model, utils ) => {
-	class Config extends Model {
-		/**
-		 * Creates an instance of the {@link Config} class.
-		 *
-		 * @param {Object} [configurations] The initial configurations to be set.
-		 * @constructor
-		 */
-		constructor( configurations ) {
-			super();
-
-			if ( configurations ) {
-				this.set( configurations );
-			}
+export default class Config extends Model {
+	/**
+	 * Creates an instance of the {@link Config} class.
+	 *
+	 * @param {Object} [configurations] The initial configurations to be set.
+	 * @constructor
+	 */
+	constructor( configurations ) {
+		super();
+
+		if ( configurations ) {
+			this.set( configurations );
 		}
+	}
 
-		/**
-		 * Set configuration values.
-		 *
-		 * It accepts both a name/value pair or an object, which properties and values will be used to set
-		 * configurations.
-		 *
-		 * It also accepts setting a "deep configuration" by using dots in the name. For example, `'resize.width'` sets
-		 * the value for the `width` configuration in the `resize` subset.
-		 *
-		 *     config.set( 'width', 500 );
-		 *     config.set( 'toolbar.collapsed', true );
-		 *
-		 *     // Equivalent to:
-		 *     config.set( {
-		 *         width: 500
-		 *         toolbar: {
-		 *             collapsed: true
-		 *         }
-		 *     } );
-		 *
-		 * Passing an object as the value will amend the configuration, not replace it.
-		 *
-		 *     config.set( 'toolbar', {
-		 *         collapsed: true,
-		 *     } );
-		 *
-		 *     config.set( 'toolbar', {
-		 *         color: 'red',
-		 *     } );
-		 *
-		 *     config.toolbar.collapsed; // true
-		 *     config.toolbar.color; // 'red'
-		 *
-		 * @param {String|Object} nameOrConfigurations The configuration name or an object from which take properties as
-		 * configuration entries. Configuration names are case-insensitive.
-		 * @param {*} [value=null] The configuration value. Used if a name is passed to nameOrConfigurations.
-		 */
-		set( name, value ) {
-			// Just pass the call to the original set() in case of an object. It'll deal with recursing through the
-			// object and calling set( name, value ) again for each property.
-			if ( utils.isObject( name ) ) {
-				super.set.apply( this, arguments );
-
-				return;
-			}
-
-			// The target for this configuration is, for now, this object.
-			let target = this;
+	/**
+	 * Set configuration values.
+	 *
+	 * It accepts both a name/value pair or an object, which properties and values will be used to set
+	 * configurations.
+	 *
+	 * It also accepts setting a "deep configuration" by using dots in the name. For example, `'resize.width'` sets
+	 * the value for the `width` configuration in the `resize` subset.
+	 *
+	 *     config.set( 'width', 500 );
+	 *     config.set( 'toolbar.collapsed', true );
+	 *
+	 *     // Equivalent to:
+	 *     config.set( {
+	 *         width: 500
+	 *         toolbar: {
+	 *             collapsed: true
+	 *         }
+	 *     } );
+	 *
+	 * Passing an object as the value will amend the configuration, not replace it.
+	 *
+	 *     config.set( 'toolbar', {
+	 *         collapsed: true,
+	 *     } );
+	 *
+	 *     config.set( 'toolbar', {
+	 *         color: 'red',
+	 *     } );
+	 *
+	 *     config.toolbar.collapsed; // true
+	 *     config.toolbar.color; // 'red'
+	 *
+	 * @param {String|Object} nameOrConfigurations The configuration name or an object from which take properties as
+	 * configuration entries. Configuration names are case-insensitive.
+	 * @param {*} [value=null] The configuration value. Used if a name is passed to nameOrConfigurations.
+	 */
+	set( name, value ) {
+		// Just pass the call to the original set() in case of an object. It'll deal with recursing through the
+		// object and calling set( name, value ) again for each property.
+		if ( utilsLang.isObject( name ) ) {
+			super.set.apply( this, arguments );
+
+			return;
+		}
 
-			// The configuration name should be split into parts if it has dots. E.g: `resize.width`.
-			const parts = name.toLowerCase().split( '.' );
+		// The target for this configuration is, for now, this object.
+		let target = this;
 
-			// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`
-			name = parts.pop();
+		// The configuration name should be split into parts if it has dots. E.g: `resize.width`.
+		const parts = name.toLowerCase().split( '.' );
 
-			// Retrieves the final target for this configuration recursively.
-			for ( let i = 0; i < parts.length; i++ ) {
-				// The target will always be an instance of Config.
-				if ( !( target[ parts[ i ] ] instanceof Config ) ) {
-					target.set( parts[ i ], new Config() );
-				}
+		// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`
+		name = parts.pop();
 
-				target = target[ parts[ i ] ];
+		// Retrieves the final target for this configuration recursively.
+		for ( let i = 0; i < parts.length; i++ ) {
+			// The target will always be an instance of Config.
+			if ( !( target[ parts[ i ] ] instanceof Config ) ) {
+				target.set( parts[ i ], new Config() );
 			}
 
-			// Values set as pure objects will be treated as Config subsets.
-			if ( utils.isPlainObject( value ) ) {
-				// If the target is an instance of Config (a deep config subset).
-				if ( target[ name ] instanceof Config ) {
-					// Amend the target with the value, instead of replacing it.
-					target[ name ].set( value );
+			target = target[ parts[ i ] ];
+		}
 
-					return;
-				}
+		// Values set as pure objects will be treated as Config subsets.
+		if ( utilsLang.isPlainObject( value ) ) {
+			// If the target is an instance of Config (a deep config subset).
+			if ( target[ name ] instanceof Config ) {
+				// Amend the target with the value, instead of replacing it.
+				target[ name ].set( value );
 
-				value = new Config( value );
+				return;
 			}
 
-			// Values will never be undefined.
-			if ( typeof value == 'undefined' ) {
-				value = null;
-			}
+			value = new Config( value );
+		}
 
-			// Call the original set() on the target.
-			super.set.call( target, name, value );
+		// Values will never be undefined.
+		if ( typeof value == 'undefined' ) {
+			value = null;
 		}
 
-		/**
-		 * Gets the value for a configuration entry.
-		 *
-		 *     config.get( 'name' );
-		 *
-		 * Deep configurations can be retrieved by separating each part with a dot.
-		 *
-		 *     config.get( 'toolbar.collapsed' );
-		 *
-		 * @param {String} name The configuration name. Configuration names are case-insensitive.
-		 * @returns {*} The configuration value or `undefined` if the configuration entry was not found.
-		 */
-		get( name ) {
-			// The target for this configuration is, for now, this object.
-			let source = this;
-
-			// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`]
-			const parts = name.toLowerCase().split( '.' );
-
-			// Take the name of the configuration from the parts. E.g. `resize.width` -> `width`
-			name = parts.pop();
-
-			// Retrieves the source for this configuration recursively.
-			for ( let i = 0; i < parts.length; i++ ) {
-				// The target will always be an instance of Config.
-				if ( !( source[ parts[ i ] ] instanceof Config ) ) {
-					source = null;
-					break;
-				}
-
-				source = source[ parts[ i ] ];
-			}
+		// Call the original set() on the target.
+		super.set.call( target, name, value );
+	}
 
-			// Try to retrieve it from the source object.
-			if ( source && ( typeof source[ name ] != 'undefined' ) ) {
-				return source[ name ];
+	/**
+	 * Gets the value for a configuration entry.
+	 *
+	 *     config.get( 'name' );
+	 *
+	 * Deep configurations can be retrieved by separating each part with a dot.
+	 *
+	 *     config.get( 'toolbar.collapsed' );
+	 *
+	 * @param {String} name The configuration name. Configuration names are case-insensitive.
+	 * @returns {*} The configuration value or `undefined` if the configuration entry was not found.
+	 */
+	get( name ) {
+		// The target for this configuration is, for now, this object.
+		let source = this;
+
+		// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`]
+		const parts = name.toLowerCase().split( '.' );
+
+		// Take the name of the configuration from the parts. E.g. `resize.width` -> `width`
+		name = parts.pop();
+
+		// Retrieves the source for this configuration recursively.
+		for ( let i = 0; i < parts.length; i++ ) {
+			// The target will always be an instance of Config.
+			if ( !( source[ parts[ i ] ] instanceof Config ) ) {
+				source = null;
+				break;
 			}
 
-			// If not found, take it from the definition.
-			if ( this.definition ) {
-				return this.definition[ name ];
-			}
+			source = source[ parts[ i ] ];
 		}
 
-		/**
-		 * Defines the name and default value for configurations. It accepts the same parameters as the
-		 * {@link Config#set set()} method.
-		 *
-		 * On first call, the {@link Config#definition definition} property is created to hold all defined
-		 * configurations.
-		 *
-		 * This method is supposed to be called by plugin developers to setup plugin's configurations. It would be
-		 * rarely used for other needs.
-		 *
-		 * @param {String|Object} nameOrConfigurations The configuration name or an object from which take properties as
-		 * configuration entries.
-		 * @param {*} [value] The configuration value. Used if a name is passed to nameOrConfigurations. If undefined,
-		 * the configuration is set to `null`.
-		 */
-		define( name, value ) {
-			if ( !this.definition ) {
-				/**
-				 * TODO
-				 *
-				 * @property
-				 * @type {Config}
-				 */
-				this.definition = new Config();
-			}
+		// Try to retrieve it from the source object.
+		if ( source && ( typeof source[ name ] != 'undefined' ) ) {
+			return source[ name ];
+		}
 
-			this.definition.set( name, value );
+		// If not found, take it from the definition.
+		if ( this.definition ) {
+			return this.definition[ name ];
 		}
 	}
 
-	return Config;
-} );
+	/**
+	 * Defines the name and default value for configurations. It accepts the same parameters as the
+	 * {@link Config#set set()} method.
+	 *
+	 * On first call, the {@link Config#definition definition} property is created to hold all defined
+	 * configurations.
+	 *
+	 * This method is supposed to be called by plugin developers to setup plugin's configurations. It would be
+	 * rarely used for other needs.
+	 *
+	 * @param {String|Object} nameOrConfigurations The configuration name or an object from which take properties as
+	 * configuration entries.
+	 * @param {*} [value] The configuration value. Used if a name is passed to nameOrConfigurations. If undefined,
+	 * the configuration is set to `null`.
+	 */
+	define( name, value ) {
+		if ( !this.definition ) {
+			/**
+			 * TODO
+			 *
+			 * @property
+			 * @type {Config}
+			 */
+			this.definition = new Config();
+		}
+
+		this.definition.set( name, value );
+	}
+}

+ 6 - 8
packages/ckeditor5-engine/src/creator.js

@@ -5,16 +5,14 @@
 
 'use strict';
 
+import Plugin from './plugin.js';
+
 /**
  * Basic creator class.
  *
- * @class Creator
- * @extends Plugin
+ * @class core.Creator
+ * @extends core.Plugin
  */
 
-CKEDITOR.define( [ 'plugin' ], ( Plugin ) => {
-	class Creator extends Plugin {
-	}
-
-	return Creator;
-} );
+export default class Creator extends Plugin {
+}

+ 119 - 156
packages/ckeditor5-engine/src/editor.js

@@ -5,6 +5,12 @@
 
 'use strict';
 
+import Model from './model.js';
+import EditorConfig from './editorconfig.js';
+import PluginCollection from './plugincollection.js';
+import CKEditorError from './ckeditorerror.js';
+import langUtils from './lib/lodash/lang.js';
+
 /**
  * Represents a single editor instance.
  *
@@ -12,188 +18,137 @@
  * @extends Model
  */
 
-CKEDITOR.define( [
-	'model',
-	'editorconfig',
-	'plugincollection',
-	'creator',
-	'ckeditorerror',
-	'utils'
-], ( Model, EditorConfig, PluginCollection, Creator, CKEditorError, utils ) => {
-	class Editor extends Model {
+export default class Editor extends Model {
+	/**
+	 * Creates a new instance of the Editor class.
+	 *
+	 * This constructor should be rarely used. When creating new editor instance use instead the
+	 * {@link CKEDITOR#create CKEDITOR.create() method}.
+	 *
+	 * @param {HTMLElement} element The DOM element that will be the source for the created editor.
+	 * @constructor
+	 */
+	constructor( element, config ) {
+		super();
+
 		/**
-		 * Creates a new instance of the Editor class.
+		 * The original host page element upon which the editor is created. It is only supposed to be provided on
+		 * editor creation and is not subject to be modified.
 		 *
-		 * This constructor should be rarely used. When creating new editor instance use instead the
-		 * {@link CKEDITOR#create CKEDITOR.create() method}.
-		 *
-		 * @param {HTMLElement} element The DOM element that will be the source for the created editor.
-		 * @constructor
+		 * @readonly
+		 * @property {HTMLElement}
 		 */
-		constructor( element, config ) {
-			super();
-
-			/**
-			 * The original host page element upon which the editor is created. It is only supposed to be provided on
-			 * editor creation and is not subject to be modified.
-			 *
-			 * @readonly
-			 * @property {HTMLElement}
-			 */
-			this.element = element;
+		this.element = element;
 
-			/**
-			 * Holds all configurations specific to this editor instance.
-			 *
-			 * This instance of the {@link Config} class is customized so its {@link Config#get} method will retrieve
-			 * global configurations available in {@link CKEDITOR.config} if configurations are not found in the
-			 * instance itself.
-			 *
-			 * @readonly
-			 * @property {Config}
-			 */
-			this.config = new EditorConfig( config );
+		/**
+		 * Holds all configurations specific to this editor instance.
+		 *
+		 * This instance of the {@link Config} class is customized so its {@link Config#get} method will retrieve
+		 * global configurations available in {@link CKEDITOR.config} if configurations are not found in the
+		 * instance itself.
+		 *
+		 * @readonly
+		 * @property {Config}
+		 */
+		this.config = new EditorConfig( config );
 
-			/**
-			 * The plugins loaded and in use by this editor instance.
-			 *
-			 * @readonly
-			 * @property {PluginCollection}
-			 */
-			this.plugins = new PluginCollection( this );
+		/**
+		 * The plugins loaded and in use by this editor instance.
+		 *
+		 * @readonly
+		 * @property {PluginCollection}
+		 */
+		this.plugins = new PluginCollection( this );
 
-			/**
-			 * The chosen creator.
-			 *
-			 * @property {Creator} _creator
-			 * @protected
-			 */
+		/**
+		 * The chosen creator.
+		 *
+		 * @property {Creator} _creator
+		 * @protected
+		 */
+	}
 
+	/**
+	 * Initializes the editor instance object after its creation.
+	 *
+	 * The initialization consists of the following procedures:
+	 *
+	 * * Loading and initializing the configured features and creator.
+	 * * Firing the editor creator.
+	 *
+	 * This method should be rarely used as {@link CKEDITOR#create} calls it one should never use the `Editor`
+	 * constructor directly.
+	 *
+	 * @returns {Promise} A promise which resolves once the initialization is completed.
+	 */
+	init() {
+		const that = this;
+		const config = this.config;
+		let creatorName = config.creator;
+
+		if ( !creatorName ) {
 			/**
-			 * The list of detected creators.
+			 * The `config.creator` option was not defined.
 			 *
-			 * @property {Map}
-			 * @protected
+			 * @error editor-undefined-creator
 			 */
-			this._creators = new Map();
+			return Promise.reject(
+				new CKEditorError( 'editor-undefined-creator: The config.creator option was not defined.' )
+			);
 		}
 
-		/**
-		 * Initializes the editor instance object after its creation.
-		 *
-		 * The initialization consists of the following procedures:
-		 *
-		 *  * Load and initialize the configured plugins.
-		 *  * Fires the editor creator.
-		 *
-		 * This method should be rarely used as `CKEDITOR.create` calls it one should never use the `Editor` constructor
-		 * directly.
-		 *
-		 * @returns {Promise} A promise which resolves once the initialization is completed.
-		 */
-		init() {
-			const that = this;
-			const config = this.config;
+		return loadPlugins()
+			.then( initPlugins )
+			.then( fireCreator );
 
-			return loadPlugins()
-				.then( initPlugins )
-				.then( findCreators )
-				.then( fireCreator );
+		function loadPlugins() {
+			let plugins = config.features || [];
 
-			function loadPlugins() {
-				return that.plugins.load( config.plugins );
+			// Handle features passed as a string.
+			if ( !langUtils.isArray( plugins ) ) {
+				plugins = plugins.split( ',' );
 			}
 
-			function initPlugins( loadedPlugins ) {
-				// Start with a resolved promise.
-				let promise = Promise.resolve();
+			plugins.push( creatorName );
 
-				// Chain it with promises that resolve with the init() call of every plugin.
-				for ( let i = 0; i < loadedPlugins.length; i++ ) {
-					promise = promise.then( () => loadedPlugins[ i ].init() );
-				}
+			return that.plugins.load( plugins );
+		}
 
-				// Return the promise chain.
-				return promise;
-			}
+		function initPlugins( loadedPlugins ) {
+			return loadedPlugins.reduce( ( promise, plugin ) => {
+				return promise.then( plugin.init.bind( plugin ) );
+			}, Promise.resolve() );
+		}
 
-			function findCreators() {
-				for ( let plugin of that.plugins ) {
-					if ( plugin instanceof Creator ) {
-						that._creators.set( plugin.name, plugin );
-					}
-				}
-			}
+		function fireCreator() {
+			// We can always get the creator by its name because config.creator (which is requried) is passed
+			// to PluginCollection.load().
+			that._creator = that.plugins.get( creatorName );
 
-			function fireCreator() {
-				// Take the name of the creator to use (config or any of the registered ones).
-				const creatorName = config.creator && ( 'creator-' + config.creator );
-				let creator;
-
-				if ( creatorName ) {
-					// Take the registered class for the given creator name.
-					creator = that._creators.get( creatorName );
-				} else if ( that._creators.size > 1 ) {
-					/**
-					 * The `config.creator` option was not defined.
-					 *
-					 * This error is thrown when more than one creator is available and the configuration does
-					 * not specify which one to use.
-					 *
-					 * @error editor-undefined-creator
-					 */
-					throw new CKEditorError( 'editor-undefined-creator: The config.creator option was not defined.' );
-				} else {
-					creator = utils.nth( 0, that._creators.values() );
-				}
-
-				if ( !creator ) {
-					/**
-					 * The creator has not been found.
-					 *
-					 * * If `creatorName` is defined it means that `config.creator` was configured, but such
-					 * plugin does not exist or it does not implement a creator.
-					 * * If `creatorName` is undefined it means that `config.creator` was not configured and
-					 * that none of the loaded plugins implement a creator.
-					 *
-					 * @error editor-creator-404
-					 * @param {String} creatorName
-					 */
-					throw new CKEditorError(
-						'editor-creator-404: The creator has not been found.',
-						{ creatorName: creatorName }
-					);
-				}
-
-				that._creator = creator;
-
-				// Finally fire the creator. It may be asynchronous, returning a promise.
-				return creator.create();
-			}
+			// Finally fire the creator. It may be asynchronous, returning a promise.
+			return that._creator.create();
 		}
+	}
 
-		/**
-		 * Destroys the editor instance, releasing all resources used by it. If the editor replaced an element, the
-		 * element will be recovered.
-		 *
-		 * @fires destroy
-		 * @returns {Promise} A promise that resolves once the editor instance is fully destroyed.
-		 */
-		destroy() {
-			const that = this;
+	/**
+	 * Destroys the editor instance, releasing all resources used by it. If the editor replaced an element, the
+	 * element will be recovered.
+	 *
+	 * @fires destroy
+	 * @returns {Promise} A promise that resolves once the editor instance is fully destroyed.
+	 */
+	destroy() {
+		const that = this;
 
-			this.fire( 'destroy' );
+		this.fire( 'destroy' );
 
-			delete this.element;
+		delete this.element;
 
-			return Promise.resolve().then( () => {
-				return that._creator && that._creator.destroy();
-			} );
-		}
+		return Promise.resolve().then( () => {
+			return that._creator && that._creator.destroy();
+		} );
 	}
-
-	return Editor;
-} );
+}
 
 /**
  * Fired when this editor instance is destroyed. The editor at this point is not usable and this event should be used to
@@ -201,3 +156,11 @@ CKEDITOR.define( [
  *
  * @event destroy
  */
+
+/**
+ * @cfg {String[]} features
+ */
+
+/**
+ * @cfg {String} creator
+ */

+ 16 - 24
packages/ckeditor5-engine/src/editorconfig.js

@@ -5,6 +5,9 @@
 
 'use strict';
 
+import CKEDITOR from '../ckeditor.js';
+import Config from './config.js';
+
 /**
  * Handles a configuration dictionary for an editor instance.
  *
@@ -15,30 +18,19 @@
  * @extends Config
  */
 
-CKEDITOR.define( [ 'ckeditor', 'config' ], ( CKE, Config ) => {
-	class EditorConfig extends Config {
-		/**
-		 * @inheritdoc Config#get
-		 */
-		get() {
-			// Try to take it from this editor instance.
-			let value = super.get.apply( this, arguments );
-
-			// If the configuration is not defined in the instance, try to take it from CKEDITOR.config.
-			if ( typeof value == 'undefined' ) {
-				// There is a circular dependency issue here: CKEDITOR -> Editor -> EditorConfig -> CKEDITOR.
-				// Therefore we need to require() it again here. That's why the parameter was named CKE.
-				//
-				// Note additionally that we still keep 'ckeditor' in the dependency list for correctness, to ensure
-				// that the module is loaded.
-
-				CKE = CKE || CKEDITOR.require( 'ckeditor' );
-				value = super.get.apply( CKE.config, arguments );
-			}
+export default class EditorConfig extends Config {
+	/**
+	 * @inheritdoc core.Config#get
+	 */
+	get() {
+		// Try to take it from this editor instance.
+		let value = super.get.apply( this, arguments );
 
-			return value;
+		// If the configuration is not defined in the instance, try to take it from CKEDITOR.config.
+		if ( typeof value == 'undefined' ) {
+			value = super.get.apply( CKEDITOR.config, arguments );
 		}
-	}
 
-	return EditorConfig;
-} );
+		return value;
+	}
+}

+ 247 - 246
packages/ckeditor5-engine/src/emittermixin.js

@@ -5,6 +5,13 @@
 
 'use strict';
 
+import EventInfo from './eventinfo.js';
+import utils from './utils.js';
+
+// Saves how many callbacks has been already added. Does not decrement when callback is removed.
+// Used internally as a unique id for a callback.
+let eventsCounter = 0;
+
 /**
  * Mixin that injects the events API into its host.
  *
@@ -12,287 +19,281 @@
  * @singleton
  */
 
-CKEDITOR.define( [ 'eventinfo', 'utils' ], ( EventInfo, utils ) => {
-	// Saves how many callbacks has been already added. Does not decrement when callback is removed.
-	// Used internally as a unique id for a callback.
-	let eventsCounter = 0;
-
-	const EmitterMixin = {
-		/**
-		 * Registers a callback function to be executed when an event is fired.
-		 *
-		 * @param {String} event The name of the event.
-		 * @param {Function} callback The function to be called on event.
-		 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
-		 * event.
-		 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
-		 * Lower values are called first.
-		 */
-		on( event, callback, ctx, priority ) {
-			const callbacks = getCallbacks( this, event );
-
-			// Set the priority defaults.
-			if ( typeof priority != 'number' ) {
-				priority = 10;
-			}
-
-			callback = {
-				callback: callback,
-				ctx: ctx || this,
-				priority: priority,
-				// Save counter value as unique id.
-				counter: ++eventsCounter
-			};
-
-			// Add the callback to the list in the right priority position.
-			for ( let i = callbacks.length - 1; i >= 0; i-- ) {
-				if ( callbacks[ i ].priority <= priority ) {
-					callbacks.splice( i + 1, 0, callback );
+const EmitterMixin = {
+	/**
+	 * Registers a callback function to be executed when an event is fired.
+	 *
+	 * @param {String} event The name of the event.
+	 * @param {Function} callback The function to be called on event.
+	 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
+	 * event.
+	 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
+	 * Lower values are called first.
+	 */
+	on( event, callback, ctx, priority ) {
+		const callbacks = getCallbacks( this, event );
+
+		// Set the priority defaults.
+		if ( typeof priority != 'number' ) {
+			priority = 10;
+		}
 
-					return;
-				}
-			}
+		callback = {
+			callback: callback,
+			ctx: ctx || this,
+			priority: priority,
+			// Save counter value as unique id.
+			counter: ++eventsCounter
+		};
 
-			callbacks.unshift( callback );
-		},
-
-		/**
-		 * Registers a callback function to be executed on the next time the event is fired only. This is similar to
-		 * calling {@link #on} followed by {@link #off} in the callback.
-		 *
-		 * @param {String} event The name of the event.
-		 * @param {Function} callback The function to be called on event.
-		 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
-		 * event.
-		 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
-		 * Lower values are called first.
-		 */
-		once( event, callback, ctx, priority ) {
-			const onceCallback = function( event ) {
-				// Go off() at the first call.
-				event.off();
-
-				// Go with the original callback.
-				callback.apply( this, arguments );
-			};
+		// Add the callback to the list in the right priority position.
+		for ( let i = callbacks.length - 1; i >= 0; i-- ) {
+			if ( callbacks[ i ].priority <= priority ) {
+				callbacks.splice( i + 1, 0, callback );
 
-			// Make the a similar on() call, simply replacing the callback.
-			this.on( event, onceCallback, ctx, priority );
-		},
-
-		/**
-		 * Stops executing the callback on the given event.
-		 *
-		 * @param {String} event The name of the event.
-		 * @param {Function} callback The function to stop being called.
-		 * @param {Object} [ctx] The context object to be removed, pared with the given callback. To handle cases where
-		 * the same callback is used several times with different contexts.
-		 */
-		off( event, callback, ctx ) {
-			const callbacks = getCallbacksIfAny( this, event );
-
-			if ( !callbacks ) {
 				return;
 			}
+		}
 
-			for ( let i = 0; i < callbacks.length; i++ ) {
-				if ( callbacks[ i ].callback == callback ) {
-					if ( !ctx || ctx == callbacks[ i ].ctx ) {
-						// Remove the callback from the list (fixing the next index).
-						callbacks.splice( i, 1 );
-						i--;
-					}
+		callbacks.unshift( callback );
+	},
+
+	/**
+	 * Registers a callback function to be executed on the next time the event is fired only. This is similar to
+	 * calling {@link #on} followed by {@link #off} in the callback.
+	 *
+	 * @param {String} event The name of the event.
+	 * @param {Function} callback The function to be called on event.
+	 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
+	 * event.
+	 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
+	 * Lower values are called first.
+	 */
+	once( event, callback, ctx, priority ) {
+		const onceCallback = function( event ) {
+			// Go off() at the first call.
+			event.off();
+
+			// Go with the original callback.
+			callback.apply( this, arguments );
+		};
+
+		// Make the a similar on() call, simply replacing the callback.
+		this.on( event, onceCallback, ctx, priority );
+	},
+
+	/**
+	 * Stops executing the callback on the given event.
+	 *
+	 * @param {String} event The name of the event.
+	 * @param {Function} callback The function to stop being called.
+	 * @param {Object} [ctx] The context object to be removed, pared with the given callback. To handle cases where
+	 * the same callback is used several times with different contexts.
+	 */
+	off( event, callback, ctx ) {
+		const callbacks = getCallbacksIfAny( this, event );
+
+		if ( !callbacks ) {
+			return;
+		}
+
+		for ( let i = 0; i < callbacks.length; i++ ) {
+			if ( callbacks[ i ].callback == callback ) {
+				if ( !ctx || ctx == callbacks[ i ].ctx ) {
+					// Remove the callback from the list (fixing the next index).
+					callbacks.splice( i, 1 );
+					i--;
 				}
 			}
-		},
-
-		/**
-		 * Registers a callback function to be executed when an event is fired in a specific (emitter) object.
-		 *
-		 * @param {Emitter} emitter The object that fires the event.
-		 * @param {String} event The name of the event.
-		 * @param {Function} callback The function to be called on event.
-		 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to `emitter`.
-		 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
-		 * Lower values are called first.
-		 */
-		listenTo( emitter, event, callback, ctx, priority ) {
-			let emitters, emitterId, emitterInfo, eventCallbacks;
-
-			// _listeningTo contains a list of emitters that this object is listening to.
-			// This list has the following format:
-			//
-			// _listeningTo: {
-			//     emitterId: {
-			//         emitter: emitter,
-			//         callbacks: {
-			//             event1: [ callback1, callback2, ... ]
-			//             ....
-			//         }
-			//     },
-			//     ...
-			// }
-
-			if ( !( emitters = this._listeningTo ) ) {
-				emitters = this._listeningTo = {};
-			}
+		}
+	},
+
+	/**
+	 * Registers a callback function to be executed when an event is fired in a specific (emitter) object.
+	 *
+	 * @param {Emitter} emitter The object that fires the event.
+	 * @param {String} event The name of the event.
+	 * @param {Function} callback The function to be called on event.
+	 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to `emitter`.
+	 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
+	 * Lower values are called first.
+	 */
+	listenTo( emitter, event, callback, ctx, priority ) {
+		let emitters, emitterId, emitterInfo, eventCallbacks;
+
+		// _listeningTo contains a list of emitters that this object is listening to.
+		// This list has the following format:
+		//
+		// _listeningTo: {
+		//     emitterId: {
+		//         emitter: emitter,
+		//         callbacks: {
+		//             event1: [ callback1, callback2, ... ]
+		//             ....
+		//         }
+		//     },
+		//     ...
+		// }
+
+		if ( !( emitters = this._listeningTo ) ) {
+			emitters = this._listeningTo = {};
+		}
 
-			if ( !( emitterId = emitter._emitterId ) ) {
-				emitterId = emitter._emitterId = utils.uid();
-			}
+		if ( !( emitterId = emitter._emitterId ) ) {
+			emitterId = emitter._emitterId = utils.uid();
+		}
 
-			if ( !( emitterInfo = emitters[ emitterId ] ) ) {
-				emitterInfo = emitters[ emitterId ] = {
-					emitter: emitter,
-					callbacks: {}
-				};
-			}
+		if ( !( emitterInfo = emitters[ emitterId ] ) ) {
+			emitterInfo = emitters[ emitterId ] = {
+				emitter: emitter,
+				callbacks: {}
+			};
+		}
 
-			if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
-				eventCallbacks = emitterInfo.callbacks[ event ] = [];
-			}
+		if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
+			eventCallbacks = emitterInfo.callbacks[ event ] = [];
+		}
 
-			eventCallbacks.push( callback );
-
-			// Finally register the callback to the event.
-			emitter.on( event, callback, ctx, priority );
-		},
-
-		/**
-		 * Stops listening for events. It can be used at different levels:
-		 *
-		 * * To stop listening to a specific callback.
-		 * * To stop listening to a specific event.
-		 * * To stop listening to all events fired by a specific object.
-		 * * To stop listening to all events fired by all object.
-		 *
-		 * @param {Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
-		 * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
-		 * for all events from `emitter`.
-		 * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
-		 * `event`.
-		 */
-		stopListening( emitter, event, callback ) {
-			let emitters = this._listeningTo;
-			let emitterId = emitter && emitter._emitterId;
-			let emitterInfo = emitters && emitterId && emitters[ emitterId ];
-			let eventCallbacks = emitterInfo && event && emitterInfo.callbacks[ event ];
-
-			// Stop if nothing has been listened.
-			if ( !emitters || ( emitter && !emitterInfo ) || ( event && !eventCallbacks ) ) {
-				return;
-			}
+		eventCallbacks.push( callback );
+
+		// Finally register the callback to the event.
+		emitter.on( event, callback, ctx, priority );
+	},
+
+	/**
+	 * Stops listening for events. It can be used at different levels:
+	 *
+	 * * To stop listening to a specific callback.
+	 * * To stop listening to a specific event.
+	 * * To stop listening to all events fired by a specific object.
+	 * * To stop listening to all events fired by all object.
+	 *
+	 * @param {Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
+	 * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
+	 * for all events from `emitter`.
+	 * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
+	 * `event`.
+	 */
+	stopListening( emitter, event, callback ) {
+		let emitters = this._listeningTo;
+		let emitterId = emitter && emitter._emitterId;
+		let emitterInfo = emitters && emitterId && emitters[ emitterId ];
+		let eventCallbacks = emitterInfo && event && emitterInfo.callbacks[ event ];
+
+		// Stop if nothing has been listened.
+		if ( !emitters || ( emitter && !emitterInfo ) || ( event && !eventCallbacks ) ) {
+			return;
+		}
 
-			// All params provided. off() that single callback.
-			if ( callback ) {
+		// All params provided. off() that single callback.
+		if ( callback ) {
+			emitter.off( event, callback );
+		}
+		// Only `emitter` and `event` provided. off() all callbacks for that event.
+		else if ( eventCallbacks ) {
+			while ( ( callback = eventCallbacks.pop() ) ) {
 				emitter.off( event, callback );
 			}
-			// Only `emitter` and `event` provided. off() all callbacks for that event.
-			else if ( eventCallbacks ) {
-				while ( ( callback = eventCallbacks.pop() ) ) {
-					emitter.off( event, callback );
-				}
-				delete emitterInfo.callbacks[ event ];
-			}
-			// Only `emitter` provided. off() all events for that emitter.
-			else if ( emitterInfo ) {
-				for ( event in emitterInfo.callbacks ) {
-					this.stopListening( emitter, event );
-				}
-				delete emitters[ emitterId ];
-			}
-			// No params provided. off() all emitters.
-			else {
-				for ( emitterId in emitters ) {
-					this.stopListening( emitters[ emitterId ].emitter );
-				}
-				delete this._listeningTo;
+			delete emitterInfo.callbacks[ event ];
+		}
+		// Only `emitter` provided. off() all events for that emitter.
+		else if ( emitterInfo ) {
+			for ( event in emitterInfo.callbacks ) {
+				this.stopListening( emitter, event );
 			}
-		},
-
-		/**
-		 * Fires an event, executing all callbacks registered for it.
-		 *
-		 * The first parameter passed to callbacks is an {@link EventInfo} object, followed by the optional `args` provided in
-		 * the `fire()` method call.
-		 *
-		 * @param {String} event The name of the event.
-		 * @param {...*} [args] Additional arguments to be passed to the callbacks.
-		 */
-		fire( event, args ) {
-			const callbacks = getCallbacksIfAny( this, event );
-
-			if ( !callbacks ) {
-				return;
+			delete emitters[ emitterId ];
+		}
+		// No params provided. off() all emitters.
+		else {
+			for ( emitterId in emitters ) {
+				this.stopListening( emitters[ emitterId ].emitter );
 			}
+			delete this._listeningTo;
+		}
+	},
+
+	/**
+	 * Fires an event, executing all callbacks registered for it.
+	 *
+	 * The first parameter passed to callbacks is an {@link EventInfo} object, followed by the optional `args` provided in
+	 * the `fire()` method call.
+	 *
+	 * @param {String} event The name of the event.
+	 * @param {...*} [args] Additional arguments to be passed to the callbacks.
+	 */
+	fire( event, args ) {
+		const callbacks = getCallbacksIfAny( this, event );
+
+		if ( !callbacks ) {
+			return;
+		}
 
-			let eventInfo = new EventInfo( this, event );
+		let eventInfo = new EventInfo( this, event );
 
-			// Take the list of arguments to pass to the callbacks.
-			args = Array.prototype.slice.call( arguments, 1 );
-			args.unshift( eventInfo );
+		// Take the list of arguments to pass to the callbacks.
+		args = Array.prototype.slice.call( arguments, 1 );
+		args.unshift( eventInfo );
 
-			// Save how many callbacks were added at the moment when the event has been fired.
-			const counter = eventsCounter;
+		// Save how many callbacks were added at the moment when the event has been fired.
+		const counter = eventsCounter;
 
-			for ( let i = 0; i < callbacks.length; i++ ) {
-				// Filter out callbacks that have been added after event has been fired.
-				if ( callbacks[ i ].counter > counter ) {
-					continue;
-				}
+		for ( let i = 0; i < callbacks.length; i++ ) {
+			// Filter out callbacks that have been added after event has been fired.
+			if ( callbacks[ i ].counter > counter ) {
+				continue;
+			}
 
-				callbacks[ i ].callback.apply( callbacks[ i ].ctx, args );
+			callbacks[ i ].callback.apply( callbacks[ i ].ctx, args );
 
-				// Remove the callback from future requests if off() has been called.
-				if ( eventInfo.off.called ) {
-					// Remove the called mark for the next calls.
-					delete eventInfo.off.called;
+			// Remove the callback from future requests if off() has been called.
+			if ( eventInfo.off.called ) {
+				// Remove the called mark for the next calls.
+				delete eventInfo.off.called;
 
-					// Remove the callback from the list (fixing the next index).
-					callbacks.splice( i, 1 );
-					i--;
-				}
+				// Remove the callback from the list (fixing the next index).
+				callbacks.splice( i, 1 );
+				i--;
+			}
 
-				// Do not execute next callbacks if stop() was called.
-				if ( eventInfo.stop.called ) {
-					break;
-				}
+			// Do not execute next callbacks if stop() was called.
+			if ( eventInfo.stop.called ) {
+				break;
 			}
 		}
-	};
-
-	return EmitterMixin;
+	}
+};
 
-	// Gets the internal events hash of a give object.
-	function getEvents( source ) {
-		if ( !source._events ) {
-			Object.defineProperty( source, '_events', {
-				value: {}
-			} );
-		}
+export default EmitterMixin;
 
-		return source._events;
+// Gets the internal events hash of a give object.
+function getEvents( source ) {
+	if ( !source._events ) {
+		Object.defineProperty( source, '_events', {
+			value: {}
+		} );
 	}
 
-	// Gets the list of callbacks for a given event.
-	function getCallbacks( source, eventName ) {
-		const events = getEvents( source );
+	return source._events;
+}
 
-		if ( !events[ eventName ] ) {
-			events[ eventName ] = [];
-		}
+// Gets the list of callbacks for a given event.
+function getCallbacks( source, eventName ) {
+	const events = getEvents( source );
 
-		return events[ eventName ];
+	if ( !events[ eventName ] ) {
+		events[ eventName ] = [];
 	}
 
-	// Get the list of callbacks for a given event only if there is any available.
-	function getCallbacksIfAny( source, event ) {
-		let callbacks;
+	return events[ eventName ];
+}
 
-		if ( !source._events || !( callbacks = source._events[ event ] ) || !callbacks.length ) {
-			return null;
-		}
+// Get the list of callbacks for a given event only if there is any available.
+function getCallbacksIfAny( source, event ) {
+	let callbacks;
 
-		return callbacks;
+	if ( !source._events || !( callbacks = source._events[ event ] ) || !callbacks.length ) {
+		return null;
 	}
-} );
+
+	return callbacks;
+}

+ 30 - 32
packages/ckeditor5-engine/src/eventinfo.js

@@ -5,6 +5,8 @@
 
 'use strict';
 
+import utils from './utils.js';
+
 /**
  * The event object passed to event callbacks. It is used to provide information about the event as well as a tool to
  * manipulate it.
@@ -12,36 +14,32 @@
  * @class EventInfo
  */
 
-CKEDITOR.define( [ 'utils' ], ( utils ) => {
-	class EventInfo {
-		constructor( source, name ) {
-			/**
-			 * The object that fired the event.
-			 */
-			this.source = source;
-
-			/**
-			 * The event name.
-			 */
-			this.name = name;
-
-			// The following methods are defined in the constructor because they must be re-created per instance.
-
-			/**
-			 * Stops the event emitter to call further callbacks for this event interaction.
-			 *
-			 * @method
-			 */
-			this.stop = utils.spy();
-
-			/**
-			 * Removes the current callback from future interactions of this event.
-			 *
-			 * @method
-			 */
-			this.off = utils.spy();
-		}
+export default class EventInfo {
+	constructor( source, name ) {
+		/**
+		 * The object that fired the event.
+		 */
+		this.source = source;
+
+		/**
+		 * The event name.
+		 */
+		this.name = name;
+
+		// The following methods are defined in the constructor because they must be re-created per instance.
+
+		/**
+		 * Stops the event emitter to call further callbacks for this event interaction.
+		 *
+		 * @method
+		 */
+		this.stop = utils.spy();
+
+		/**
+		 * Removes the current callback from future interactions of this event.
+		 *
+		 * @method
+		 */
+		this.off = utils.spy();
 	}
-
-	return EventInfo;
-} );
+}

+ 5 - 0
packages/ckeditor5-engine/src/lib/lodash/array.js

@@ -0,0 +1,5 @@
+import last from './array/last';
+
+export default {
+  'last': last
+};

+ 28 - 0
packages/ckeditor5-engine/src/lib/lodash/array/last.js

@@ -0,0 +1,28 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+
+/**
+ * Gets the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the last element of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ */
+function last(array) {
+  var length = array ? array.length : 0;
+  return length ? array[length - 1] : undefined;
+}
+
+export default last;

+ 5 - 0
packages/ckeditor5-engine/src/lib/lodash/function.js

@@ -0,0 +1,5 @@
+import restParam from './function/restParam';
+
+export default {
+  'restParam': restParam
+};

+ 58 - 0
packages/ckeditor5-engine/src/lib/lodash/function/restParam.js

@@ -0,0 +1,58 @@
+/** Used as the `TypeError` message for "Functions" methods. */
+var FUNC_ERROR_TEXT = 'Expected a function';
+
+/* Native method references for those with the same name as other `lodash` methods. */
+var nativeMax = Math.max;
+
+/**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * created function and arguments from `start` and beyond provided as an array.
+ *
+ * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters).
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.restParam(function(what, names) {
+ *   return what + ' ' + _.initial(names).join(', ') +
+ *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+ * });
+ *
+ * say('hello', 'fred', 'barney', 'pebbles');
+ * // => 'hello fred, barney, & pebbles'
+ */
+function restParam(func, start) {
+  if (typeof func != 'function') {
+    throw new TypeError(FUNC_ERROR_TEXT);
+  }
+  start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
+  return function() {
+    var args = arguments,
+        index = -1,
+        length = nativeMax(args.length - start, 0),
+        rest = Array(length);
+
+    while (++index < length) {
+      rest[index] = args[start + index];
+    }
+    switch (start) {
+      case 0: return func.call(this, rest);
+      case 1: return func.call(this, args[0], rest);
+      case 2: return func.call(this, args[0], args[1], rest);
+    }
+    var otherArgs = Array(start + 1);
+    index = -1;
+    while (++index < start) {
+      otherArgs[index] = args[index];
+    }
+    otherArgs[start] = rest;
+    return func.apply(this, otherArgs);
+  };
+}
+
+export default restParam;

+ 20 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/arrayCopy.js

@@ -0,0 +1,20 @@
+/**
+ * Copies the values of `source` to `array`.
+ *
+ * @private
+ * @param {Array} source The array to copy values from.
+ * @param {Array} [array=[]] The array to copy values to.
+ * @returns {Array} Returns `array`.
+ */
+function arrayCopy(source, array) {
+  var index = -1,
+      length = source.length;
+
+  array || (array = Array(length));
+  while (++index < length) {
+    array[index] = source[index];
+  }
+  return array;
+}
+
+export default arrayCopy;

+ 22 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/arrayEach.js

@@ -0,0 +1,22 @@
+/**
+ * A specialized version of `_.forEach` for arrays without support for callback
+ * shorthands and `this` binding.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
+function arrayEach(array, iteratee) {
+  var index = -1,
+      length = array.length;
+
+  while (++index < length) {
+    if (iteratee(array[index], index, array) === false) {
+      break;
+    }
+  }
+  return array;
+}
+
+export default arrayEach;

+ 23 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/arraySome.js

@@ -0,0 +1,23 @@
+/**
+ * A specialized version of `_.some` for arrays without support for callback
+ * shorthands and `this` binding.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ *  else `false`.
+ */
+function arraySome(array, predicate) {
+  var index = -1,
+      length = array.length;
+
+  while (++index < length) {
+    if (predicate(array[index], index, array)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+export default arraySome;

+ 32 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/assignWith.js

@@ -0,0 +1,32 @@
+import keys from '../object/keys';
+
+/**
+ * A specialized version of `_.assign` for customizing assigned values without
+ * support for argument juggling, multiple sources, and `this` binding `customizer`
+ * functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {Function} customizer The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ */
+function assignWith(object, source, customizer) {
+  var index = -1,
+      props = keys(source),
+      length = props.length;
+
+  while (++index < length) {
+    var key = props[index],
+        value = object[key],
+        result = customizer(value, source[key], key, object, source);
+
+    if ((result === result ? (result !== value) : (value === value)) ||
+        (value === undefined && !(key in object))) {
+      object[key] = result;
+    }
+  }
+  return object;
+}
+
+export default assignWith;

+ 19 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseAssign.js

@@ -0,0 +1,19 @@
+import baseCopy from './baseCopy';
+import keys from '../object/keys';
+
+/**
+ * The base implementation of `_.assign` without support for argument juggling,
+ * multiple sources, and `customizer` functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @returns {Object} Returns `object`.
+ */
+function baseAssign(object, source) {
+  return source == null
+    ? object
+    : baseCopy(source, keys(source), object);
+}
+
+export default baseAssign;

+ 128 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseClone.js

@@ -0,0 +1,128 @@
+import arrayCopy from './arrayCopy';
+import arrayEach from './arrayEach';
+import baseAssign from './baseAssign';
+import baseForOwn from './baseForOwn';
+import initCloneArray from './initCloneArray';
+import initCloneByTag from './initCloneByTag';
+import initCloneObject from './initCloneObject';
+import isArray from '../lang/isArray';
+import isObject from '../lang/isObject';
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+    arrayTag = '[object Array]',
+    boolTag = '[object Boolean]',
+    dateTag = '[object Date]',
+    errorTag = '[object Error]',
+    funcTag = '[object Function]',
+    mapTag = '[object Map]',
+    numberTag = '[object Number]',
+    objectTag = '[object Object]',
+    regexpTag = '[object RegExp]',
+    setTag = '[object Set]',
+    stringTag = '[object String]',
+    weakMapTag = '[object WeakMap]';
+
+var arrayBufferTag = '[object ArrayBuffer]',
+    float32Tag = '[object Float32Array]',
+    float64Tag = '[object Float64Array]',
+    int8Tag = '[object Int8Array]',
+    int16Tag = '[object Int16Array]',
+    int32Tag = '[object Int32Array]',
+    uint8Tag = '[object Uint8Array]',
+    uint8ClampedTag = '[object Uint8ClampedArray]',
+    uint16Tag = '[object Uint16Array]',
+    uint32Tag = '[object Uint32Array]';
+
+/** Used to identify `toStringTag` values supported by `_.clone`. */
+var cloneableTags = {};
+cloneableTags[argsTag] = cloneableTags[arrayTag] =
+cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
+cloneableTags[dateTag] = cloneableTags[float32Tag] =
+cloneableTags[float64Tag] = cloneableTags[int8Tag] =
+cloneableTags[int16Tag] = cloneableTags[int32Tag] =
+cloneableTags[numberTag] = cloneableTags[objectTag] =
+cloneableTags[regexpTag] = cloneableTags[stringTag] =
+cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+cloneableTags[errorTag] = cloneableTags[funcTag] =
+cloneableTags[mapTag] = cloneableTags[setTag] =
+cloneableTags[weakMapTag] = false;
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * The base implementation of `_.clone` without support for argument juggling
+ * and `this` binding `customizer` functions.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @param {Function} [customizer] The function to customize cloning values.
+ * @param {string} [key] The key of `value`.
+ * @param {Object} [object] The object `value` belongs to.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates clones with source counterparts.
+ * @returns {*} Returns the cloned value.
+ */
+function baseClone(value, isDeep, customizer, key, object, stackA, stackB) {
+  var result;
+  if (customizer) {
+    result = object ? customizer(value, key, object) : customizer(value);
+  }
+  if (result !== undefined) {
+    return result;
+  }
+  if (!isObject(value)) {
+    return value;
+  }
+  var isArr = isArray(value);
+  if (isArr) {
+    result = initCloneArray(value);
+    if (!isDeep) {
+      return arrayCopy(value, result);
+    }
+  } else {
+    var tag = objToString.call(value),
+        isFunc = tag == funcTag;
+
+    if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+      result = initCloneObject(isFunc ? {} : value);
+      if (!isDeep) {
+        return baseAssign(result, value);
+      }
+    } else {
+      return cloneableTags[tag]
+        ? initCloneByTag(value, tag, isDeep)
+        : (object ? value : {});
+    }
+  }
+  // Check for circular references and return its corresponding clone.
+  stackA || (stackA = []);
+  stackB || (stackB = []);
+
+  var length = stackA.length;
+  while (length--) {
+    if (stackA[length] == value) {
+      return stackB[length];
+    }
+  }
+  // Add the source value to the stack of traversed objects and associate it with its clone.
+  stackA.push(value);
+  stackB.push(result);
+
+  // Recursively populate clone (susceptible to call stack limits).
+  (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
+    result[key] = baseClone(subValue, isDeep, customizer, key, value, stackA, stackB);
+  });
+  return result;
+}
+
+export default baseClone;

+ 23 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseCopy.js

@@ -0,0 +1,23 @@
+/**
+ * Copies properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property names to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @returns {Object} Returns `object`.
+ */
+function baseCopy(source, props, object) {
+  object || (object = {});
+
+  var index = -1,
+      length = props.length;
+
+  while (++index < length) {
+    var key = props[index];
+    object[key] = source[key];
+  }
+  return object;
+}
+
+export default baseCopy;

+ 17 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseFor.js

@@ -0,0 +1,17 @@
+import createBaseFor from './createBaseFor';
+
+/**
+ * The base implementation of `baseForIn` and `baseForOwn` which iterates
+ * over `object` properties returned by `keysFunc` invoking `iteratee` for
+ * each property. Iteratee functions may exit iteration early by explicitly
+ * returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+var baseFor = createBaseFor();
+
+export default baseFor;

+ 17 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseForIn.js

@@ -0,0 +1,17 @@
+import baseFor from './baseFor';
+import keysIn from '../object/keysIn';
+
+/**
+ * The base implementation of `_.forIn` without support for callback
+ * shorthands and `this` binding.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+function baseForIn(object, iteratee) {
+  return baseFor(object, iteratee, keysIn);
+}
+
+export default baseForIn;

+ 17 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseForOwn.js

@@ -0,0 +1,17 @@
+import baseFor from './baseFor';
+import keys from '../object/keys';
+
+/**
+ * The base implementation of `_.forOwn` without support for callback
+ * shorthands and `this` binding.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+function baseForOwn(object, iteratee) {
+  return baseFor(object, iteratee, keys);
+}
+
+export default baseForOwn;

+ 28 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseIsEqual.js

@@ -0,0 +1,28 @@
+import baseIsEqualDeep from './baseIsEqualDeep';
+import isObject from '../lang/isObject';
+import isObjectLike from './isObjectLike';
+
+/**
+ * The base implementation of `_.isEqual` without support for `this` binding
+ * `customizer` functions.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize comparing values.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
+ * @param {Array} [stackA] Tracks traversed `value` objects.
+ * @param {Array} [stackB] Tracks traversed `other` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
+function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
+  if (value === other) {
+    return true;
+  }
+  if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+    return value !== value && other !== other;
+  }
+  return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
+}
+
+export default baseIsEqual;

+ 102 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseIsEqualDeep.js

@@ -0,0 +1,102 @@
+import equalArrays from './equalArrays';
+import equalByTag from './equalByTag';
+import equalObjects from './equalObjects';
+import isArray from '../lang/isArray';
+import isTypedArray from '../lang/isTypedArray';
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+    arrayTag = '[object Array]',
+    objectTag = '[object Object]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * A specialized version of `baseIsEqual` for arrays and objects which performs
+ * deep comparisons and tracks traversed objects enabling objects with circular
+ * references to be compared.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} [customizer] The function to customize comparing objects.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
+ * @param {Array} [stackA=[]] Tracks traversed `value` objects.
+ * @param {Array} [stackB=[]] Tracks traversed `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
+  var objIsArr = isArray(object),
+      othIsArr = isArray(other),
+      objTag = arrayTag,
+      othTag = arrayTag;
+
+  if (!objIsArr) {
+    objTag = objToString.call(object);
+    if (objTag == argsTag) {
+      objTag = objectTag;
+    } else if (objTag != objectTag) {
+      objIsArr = isTypedArray(object);
+    }
+  }
+  if (!othIsArr) {
+    othTag = objToString.call(other);
+    if (othTag == argsTag) {
+      othTag = objectTag;
+    } else if (othTag != objectTag) {
+      othIsArr = isTypedArray(other);
+    }
+  }
+  var objIsObj = objTag == objectTag,
+      othIsObj = othTag == objectTag,
+      isSameTag = objTag == othTag;
+
+  if (isSameTag && !(objIsArr || objIsObj)) {
+    return equalByTag(object, other, objTag);
+  }
+  if (!isLoose) {
+    var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+        othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+    if (objIsWrapped || othIsWrapped) {
+      return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
+    }
+  }
+  if (!isSameTag) {
+    return false;
+  }
+  // Assume cyclic values are equal.
+  // For more information on detecting circular references see https://es5.github.io/#JO.
+  stackA || (stackA = []);
+  stackB || (stackB = []);
+
+  var length = stackA.length;
+  while (length--) {
+    if (stackA[length] == object) {
+      return stackB[length] == other;
+    }
+  }
+  // Add `object` and `other` to the stack of traversed objects.
+  stackA.push(object);
+  stackB.push(other);
+
+  var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
+
+  stackA.pop();
+  stackB.pop();
+
+  return result;
+}
+
+export default baseIsEqualDeep;

+ 14 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/baseProperty.js

@@ -0,0 +1,14 @@
+/**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new function.
+ */
+function baseProperty(key) {
+  return function(object) {
+    return object == null ? undefined : object[key];
+  };
+}
+
+export default baseProperty;

+ 39 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/bindCallback.js

@@ -0,0 +1,39 @@
+import identity from '../utility/identity';
+
+/**
+ * A specialized version of `baseCallback` which only supports `this` binding
+ * and specifying the number of arguments to provide to `func`.
+ *
+ * @private
+ * @param {Function} func The function to bind.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {number} [argCount] The number of arguments to provide to `func`.
+ * @returns {Function} Returns the callback.
+ */
+function bindCallback(func, thisArg, argCount) {
+  if (typeof func != 'function') {
+    return identity;
+  }
+  if (thisArg === undefined) {
+    return func;
+  }
+  switch (argCount) {
+    case 1: return function(value) {
+      return func.call(thisArg, value);
+    };
+    case 3: return function(value, index, collection) {
+      return func.call(thisArg, value, index, collection);
+    };
+    case 4: return function(accumulator, value, index, collection) {
+      return func.call(thisArg, accumulator, value, index, collection);
+    };
+    case 5: return function(value, other, key, object, source) {
+      return func.call(thisArg, value, other, key, object, source);
+    };
+  }
+  return function() {
+    return func.apply(thisArg, arguments);
+  };
+}
+
+export default bindCallback;

+ 22 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/bufferClone.js

@@ -0,0 +1,22 @@
+import root from './root';
+
+/** Native method references. */
+var ArrayBuffer = root.ArrayBuffer,
+    Uint8Array = root.Uint8Array;
+
+/**
+ * Creates a clone of the given array buffer.
+ *
+ * @private
+ * @param {ArrayBuffer} buffer The array buffer to clone.
+ * @returns {ArrayBuffer} Returns the cloned array buffer.
+ */
+function bufferClone(buffer) {
+  var result = new ArrayBuffer(buffer.byteLength),
+      view = new Uint8Array(result);
+
+  view.set(new Uint8Array(buffer));
+  return result;
+}
+
+export default bufferClone;

+ 41 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/createAssigner.js

@@ -0,0 +1,41 @@
+import bindCallback from './bindCallback';
+import isIterateeCall from './isIterateeCall';
+import restParam from '../function/restParam';
+
+/**
+ * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
+ *
+ * @private
+ * @param {Function} assigner The function to assign values.
+ * @returns {Function} Returns the new assigner function.
+ */
+function createAssigner(assigner) {
+  return restParam(function(object, sources) {
+    var index = -1,
+        length = object == null ? 0 : sources.length,
+        customizer = length > 2 ? sources[length - 2] : undefined,
+        guard = length > 2 ? sources[2] : undefined,
+        thisArg = length > 1 ? sources[length - 1] : undefined;
+
+    if (typeof customizer == 'function') {
+      customizer = bindCallback(customizer, thisArg, 5);
+      length -= 2;
+    } else {
+      customizer = typeof thisArg == 'function' ? thisArg : undefined;
+      length -= (customizer ? 1 : 0);
+    }
+    if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+      customizer = length < 3 ? undefined : customizer;
+      length = 1;
+    }
+    while (++index < length) {
+      var source = sources[index];
+      if (source) {
+        assigner(object, source, customizer);
+      }
+    }
+    return object;
+  });
+}
+
+export default createAssigner;

+ 27 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/createBaseFor.js

@@ -0,0 +1,27 @@
+import toObject from './toObject';
+
+/**
+ * Creates a base function for `_.forIn` or `_.forInRight`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+function createBaseFor(fromRight) {
+  return function(object, iteratee, keysFunc) {
+    var iterable = toObject(object),
+        props = keysFunc(object),
+        length = props.length,
+        index = fromRight ? length : -1;
+
+    while ((fromRight ? index-- : ++index < length)) {
+      var key = props[index];
+      if (iteratee(iterable[key], key, iterable) === false) {
+        break;
+      }
+    }
+    return object;
+  };
+}
+
+export default createBaseFor;

+ 51 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/equalArrays.js

@@ -0,0 +1,51 @@
+import arraySome from './arraySome';
+
+/**
+ * A specialized version of `baseIsEqualDeep` for arrays with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Array} array The array to compare.
+ * @param {Array} other The other array to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} [customizer] The function to customize comparing arrays.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
+ * @param {Array} [stackA] Tracks traversed `value` objects.
+ * @param {Array} [stackB] Tracks traversed `other` objects.
+ * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+ */
+function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
+  var index = -1,
+      arrLength = array.length,
+      othLength = other.length;
+
+  if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
+    return false;
+  }
+  // Ignore non-index properties.
+  while (++index < arrLength) {
+    var arrValue = array[index],
+        othValue = other[index],
+        result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined;
+
+    if (result !== undefined) {
+      if (result) {
+        continue;
+      }
+      return false;
+    }
+    // Recursively compare arrays (susceptible to call stack limits).
+    if (isLoose) {
+      if (!arraySome(other, function(othValue) {
+            return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
+          })) {
+        return false;
+      }
+    } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+export default equalArrays;

+ 48 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/equalByTag.js

@@ -0,0 +1,48 @@
+/** `Object#toString` result references. */
+var boolTag = '[object Boolean]',
+    dateTag = '[object Date]',
+    errorTag = '[object Error]',
+    numberTag = '[object Number]',
+    regexpTag = '[object RegExp]',
+    stringTag = '[object String]';
+
+/**
+ * A specialized version of `baseIsEqualDeep` for comparing objects of
+ * the same `toStringTag`.
+ *
+ * **Note:** This function only supports comparing values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {string} tag The `toStringTag` of the objects to compare.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+function equalByTag(object, other, tag) {
+  switch (tag) {
+    case boolTag:
+    case dateTag:
+      // Coerce dates and booleans to numbers, dates to milliseconds and booleans
+      // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.
+      return +object == +other;
+
+    case errorTag:
+      return object.name == other.name && object.message == other.message;
+
+    case numberTag:
+      // Treat `NaN` vs. `NaN` as equal.
+      return (object != +object)
+        ? other != +other
+        : object == +other;
+
+    case regexpTag:
+    case stringTag:
+      // Coerce regexes to strings and treat strings primitives and string
+      // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.
+      return object == (other + '');
+  }
+  return false;
+}
+
+export default equalByTag;

+ 67 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/equalObjects.js

@@ -0,0 +1,67 @@
+import keys from '../object/keys';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * A specialized version of `baseIsEqualDeep` for objects with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} [customizer] The function to customize comparing values.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
+ * @param {Array} [stackA] Tracks traversed `value` objects.
+ * @param {Array} [stackB] Tracks traversed `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
+  var objProps = keys(object),
+      objLength = objProps.length,
+      othProps = keys(other),
+      othLength = othProps.length;
+
+  if (objLength != othLength && !isLoose) {
+    return false;
+  }
+  var index = objLength;
+  while (index--) {
+    var key = objProps[index];
+    if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {
+      return false;
+    }
+  }
+  var skipCtor = isLoose;
+  while (++index < objLength) {
+    key = objProps[index];
+    var objValue = object[key],
+        othValue = other[key],
+        result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined;
+
+    // Recursively compare objects (susceptible to call stack limits).
+    if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {
+      return false;
+    }
+    skipCtor || (skipCtor = key == 'constructor');
+  }
+  if (!skipCtor) {
+    var objCtor = object.constructor,
+        othCtor = other.constructor;
+
+    // Non `Object` object instances with different constructors are not equal.
+    if (objCtor != othCtor &&
+        ('constructor' in object && 'constructor' in other) &&
+        !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+          typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+export default equalObjects;

+ 15 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/getLength.js

@@ -0,0 +1,15 @@
+import baseProperty from './baseProperty';
+
+/**
+ * Gets the "length" property value of `object`.
+ *
+ * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
+ * that affects Safari on at least iOS 8.1-8.3 ARM64.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {*} Returns the "length" value.
+ */
+var getLength = baseProperty('length');
+
+export default getLength;

+ 16 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/getNative.js

@@ -0,0 +1,16 @@
+import isNative from '../lang/isNative';
+
+/**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+function getNative(object, key) {
+  var value = object == null ? undefined : object[key];
+  return isNative(value) ? value : undefined;
+}
+
+export default getNative;

+ 26 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/initCloneArray.js

@@ -0,0 +1,26 @@
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Initializes an array clone.
+ *
+ * @private
+ * @param {Array} array The array to clone.
+ * @returns {Array} Returns the initialized clone.
+ */
+function initCloneArray(array) {
+  var length = array.length,
+      result = new array.constructor(length);
+
+  // Add array properties assigned by `RegExp#exec`.
+  if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+    result.index = array.index;
+    result.input = array.input;
+  }
+  return result;
+}
+
+export default initCloneArray;

+ 63 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/initCloneByTag.js

@@ -0,0 +1,63 @@
+import bufferClone from './bufferClone';
+
+/** `Object#toString` result references. */
+var boolTag = '[object Boolean]',
+    dateTag = '[object Date]',
+    numberTag = '[object Number]',
+    regexpTag = '[object RegExp]',
+    stringTag = '[object String]';
+
+var arrayBufferTag = '[object ArrayBuffer]',
+    float32Tag = '[object Float32Array]',
+    float64Tag = '[object Float64Array]',
+    int8Tag = '[object Int8Array]',
+    int16Tag = '[object Int16Array]',
+    int32Tag = '[object Int32Array]',
+    uint8Tag = '[object Uint8Array]',
+    uint8ClampedTag = '[object Uint8ClampedArray]',
+    uint16Tag = '[object Uint16Array]',
+    uint32Tag = '[object Uint32Array]';
+
+/** Used to match `RegExp` flags from their coerced string values. */
+var reFlags = /\w*$/;
+
+/**
+ * Initializes an object clone based on its `toStringTag`.
+ *
+ * **Note:** This function only supports cloning values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @param {string} tag The `toStringTag` of the object to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+function initCloneByTag(object, tag, isDeep) {
+  var Ctor = object.constructor;
+  switch (tag) {
+    case arrayBufferTag:
+      return bufferClone(object);
+
+    case boolTag:
+    case dateTag:
+      return new Ctor(+object);
+
+    case float32Tag: case float64Tag:
+    case int8Tag: case int16Tag: case int32Tag:
+    case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+      var buffer = object.buffer;
+      return new Ctor(isDeep ? bufferClone(buffer) : buffer, object.byteOffset, object.length);
+
+    case numberTag:
+    case stringTag:
+      return new Ctor(object);
+
+    case regexpTag:
+      var result = new Ctor(object.source, reFlags.exec(object));
+      result.lastIndex = object.lastIndex;
+  }
+  return result;
+}
+
+export default initCloneByTag;

+ 16 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/initCloneObject.js

@@ -0,0 +1,16 @@
+/**
+ * Initializes an object clone.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+function initCloneObject(object) {
+  var Ctor = object.constructor;
+  if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
+    Ctor = Object;
+  }
+  return new Ctor;
+}
+
+export default initCloneObject;

+ 15 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/isArrayLike.js

@@ -0,0 +1,15 @@
+import getLength from './getLength';
+import isLength from './isLength';
+
+/**
+ * Checks if `value` is array-like.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ */
+function isArrayLike(value) {
+  return value != null && isLength(getLength(value));
+}
+
+export default isArrayLike;

+ 24 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/isIndex.js

@@ -0,0 +1,24 @@
+/** Used to detect unsigned integer values. */
+var reIsUint = /^\d+$/;
+
+/**
+ * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
+ * of an array-like value.
+ */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/**
+ * Checks if `value` is a valid array-like index.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+ */
+function isIndex(value, length) {
+  value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
+  length = length == null ? MAX_SAFE_INTEGER : length;
+  return value > -1 && value % 1 == 0 && value < length;
+}
+
+export default isIndex;

+ 28 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/isIterateeCall.js

@@ -0,0 +1,28 @@
+import isArrayLike from './isArrayLike';
+import isIndex from './isIndex';
+import isObject from '../lang/isObject';
+
+/**
+ * Checks if the provided arguments are from an iteratee call.
+ *
+ * @private
+ * @param {*} value The potential iteratee value argument.
+ * @param {*} index The potential iteratee index or key argument.
+ * @param {*} object The potential iteratee object argument.
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
+ */
+function isIterateeCall(value, index, object) {
+  if (!isObject(object)) {
+    return false;
+  }
+  var type = typeof index;
+  if (type == 'number'
+      ? (isArrayLike(object) && isIndex(index, object.length))
+      : (type == 'string' && index in object)) {
+    var other = object[index];
+    return value === value ? (value === other) : (other !== other);
+  }
+  return false;
+}
+
+export default isIterateeCall;

+ 20 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/isLength.js

@@ -0,0 +1,20 @@
+/**
+ * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
+ * of an array-like value.
+ */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ */
+function isLength(value) {
+  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+}
+
+export default isLength;

+ 12 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/isObjectLike.js

@@ -0,0 +1,12 @@
+/**
+ * Checks if `value` is object-like.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ */
+function isObjectLike(value) {
+  return !!value && typeof value == 'object';
+}
+
+export default isObjectLike;

+ 30 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/root.js

@@ -0,0 +1,30 @@
+/** Used to determine if values are of the language type `Object`. */
+var objectTypes = {
+  'function': true,
+  'object': true
+};
+
+/** Detect free variable `exports`. */
+var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+/** Detect free variable `module`. */
+var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+/** Detect free variable `global` from Node.js. */
+var freeGlobal = freeExports && freeModule && typeof global == 'object' && global && global.Object && global;
+
+/** Detect free variable `self`. */
+var freeSelf = objectTypes[typeof self] && self && self.Object && self;
+
+/** Detect free variable `window`. */
+var freeWindow = objectTypes[typeof window] && window && window.Object && window;
+
+/**
+ * Used as a reference to the global object.
+ *
+ * The `this` value is used if it's the global object to avoid Greasemonkey's
+ * restricted `window` object, otherwise the `window` object is used.
+ */
+var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;
+
+export default root;

+ 41 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/shimKeys.js

@@ -0,0 +1,41 @@
+import isArguments from '../lang/isArguments';
+import isArray from '../lang/isArray';
+import isIndex from './isIndex';
+import isLength from './isLength';
+import keysIn from '../object/keysIn';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * A fallback implementation of `Object.keys` which creates an array of the
+ * own enumerable property names of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+function shimKeys(object) {
+  var props = keysIn(object),
+      propsLength = props.length,
+      length = propsLength && object.length;
+
+  var allowIndexes = !!length && isLength(length) &&
+    (isArray(object) || isArguments(object));
+
+  var index = -1,
+      result = [];
+
+  while (++index < propsLength) {
+    var key = props[index];
+    if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
+      result.push(key);
+    }
+  }
+  return result;
+}
+
+export default shimKeys;

+ 14 - 0
packages/ckeditor5-engine/src/lib/lodash/internal/toObject.js

@@ -0,0 +1,14 @@
+import isObject from '../lang/isObject';
+
+/**
+ * Converts `value` to an object if it's not one.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {Object} Returns the object.
+ */
+function toObject(value) {
+  return isObject(value) ? value : Object(value);
+}
+
+export default toObject;

+ 23 - 0
packages/ckeditor5-engine/src/lib/lodash/lang.js

@@ -0,0 +1,23 @@
+import clone from './lang/clone';
+import eq from './lang/eq';
+import isArguments from './lang/isArguments';
+import isArray from './lang/isArray';
+import isEqual from './lang/isEqual';
+import isFunction from './lang/isFunction';
+import isNative from './lang/isNative';
+import isObject from './lang/isObject';
+import isPlainObject from './lang/isPlainObject';
+import isTypedArray from './lang/isTypedArray';
+
+export default {
+  'clone': clone,
+  'eq': eq,
+  'isArguments': isArguments,
+  'isArray': isArray,
+  'isEqual': isEqual,
+  'isFunction': isFunction,
+  'isNative': isNative,
+  'isObject': isObject,
+  'isPlainObject': isPlainObject,
+  'isTypedArray': isTypedArray
+};

+ 78 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/clone.js

@@ -0,0 +1,78 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+import baseClone from '../internal/baseClone';
+import bindCallback from '../internal/bindCallback';
+import isIterateeCall from '../internal/isIterateeCall';
+
+/**
+ * Creates a clone of `value`. If `isDeep` is `true` nested objects are cloned,
+ * otherwise they are assigned by reference. If `customizer` is provided it's
+ * invoked to produce the cloned values. If `customizer` returns `undefined`
+ * cloning is handled by the method instead. The `customizer` is bound to
+ * `thisArg` and invoked with up to three argument; (value [, index|key, object]).
+ *
+ * **Note:** This method is loosely based on the
+ * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
+ * The enumerable properties of `arguments` objects and objects created by
+ * constructors other than `Object` are cloned to plain `Object` objects. An
+ * empty object is returned for uncloneable values such as functions, DOM nodes,
+ * Maps, Sets, and WeakMaps.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @param {Function} [customizer] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `customizer`.
+ * @returns {*} Returns the cloned value.
+ * @example
+ *
+ * var users = [
+ *   { 'user': 'barney' },
+ *   { 'user': 'fred' }
+ * ];
+ *
+ * var shallow = _.clone(users);
+ * shallow[0] === users[0];
+ * // => true
+ *
+ * var deep = _.clone(users, true);
+ * deep[0] === users[0];
+ * // => false
+ *
+ * // using a customizer callback
+ * var el = _.clone(document.body, function(value) {
+ *   if (_.isElement(value)) {
+ *     return value.cloneNode(false);
+ *   }
+ * });
+ *
+ * el === document.body
+ * // => false
+ * el.nodeName
+ * // => BODY
+ * el.childNodes.length;
+ * // => 0
+ */
+function clone(value, isDeep, customizer, thisArg) {
+  if (isDeep && typeof isDeep != 'boolean' && isIterateeCall(value, isDeep, customizer)) {
+    isDeep = false;
+  }
+  else if (typeof isDeep == 'function') {
+    thisArg = customizer;
+    customizer = isDeep;
+    isDeep = false;
+  }
+  return typeof customizer == 'function'
+    ? baseClone(value, isDeep, bindCallback(customizer, thisArg, 3))
+    : baseClone(value, isDeep);
+}
+
+export default clone;

+ 2 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/eq.js

@@ -0,0 +1,2 @@
+import isEqual from './isEqual'
+export default isEqual;

+ 34 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isArguments.js

@@ -0,0 +1,34 @@
+import isArrayLike from '../internal/isArrayLike';
+import isObjectLike from '../internal/isObjectLike';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/** Native method references. */
+var propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+/**
+ * Checks if `value` is classified as an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+function isArguments(value) {
+  return isObjectLike(value) && isArrayLike(value) &&
+    hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
+}
+
+export default isArguments;

+ 48 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isArray.js

@@ -0,0 +1,48 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+import getNative from '../internal/getNative';
+import isLength from '../internal/isLength';
+import isObjectLike from '../internal/isObjectLike';
+
+/** `Object#toString` result references. */
+var arrayTag = '[object Array]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/* Native method references for those with the same name as other `lodash` methods. */
+var nativeIsArray = getNative(Array, 'isArray');
+
+/**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(function() { return arguments; }());
+ * // => false
+ */
+var isArray = nativeIsArray || function(value) {
+  return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
+};
+
+export default isArray;

+ 62 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isEqual.js

@@ -0,0 +1,62 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+import baseIsEqual from '../internal/baseIsEqual';
+import bindCallback from '../internal/bindCallback';
+
+/**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent. If `customizer` is provided it's invoked to compare values.
+ * If `customizer` returns `undefined` comparisons are handled by the method
+ * instead. The `customizer` is bound to `thisArg` and invoked with up to
+ * three arguments: (value, other [, index|key]).
+ *
+ * **Note:** This method supports comparing arrays, booleans, `Date` objects,
+ * numbers, `Object` objects, regexes, and strings. Objects are compared by
+ * their own, not inherited, enumerable properties. Functions and DOM nodes
+ * are **not** supported. Provide a customizer function to extend support
+ * for comparing other values.
+ *
+ * @static
+ * @memberOf _
+ * @alias eq
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize value comparisons.
+ * @param {*} [thisArg] The `this` binding of `customizer`.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ * var other = { 'user': 'fred' };
+ *
+ * object == other;
+ * // => false
+ *
+ * _.isEqual(object, other);
+ * // => true
+ *
+ * // using a customizer callback
+ * var array = ['hello', 'goodbye'];
+ * var other = ['hi', 'goodbye'];
+ *
+ * _.isEqual(array, other, function(value, other) {
+ *   if (_.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) {
+ *     return true;
+ *   }
+ * });
+ * // => true
+ */
+function isEqual(value, other, customizer, thisArg) {
+  customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
+  var result = customizer ? customizer(value, other) : undefined;
+  return  result === undefined ? baseIsEqual(value, other, customizer) : !!result;
+}
+
+export default isEqual;

+ 38 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isFunction.js

@@ -0,0 +1,38 @@
+import isObject from './isObject';
+
+/** `Object#toString` result references. */
+var funcTag = '[object Function]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+function isFunction(value) {
+  // The use of `Object#toString` avoids issues with the `typeof` operator
+  // in older versions of Chrome and Safari which return 'function' for regexes
+  // and Safari 8 which returns 'object' for typed array constructors.
+  return isObject(value) && objToString.call(value) == funcTag;
+}
+
+export default isFunction;

+ 48 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isNative.js

@@ -0,0 +1,48 @@
+import isFunction from './isFunction';
+import isObjectLike from '../internal/isObjectLike';
+
+/** Used to detect host constructors (Safari > 5). */
+var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to resolve the decompiled source of functions. */
+var fnToString = Function.prototype.toString;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/** Used to detect if a method is native. */
+var reIsNative = RegExp('^' +
+  fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
+  .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+);
+
+/**
+ * Checks if `value` is a native function.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
+ * @example
+ *
+ * _.isNative(Array.prototype.push);
+ * // => true
+ *
+ * _.isNative(_);
+ * // => false
+ */
+function isNative(value) {
+  if (value == null) {
+    return false;
+  }
+  if (isFunction(value)) {
+    return reIsNative.test(fnToString.call(value));
+  }
+  return isObjectLike(value) && reIsHostCtor.test(value);
+}
+
+export default isNative;

+ 37 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isObject.js

@@ -0,0 +1,37 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+
+/**
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+function isObject(value) {
+  // Avoid a V8 JIT bug in Chrome 19-20.
+  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+  var type = typeof value;
+  return !!value && (type == 'object' || type == 'function');
+}
+
+export default isObject;

+ 79 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isPlainObject.js

@@ -0,0 +1,79 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+import baseForIn from '../internal/baseForIn';
+import isArguments from './isArguments';
+import isObjectLike from '../internal/isObjectLike';
+
+/** `Object#toString` result references. */
+var objectTag = '[object Object]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is a plain object, that is, an object created by the
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
+ *
+ * **Note:** This method assumes objects created by the `Object` constructor
+ * have no inherited enumerable properties.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Foo() {
+ *   this.a = 1;
+ * }
+ *
+ * _.isPlainObject(new Foo);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ *
+ * _.isPlainObject(Object.create(null));
+ * // => true
+ */
+function isPlainObject(value) {
+  var Ctor;
+
+  // Exit early for non `Object` objects.
+  if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isArguments(value)) ||
+      (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
+    return false;
+  }
+  // IE < 9 iterates inherited properties before own properties. If the first
+  // iterated property is an object's own property then there are no inherited
+  // enumerable properties.
+  var result;
+  // In most environments an object's own properties are iterated before
+  // its inherited properties. If the last iterated property is an object's
+  // own property then there are no inherited enumerable properties.
+  baseForIn(value, function(subValue, key) {
+    result = key;
+  });
+  return result === undefined || hasOwnProperty.call(value, result);
+}
+
+export default isPlainObject;

+ 74 - 0
packages/ckeditor5-engine/src/lib/lodash/lang/isTypedArray.js

@@ -0,0 +1,74 @@
+import isLength from '../internal/isLength';
+import isObjectLike from '../internal/isObjectLike';
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+    arrayTag = '[object Array]',
+    boolTag = '[object Boolean]',
+    dateTag = '[object Date]',
+    errorTag = '[object Error]',
+    funcTag = '[object Function]',
+    mapTag = '[object Map]',
+    numberTag = '[object Number]',
+    objectTag = '[object Object]',
+    regexpTag = '[object RegExp]',
+    setTag = '[object Set]',
+    stringTag = '[object String]',
+    weakMapTag = '[object WeakMap]';
+
+var arrayBufferTag = '[object ArrayBuffer]',
+    float32Tag = '[object Float32Array]',
+    float64Tag = '[object Float64Array]',
+    int8Tag = '[object Int8Array]',
+    int16Tag = '[object Int16Array]',
+    int32Tag = '[object Int32Array]',
+    uint8Tag = '[object Uint8Array]',
+    uint8ClampedTag = '[object Uint8ClampedArray]',
+    uint16Tag = '[object Uint16Array]',
+    uint32Tag = '[object Uint32Array]';
+
+/** Used to identify `toStringTag` values of typed arrays. */
+var typedArrayTags = {};
+typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+typedArrayTags[uint32Tag] = true;
+typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+typedArrayTags[dateTag] = typedArrayTags[errorTag] =
+typedArrayTags[funcTag] = typedArrayTags[mapTag] =
+typedArrayTags[numberTag] = typedArrayTags[objectTag] =
+typedArrayTags[regexpTag] = typedArrayTags[setTag] =
+typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is classified as a typed array.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isTypedArray(new Uint8Array);
+ * // => true
+ *
+ * _.isTypedArray([]);
+ * // => false
+ */
+function isTypedArray(value) {
+  return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
+}
+
+export default isTypedArray;

+ 0 - 1650
packages/ckeditor5-engine/src/lib/lodash/lodash-ckeditor.js

@@ -1,1650 +0,0 @@
-/**
- * @license
- * lodash 3.10.1 (Custom Build) <https://lodash.com/>
- * Build: `lodash modern exports="amd" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash/lodash-ckeditor.js`
- * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
- * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
- * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
- * Available under MIT license <https://lodash.com/license>
- */
-;(function() {
-
-  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
-  var undefined;
-
-  /** Used as the semantic version number. */
-  var VERSION = '3.10.1';
-
-  /** Used as the `TypeError` message for "Functions" methods. */
-  var FUNC_ERROR_TEXT = 'Expected a function';
-
-  /** `Object#toString` result references. */
-  var argsTag = '[object Arguments]',
-      arrayTag = '[object Array]',
-      boolTag = '[object Boolean]',
-      dateTag = '[object Date]',
-      errorTag = '[object Error]',
-      funcTag = '[object Function]',
-      mapTag = '[object Map]',
-      numberTag = '[object Number]',
-      objectTag = '[object Object]',
-      regexpTag = '[object RegExp]',
-      setTag = '[object Set]',
-      stringTag = '[object String]',
-      weakMapTag = '[object WeakMap]';
-
-  var arrayBufferTag = '[object ArrayBuffer]',
-      float32Tag = '[object Float32Array]',
-      float64Tag = '[object Float64Array]',
-      int8Tag = '[object Int8Array]',
-      int16Tag = '[object Int16Array]',
-      int32Tag = '[object Int32Array]',
-      uint8Tag = '[object Uint8Array]',
-      uint8ClampedTag = '[object Uint8ClampedArray]',
-      uint16Tag = '[object Uint16Array]',
-      uint32Tag = '[object Uint32Array]';
-
-  /** Used to match `RegExp` flags from their coerced string values. */
-  var reFlags = /\w*$/;
-
-  /** Used to detect host constructors (Safari > 5). */
-  var reIsHostCtor = /^\[object .+?Constructor\]$/;
-
-  /** Used to detect unsigned integer values. */
-  var reIsUint = /^\d+$/;
-
-  /** Used to identify `toStringTag` values of typed arrays. */
-  var typedArrayTags = {};
-  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
-  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
-  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
-  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
-  typedArrayTags[uint32Tag] = true;
-  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
-  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
-  typedArrayTags[dateTag] = typedArrayTags[errorTag] =
-  typedArrayTags[funcTag] = typedArrayTags[mapTag] =
-  typedArrayTags[numberTag] = typedArrayTags[objectTag] =
-  typedArrayTags[regexpTag] = typedArrayTags[setTag] =
-  typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
-
-  /** Used to identify `toStringTag` values supported by `_.clone`. */
-  var cloneableTags = {};
-  cloneableTags[argsTag] = cloneableTags[arrayTag] =
-  cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
-  cloneableTags[dateTag] = cloneableTags[float32Tag] =
-  cloneableTags[float64Tag] = cloneableTags[int8Tag] =
-  cloneableTags[int16Tag] = cloneableTags[int32Tag] =
-  cloneableTags[numberTag] = cloneableTags[objectTag] =
-  cloneableTags[regexpTag] = cloneableTags[stringTag] =
-  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
-  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
-  cloneableTags[errorTag] = cloneableTags[funcTag] =
-  cloneableTags[mapTag] = cloneableTags[setTag] =
-  cloneableTags[weakMapTag] = false;
-
-  /** Used to determine if values are of the language type `Object`. */
-  var objectTypes = {
-    'function': true,
-    'object': true
-  };
-
-  /** Detect free variable `exports`. */
-  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
-
-  /** Detect free variable `module`. */
-  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
-
-  /** Detect free variable `global` from Node.js. */
-  var freeGlobal = freeExports && freeModule && typeof global == 'object' && global && global.Object && global;
-
-  /** Detect free variable `self`. */
-  var freeSelf = objectTypes[typeof self] && self && self.Object && self;
-
-  /** Detect free variable `window`. */
-  var freeWindow = objectTypes[typeof window] && window && window.Object && window;
-
-  /**
-   * Used as a reference to the global object.
-   *
-   * The `this` value is used if it's the global object to avoid Greasemonkey's
-   * restricted `window` object, otherwise the `window` object is used.
-   */
-  var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;
-
-  /*--------------------------------------------------------------------------*/
-
-  /**
-   * Checks if `value` is object-like.
-   *
-   * @private
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
-   */
-  function isObjectLike(value) {
-    return !!value && typeof value == 'object';
-  }
-
-  /*--------------------------------------------------------------------------*/
-
-  /** Used for native method references. */
-  var objectProto = Object.prototype;
-
-  /** Used to resolve the decompiled source of functions. */
-  var fnToString = Function.prototype.toString;
-
-  /** Used to check objects for own properties. */
-  var hasOwnProperty = objectProto.hasOwnProperty;
-
-  /**
-   * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
-   * of values.
-   */
-  var objToString = objectProto.toString;
-
-  /** Used to detect if a method is native. */
-  var reIsNative = RegExp('^' +
-    fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
-    .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
-  );
-
-  /** Native method references. */
-  var ArrayBuffer = root.ArrayBuffer,
-      propertyIsEnumerable = objectProto.propertyIsEnumerable,
-      Uint8Array = root.Uint8Array;
-
-  /* Native method references for those with the same name as other `lodash` methods. */
-  var nativeIsArray = getNative(Array, 'isArray'),
-      nativeKeys = getNative(Object, 'keys'),
-      nativeMax = Math.max;
-
-  /**
-   * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
-   * of an array-like value.
-   */
-  var MAX_SAFE_INTEGER = 9007199254740991;
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * Creates a `lodash` object which wraps `value` to enable implicit chaining.
-   * Methods that operate on and return arrays, collections, and functions can
-   * be chained together. Methods that retrieve a single value or may return a
-   * primitive value will automatically end the chain returning the unwrapped
-   * value. Explicit chaining may be enabled using `_.chain`. The execution of
-   * chained methods is lazy, that is, execution is deferred until `_#value`
-   * is implicitly or explicitly called.
-   *
-   * Lazy evaluation allows several methods to support shortcut fusion. Shortcut
-   * fusion is an optimization strategy which merge iteratee calls; this can help
-   * to avoid the creation of intermediate data structures and greatly reduce the
-   * number of iteratee executions.
-   *
-   * Chaining is supported in custom builds as long as the `_#value` method is
-   * directly or indirectly included in the build.
-   *
-   * In addition to lodash methods, wrappers have `Array` and `String` methods.
-   *
-   * The wrapper `Array` methods are:
-   * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`,
-   * `splice`, and `unshift`
-   *
-   * The wrapper `String` methods are:
-   * `replace` and `split`
-   *
-   * The wrapper methods that support shortcut fusion are:
-   * `compact`, `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`,
-   * `first`, `initial`, `last`, `map`, `pluck`, `reject`, `rest`, `reverse`,
-   * `slice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `toArray`,
-   * and `where`
-   *
-   * The chainable wrapper methods are:
-   * `after`, `ary`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`,
-   * `callback`, `chain`, `chunk`, `commit`, `compact`, `concat`, `constant`,
-   * `countBy`, `create`, `curry`, `debounce`, `defaults`, `defaultsDeep`,
-   * `defer`, `delay`, `difference`, `drop`, `dropRight`, `dropRightWhile`,
-   * `dropWhile`, `fill`, `filter`, `flatten`, `flattenDeep`, `flow`, `flowRight`,
-   * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
-   * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
-   * `invoke`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`,
-   * `matchesProperty`, `memoize`, `merge`, `method`, `methodOf`, `mixin`,
-   * `modArgs`, `negate`, `omit`, `once`, `pairs`, `partial`, `partialRight`,
-   * `partition`, `pick`, `plant`, `pluck`, `property`, `propertyOf`, `pull`,
-   * `pullAt`, `push`, `range`, `rearg`, `reject`, `remove`, `rest`, `restParam`,
-   * `reverse`, `set`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByAll`,
-   * `sortByOrder`, `splice`, `spread`, `take`, `takeRight`, `takeRightWhile`,
-   * `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `toPlainObject`,
-   * `transform`, `union`, `uniq`, `unshift`, `unzip`, `unzipWith`, `values`,
-   * `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, `zipObject`, `zipWith`
-   *
-   * The wrapper methods that are **not** chainable by default are:
-   * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,
-   * `deburr`, `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,
-   * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`,
-   * `floor`, `get`, `gt`, `gte`, `has`, `identity`, `includes`, `indexOf`,
-   * `inRange`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
-   * `isEmpty`, `isEqual`, `isError`, `isFinite` `isFunction`, `isMatch`,
-   * `isNative`, `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`,
-   * `isRegExp`, `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`,
-   * `last`, `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`,
-   * `now`, `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`,
-   * `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`,
-   * `snakeCase`, `some`, `sortedIndex`, `sortedLastIndex`, `startCase`,
-   * `startsWith`, `sum`, `template`, `trim`, `trimLeft`, `trimRight`, `trunc`,
-   * `unescape`, `uniqueId`, `value`, and `words`
-   *
-   * The wrapper method `sample` will return a wrapped value when `n` is provided,
-   * otherwise an unwrapped value is returned.
-   *
-   * @name _
-   * @constructor
-   * @category Chain
-   * @param {*} value The value to wrap in a `lodash` instance.
-   * @returns {Object} Returns the new `lodash` wrapper instance.
-   * @example
-   *
-   * var wrapped = _([1, 2, 3]);
-   *
-   * // returns an unwrapped value
-   * wrapped.reduce(function(total, n) {
-   *   return total + n;
-   * });
-   * // => 6
-   *
-   * // returns a wrapped value
-   * var squares = wrapped.map(function(n) {
-   *   return n * n;
-   * });
-   *
-   * _.isArray(squares);
-   * // => false
-   *
-   * _.isArray(squares.value());
-   * // => true
-   */
-  function lodash() {
-    // No operation performed.
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * Copies the values of `source` to `array`.
-   *
-   * @private
-   * @param {Array} source The array to copy values from.
-   * @param {Array} [array=[]] The array to copy values to.
-   * @returns {Array} Returns `array`.
-   */
-  function arrayCopy(source, array) {
-    var index = -1,
-        length = source.length;
-
-    array || (array = Array(length));
-    while (++index < length) {
-      array[index] = source[index];
-    }
-    return array;
-  }
-
-  /**
-   * A specialized version of `_.forEach` for arrays without support for callback
-   * shorthands and `this` binding.
-   *
-   * @private
-   * @param {Array} array The array to iterate over.
-   * @param {Function} iteratee The function invoked per iteration.
-   * @returns {Array} Returns `array`.
-   */
-  function arrayEach(array, iteratee) {
-    var index = -1,
-        length = array.length;
-
-    while (++index < length) {
-      if (iteratee(array[index], index, array) === false) {
-        break;
-      }
-    }
-    return array;
-  }
-
-  /**
-   * A specialized version of `_.some` for arrays without support for callback
-   * shorthands and `this` binding.
-   *
-   * @private
-   * @param {Array} array The array to iterate over.
-   * @param {Function} predicate The function invoked per iteration.
-   * @returns {boolean} Returns `true` if any element passes the predicate check,
-   *  else `false`.
-   */
-  function arraySome(array, predicate) {
-    var index = -1,
-        length = array.length;
-
-    while (++index < length) {
-      if (predicate(array[index], index, array)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * A specialized version of `_.assign` for customizing assigned values without
-   * support for argument juggling, multiple sources, and `this` binding `customizer`
-   * functions.
-   *
-   * @private
-   * @param {Object} object The destination object.
-   * @param {Object} source The source object.
-   * @param {Function} customizer The function to customize assigned values.
-   * @returns {Object} Returns `object`.
-   */
-  function assignWith(object, source, customizer) {
-    var index = -1,
-        props = keys(source),
-        length = props.length;
-
-    while (++index < length) {
-      var key = props[index],
-          value = object[key],
-          result = customizer(value, source[key], key, object, source);
-
-      if ((result === result ? (result !== value) : (value === value)) ||
-          (value === undefined && !(key in object))) {
-        object[key] = result;
-      }
-    }
-    return object;
-  }
-
-  /**
-   * The base implementation of `_.assign` without support for argument juggling,
-   * multiple sources, and `customizer` functions.
-   *
-   * @private
-   * @param {Object} object The destination object.
-   * @param {Object} source The source object.
-   * @returns {Object} Returns `object`.
-   */
-  function baseAssign(object, source) {
-    return source == null
-      ? object
-      : baseCopy(source, keys(source), object);
-  }
-
-  /**
-   * Copies properties of `source` to `object`.
-   *
-   * @private
-   * @param {Object} source The object to copy properties from.
-   * @param {Array} props The property names to copy.
-   * @param {Object} [object={}] The object to copy properties to.
-   * @returns {Object} Returns `object`.
-   */
-  function baseCopy(source, props, object) {
-    object || (object = {});
-
-    var index = -1,
-        length = props.length;
-
-    while (++index < length) {
-      var key = props[index];
-      object[key] = source[key];
-    }
-    return object;
-  }
-
-  /**
-   * The base implementation of `_.clone` without support for argument juggling
-   * and `this` binding `customizer` functions.
-   *
-   * @private
-   * @param {*} value The value to clone.
-   * @param {boolean} [isDeep] Specify a deep clone.
-   * @param {Function} [customizer] The function to customize cloning values.
-   * @param {string} [key] The key of `value`.
-   * @param {Object} [object] The object `value` belongs to.
-   * @param {Array} [stackA=[]] Tracks traversed source objects.
-   * @param {Array} [stackB=[]] Associates clones with source counterparts.
-   * @returns {*} Returns the cloned value.
-   */
-  function baseClone(value, isDeep, customizer, key, object, stackA, stackB) {
-    var result;
-    if (customizer) {
-      result = object ? customizer(value, key, object) : customizer(value);
-    }
-    if (result !== undefined) {
-      return result;
-    }
-    if (!isObject(value)) {
-      return value;
-    }
-    var isArr = isArray(value);
-    if (isArr) {
-      result = initCloneArray(value);
-      if (!isDeep) {
-        return arrayCopy(value, result);
-      }
-    } else {
-      var tag = objToString.call(value),
-          isFunc = tag == funcTag;
-
-      if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
-        result = initCloneObject(isFunc ? {} : value);
-        if (!isDeep) {
-          return baseAssign(result, value);
-        }
-      } else {
-        return cloneableTags[tag]
-          ? initCloneByTag(value, tag, isDeep)
-          : (object ? value : {});
-      }
-    }
-    // Check for circular references and return its corresponding clone.
-    stackA || (stackA = []);
-    stackB || (stackB = []);
-
-    var length = stackA.length;
-    while (length--) {
-      if (stackA[length] == value) {
-        return stackB[length];
-      }
-    }
-    // Add the source value to the stack of traversed objects and associate it with its clone.
-    stackA.push(value);
-    stackB.push(result);
-
-    // Recursively populate clone (susceptible to call stack limits).
-    (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
-      result[key] = baseClone(subValue, isDeep, customizer, key, value, stackA, stackB);
-    });
-    return result;
-  }
-
-  /**
-   * The base implementation of `baseForIn` and `baseForOwn` which iterates
-   * over `object` properties returned by `keysFunc` invoking `iteratee` for
-   * each property. Iteratee functions may exit iteration early by explicitly
-   * returning `false`.
-   *
-   * @private
-   * @param {Object} object The object to iterate over.
-   * @param {Function} iteratee The function invoked per iteration.
-   * @param {Function} keysFunc The function to get the keys of `object`.
-   * @returns {Object} Returns `object`.
-   */
-  var baseFor = createBaseFor();
-
-  /**
-   * The base implementation of `_.forIn` without support for callback
-   * shorthands and `this` binding.
-   *
-   * @private
-   * @param {Object} object The object to iterate over.
-   * @param {Function} iteratee The function invoked per iteration.
-   * @returns {Object} Returns `object`.
-   */
-  function baseForIn(object, iteratee) {
-    return baseFor(object, iteratee, keysIn);
-  }
-
-  /**
-   * The base implementation of `_.forOwn` without support for callback
-   * shorthands and `this` binding.
-   *
-   * @private
-   * @param {Object} object The object to iterate over.
-   * @param {Function} iteratee The function invoked per iteration.
-   * @returns {Object} Returns `object`.
-   */
-  function baseForOwn(object, iteratee) {
-    return baseFor(object, iteratee, keys);
-  }
-
-  /**
-   * The base implementation of `_.isEqual` without support for `this` binding
-   * `customizer` functions.
-   *
-   * @private
-   * @param {*} value The value to compare.
-   * @param {*} other The other value to compare.
-   * @param {Function} [customizer] The function to customize comparing values.
-   * @param {boolean} [isLoose] Specify performing partial comparisons.
-   * @param {Array} [stackA] Tracks traversed `value` objects.
-   * @param {Array} [stackB] Tracks traversed `other` objects.
-   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
-   */
-  function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
-    if (value === other) {
-      return true;
-    }
-    if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
-      return value !== value && other !== other;
-    }
-    return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
-  }
-
-  /**
-   * A specialized version of `baseIsEqual` for arrays and objects which performs
-   * deep comparisons and tracks traversed objects enabling objects with circular
-   * references to be compared.
-   *
-   * @private
-   * @param {Object} object The object to compare.
-   * @param {Object} other The other object to compare.
-   * @param {Function} equalFunc The function to determine equivalents of values.
-   * @param {Function} [customizer] The function to customize comparing objects.
-   * @param {boolean} [isLoose] Specify performing partial comparisons.
-   * @param {Array} [stackA=[]] Tracks traversed `value` objects.
-   * @param {Array} [stackB=[]] Tracks traversed `other` objects.
-   * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
-   */
-  function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
-    var objIsArr = isArray(object),
-        othIsArr = isArray(other),
-        objTag = arrayTag,
-        othTag = arrayTag;
-
-    if (!objIsArr) {
-      objTag = objToString.call(object);
-      if (objTag == argsTag) {
-        objTag = objectTag;
-      } else if (objTag != objectTag) {
-        objIsArr = isTypedArray(object);
-      }
-    }
-    if (!othIsArr) {
-      othTag = objToString.call(other);
-      if (othTag == argsTag) {
-        othTag = objectTag;
-      } else if (othTag != objectTag) {
-        othIsArr = isTypedArray(other);
-      }
-    }
-    var objIsObj = objTag == objectTag,
-        othIsObj = othTag == objectTag,
-        isSameTag = objTag == othTag;
-
-    if (isSameTag && !(objIsArr || objIsObj)) {
-      return equalByTag(object, other, objTag);
-    }
-    if (!isLoose) {
-      var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
-          othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
-
-      if (objIsWrapped || othIsWrapped) {
-        return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
-      }
-    }
-    if (!isSameTag) {
-      return false;
-    }
-    // Assume cyclic values are equal.
-    // For more information on detecting circular references see https://es5.github.io/#JO.
-    stackA || (stackA = []);
-    stackB || (stackB = []);
-
-    var length = stackA.length;
-    while (length--) {
-      if (stackA[length] == object) {
-        return stackB[length] == other;
-      }
-    }
-    // Add `object` and `other` to the stack of traversed objects.
-    stackA.push(object);
-    stackB.push(other);
-
-    var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
-
-    stackA.pop();
-    stackB.pop();
-
-    return result;
-  }
-
-  /**
-   * The base implementation of `_.property` without support for deep paths.
-   *
-   * @private
-   * @param {string} key The key of the property to get.
-   * @returns {Function} Returns the new function.
-   */
-  function baseProperty(key) {
-    return function(object) {
-      return object == null ? undefined : object[key];
-    };
-  }
-
-  /**
-   * A specialized version of `baseCallback` which only supports `this` binding
-   * and specifying the number of arguments to provide to `func`.
-   *
-   * @private
-   * @param {Function} func The function to bind.
-   * @param {*} thisArg The `this` binding of `func`.
-   * @param {number} [argCount] The number of arguments to provide to `func`.
-   * @returns {Function} Returns the callback.
-   */
-  function bindCallback(func, thisArg, argCount) {
-    if (typeof func != 'function') {
-      return identity;
-    }
-    if (thisArg === undefined) {
-      return func;
-    }
-    switch (argCount) {
-      case 1: return function(value) {
-        return func.call(thisArg, value);
-      };
-      case 3: return function(value, index, collection) {
-        return func.call(thisArg, value, index, collection);
-      };
-      case 4: return function(accumulator, value, index, collection) {
-        return func.call(thisArg, accumulator, value, index, collection);
-      };
-      case 5: return function(value, other, key, object, source) {
-        return func.call(thisArg, value, other, key, object, source);
-      };
-    }
-    return function() {
-      return func.apply(thisArg, arguments);
-    };
-  }
-
-  /**
-   * Creates a clone of the given array buffer.
-   *
-   * @private
-   * @param {ArrayBuffer} buffer The array buffer to clone.
-   * @returns {ArrayBuffer} Returns the cloned array buffer.
-   */
-  function bufferClone(buffer) {
-    var result = new ArrayBuffer(buffer.byteLength),
-        view = new Uint8Array(result);
-
-    view.set(new Uint8Array(buffer));
-    return result;
-  }
-
-  /**
-   * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
-   *
-   * @private
-   * @param {Function} assigner The function to assign values.
-   * @returns {Function} Returns the new assigner function.
-   */
-  function createAssigner(assigner) {
-    return restParam(function(object, sources) {
-      var index = -1,
-          length = object == null ? 0 : sources.length,
-          customizer = length > 2 ? sources[length - 2] : undefined,
-          guard = length > 2 ? sources[2] : undefined,
-          thisArg = length > 1 ? sources[length - 1] : undefined;
-
-      if (typeof customizer == 'function') {
-        customizer = bindCallback(customizer, thisArg, 5);
-        length -= 2;
-      } else {
-        customizer = typeof thisArg == 'function' ? thisArg : undefined;
-        length -= (customizer ? 1 : 0);
-      }
-      if (guard && isIterateeCall(sources[0], sources[1], guard)) {
-        customizer = length < 3 ? undefined : customizer;
-        length = 1;
-      }
-      while (++index < length) {
-        var source = sources[index];
-        if (source) {
-          assigner(object, source, customizer);
-        }
-      }
-      return object;
-    });
-  }
-
-  /**
-   * Creates a base function for `_.forIn` or `_.forInRight`.
-   *
-   * @private
-   * @param {boolean} [fromRight] Specify iterating from right to left.
-   * @returns {Function} Returns the new base function.
-   */
-  function createBaseFor(fromRight) {
-    return function(object, iteratee, keysFunc) {
-      var iterable = toObject(object),
-          props = keysFunc(object),
-          length = props.length,
-          index = fromRight ? length : -1;
-
-      while ((fromRight ? index-- : ++index < length)) {
-        var key = props[index];
-        if (iteratee(iterable[key], key, iterable) === false) {
-          break;
-        }
-      }
-      return object;
-    };
-  }
-
-  /**
-   * A specialized version of `baseIsEqualDeep` for arrays with support for
-   * partial deep comparisons.
-   *
-   * @private
-   * @param {Array} array The array to compare.
-   * @param {Array} other The other array to compare.
-   * @param {Function} equalFunc The function to determine equivalents of values.
-   * @param {Function} [customizer] The function to customize comparing arrays.
-   * @param {boolean} [isLoose] Specify performing partial comparisons.
-   * @param {Array} [stackA] Tracks traversed `value` objects.
-   * @param {Array} [stackB] Tracks traversed `other` objects.
-   * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
-   */
-  function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
-    var index = -1,
-        arrLength = array.length,
-        othLength = other.length;
-
-    if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
-      return false;
-    }
-    // Ignore non-index properties.
-    while (++index < arrLength) {
-      var arrValue = array[index],
-          othValue = other[index],
-          result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined;
-
-      if (result !== undefined) {
-        if (result) {
-          continue;
-        }
-        return false;
-      }
-      // Recursively compare arrays (susceptible to call stack limits).
-      if (isLoose) {
-        if (!arraySome(other, function(othValue) {
-              return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
-            })) {
-          return false;
-        }
-      } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * A specialized version of `baseIsEqualDeep` for comparing objects of
-   * the same `toStringTag`.
-   *
-   * **Note:** This function only supports comparing values with tags of
-   * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
-   *
-   * @private
-   * @param {Object} object The object to compare.
-   * @param {Object} other The other object to compare.
-   * @param {string} tag The `toStringTag` of the objects to compare.
-   * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
-   */
-  function equalByTag(object, other, tag) {
-    switch (tag) {
-      case boolTag:
-      case dateTag:
-        // Coerce dates and booleans to numbers, dates to milliseconds and booleans
-        // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.
-        return +object == +other;
-
-      case errorTag:
-        return object.name == other.name && object.message == other.message;
-
-      case numberTag:
-        // Treat `NaN` vs. `NaN` as equal.
-        return (object != +object)
-          ? other != +other
-          : object == +other;
-
-      case regexpTag:
-      case stringTag:
-        // Coerce regexes to strings and treat strings primitives and string
-        // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.
-        return object == (other + '');
-    }
-    return false;
-  }
-
-  /**
-   * A specialized version of `baseIsEqualDeep` for objects with support for
-   * partial deep comparisons.
-   *
-   * @private
-   * @param {Object} object The object to compare.
-   * @param {Object} other The other object to compare.
-   * @param {Function} equalFunc The function to determine equivalents of values.
-   * @param {Function} [customizer] The function to customize comparing values.
-   * @param {boolean} [isLoose] Specify performing partial comparisons.
-   * @param {Array} [stackA] Tracks traversed `value` objects.
-   * @param {Array} [stackB] Tracks traversed `other` objects.
-   * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
-   */
-  function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
-    var objProps = keys(object),
-        objLength = objProps.length,
-        othProps = keys(other),
-        othLength = othProps.length;
-
-    if (objLength != othLength && !isLoose) {
-      return false;
-    }
-    var index = objLength;
-    while (index--) {
-      var key = objProps[index];
-      if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {
-        return false;
-      }
-    }
-    var skipCtor = isLoose;
-    while (++index < objLength) {
-      key = objProps[index];
-      var objValue = object[key],
-          othValue = other[key],
-          result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined;
-
-      // Recursively compare objects (susceptible to call stack limits).
-      if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {
-        return false;
-      }
-      skipCtor || (skipCtor = key == 'constructor');
-    }
-    if (!skipCtor) {
-      var objCtor = object.constructor,
-          othCtor = other.constructor;
-
-      // Non `Object` object instances with different constructors are not equal.
-      if (objCtor != othCtor &&
-          ('constructor' in object && 'constructor' in other) &&
-          !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
-            typeof othCtor == 'function' && othCtor instanceof othCtor)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Gets the "length" property value of `object`.
-   *
-   * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
-   * that affects Safari on at least iOS 8.1-8.3 ARM64.
-   *
-   * @private
-   * @param {Object} object The object to query.
-   * @returns {*} Returns the "length" value.
-   */
-  var getLength = baseProperty('length');
-
-  /**
-   * Gets the native function at `key` of `object`.
-   *
-   * @private
-   * @param {Object} object The object to query.
-   * @param {string} key The key of the method to get.
-   * @returns {*} Returns the function if it's native, else `undefined`.
-   */
-  function getNative(object, key) {
-    var value = object == null ? undefined : object[key];
-    return isNative(value) ? value : undefined;
-  }
-
-  /**
-   * Initializes an array clone.
-   *
-   * @private
-   * @param {Array} array The array to clone.
-   * @returns {Array} Returns the initialized clone.
-   */
-  function initCloneArray(array) {
-    var length = array.length,
-        result = new array.constructor(length);
-
-    // Add array properties assigned by `RegExp#exec`.
-    if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
-      result.index = array.index;
-      result.input = array.input;
-    }
-    return result;
-  }
-
-  /**
-   * Initializes an object clone.
-   *
-   * @private
-   * @param {Object} object The object to clone.
-   * @returns {Object} Returns the initialized clone.
-   */
-  function initCloneObject(object) {
-    var Ctor = object.constructor;
-    if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
-      Ctor = Object;
-    }
-    return new Ctor;
-  }
-
-  /**
-   * Initializes an object clone based on its `toStringTag`.
-   *
-   * **Note:** This function only supports cloning values with tags of
-   * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
-   *
-   * @private
-   * @param {Object} object The object to clone.
-   * @param {string} tag The `toStringTag` of the object to clone.
-   * @param {boolean} [isDeep] Specify a deep clone.
-   * @returns {Object} Returns the initialized clone.
-   */
-  function initCloneByTag(object, tag, isDeep) {
-    var Ctor = object.constructor;
-    switch (tag) {
-      case arrayBufferTag:
-        return bufferClone(object);
-
-      case boolTag:
-      case dateTag:
-        return new Ctor(+object);
-
-      case float32Tag: case float64Tag:
-      case int8Tag: case int16Tag: case int32Tag:
-      case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
-        var buffer = object.buffer;
-        return new Ctor(isDeep ? bufferClone(buffer) : buffer, object.byteOffset, object.length);
-
-      case numberTag:
-      case stringTag:
-        return new Ctor(object);
-
-      case regexpTag:
-        var result = new Ctor(object.source, reFlags.exec(object));
-        result.lastIndex = object.lastIndex;
-    }
-    return result;
-  }
-
-  /**
-   * Checks if `value` is array-like.
-   *
-   * @private
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
-   */
-  function isArrayLike(value) {
-    return value != null && isLength(getLength(value));
-  }
-
-  /**
-   * Checks if `value` is a valid array-like index.
-   *
-   * @private
-   * @param {*} value The value to check.
-   * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
-   * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
-   */
-  function isIndex(value, length) {
-    value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
-    length = length == null ? MAX_SAFE_INTEGER : length;
-    return value > -1 && value % 1 == 0 && value < length;
-  }
-
-  /**
-   * Checks if the provided arguments are from an iteratee call.
-   *
-   * @private
-   * @param {*} value The potential iteratee value argument.
-   * @param {*} index The potential iteratee index or key argument.
-   * @param {*} object The potential iteratee object argument.
-   * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
-   */
-  function isIterateeCall(value, index, object) {
-    if (!isObject(object)) {
-      return false;
-    }
-    var type = typeof index;
-    if (type == 'number'
-        ? (isArrayLike(object) && isIndex(index, object.length))
-        : (type == 'string' && index in object)) {
-      var other = object[index];
-      return value === value ? (value === other) : (other !== other);
-    }
-    return false;
-  }
-
-  /**
-   * Checks if `value` is a valid array-like length.
-   *
-   * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
-   *
-   * @private
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
-   */
-  function isLength(value) {
-    return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
-  }
-
-  /**
-   * A fallback implementation of `Object.keys` which creates an array of the
-   * own enumerable property names of `object`.
-   *
-   * @private
-   * @param {Object} object The object to query.
-   * @returns {Array} Returns the array of property names.
-   */
-  function shimKeys(object) {
-    var props = keysIn(object),
-        propsLength = props.length,
-        length = propsLength && object.length;
-
-    var allowIndexes = !!length && isLength(length) &&
-      (isArray(object) || isArguments(object));
-
-    var index = -1,
-        result = [];
-
-    while (++index < propsLength) {
-      var key = props[index];
-      if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
-        result.push(key);
-      }
-    }
-    return result;
-  }
-
-  /**
-   * Converts `value` to an object if it's not one.
-   *
-   * @private
-   * @param {*} value The value to process.
-   * @returns {Object} Returns the object.
-   */
-  function toObject(value) {
-    return isObject(value) ? value : Object(value);
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * Gets the last element of `array`.
-   *
-   * @static
-   * @memberOf _
-   * @category Array
-   * @param {Array} array The array to query.
-   * @returns {*} Returns the last element of `array`.
-   * @example
-   *
-   * _.last([1, 2, 3]);
-   * // => 3
-   */
-  function last(array) {
-    var length = array ? array.length : 0;
-    return length ? array[length - 1] : undefined;
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * Creates a function that invokes `func` with the `this` binding of the
-   * created function and arguments from `start` and beyond provided as an array.
-   *
-   * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters).
-   *
-   * @static
-   * @memberOf _
-   * @category Function
-   * @param {Function} func The function to apply a rest parameter to.
-   * @param {number} [start=func.length-1] The start position of the rest parameter.
-   * @returns {Function} Returns the new function.
-   * @example
-   *
-   * var say = _.restParam(function(what, names) {
-   *   return what + ' ' + _.initial(names).join(', ') +
-   *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
-   * });
-   *
-   * say('hello', 'fred', 'barney', 'pebbles');
-   * // => 'hello fred, barney, & pebbles'
-   */
-  function restParam(func, start) {
-    if (typeof func != 'function') {
-      throw new TypeError(FUNC_ERROR_TEXT);
-    }
-    start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
-    return function() {
-      var args = arguments,
-          index = -1,
-          length = nativeMax(args.length - start, 0),
-          rest = Array(length);
-
-      while (++index < length) {
-        rest[index] = args[start + index];
-      }
-      switch (start) {
-        case 0: return func.call(this, rest);
-        case 1: return func.call(this, args[0], rest);
-        case 2: return func.call(this, args[0], args[1], rest);
-      }
-      var otherArgs = Array(start + 1);
-      index = -1;
-      while (++index < start) {
-        otherArgs[index] = args[index];
-      }
-      otherArgs[start] = rest;
-      return func.apply(this, otherArgs);
-    };
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * Creates a clone of `value`. If `isDeep` is `true` nested objects are cloned,
-   * otherwise they are assigned by reference. If `customizer` is provided it's
-   * invoked to produce the cloned values. If `customizer` returns `undefined`
-   * cloning is handled by the method instead. The `customizer` is bound to
-   * `thisArg` and invoked with up to three argument; (value [, index|key, object]).
-   *
-   * **Note:** This method is loosely based on the
-   * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
-   * The enumerable properties of `arguments` objects and objects created by
-   * constructors other than `Object` are cloned to plain `Object` objects. An
-   * empty object is returned for uncloneable values such as functions, DOM nodes,
-   * Maps, Sets, and WeakMaps.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to clone.
-   * @param {boolean} [isDeep] Specify a deep clone.
-   * @param {Function} [customizer] The function to customize cloning values.
-   * @param {*} [thisArg] The `this` binding of `customizer`.
-   * @returns {*} Returns the cloned value.
-   * @example
-   *
-   * var users = [
-   *   { 'user': 'barney' },
-   *   { 'user': 'fred' }
-   * ];
-   *
-   * var shallow = _.clone(users);
-   * shallow[0] === users[0];
-   * // => true
-   *
-   * var deep = _.clone(users, true);
-   * deep[0] === users[0];
-   * // => false
-   *
-   * // using a customizer callback
-   * var el = _.clone(document.body, function(value) {
-   *   if (_.isElement(value)) {
-   *     return value.cloneNode(false);
-   *   }
-   * });
-   *
-   * el === document.body
-   * // => false
-   * el.nodeName
-   * // => BODY
-   * el.childNodes.length;
-   * // => 0
-   */
-  function clone(value, isDeep, customizer, thisArg) {
-    if (isDeep && typeof isDeep != 'boolean' && isIterateeCall(value, isDeep, customizer)) {
-      isDeep = false;
-    }
-    else if (typeof isDeep == 'function') {
-      thisArg = customizer;
-      customizer = isDeep;
-      isDeep = false;
-    }
-    return typeof customizer == 'function'
-      ? baseClone(value, isDeep, bindCallback(customizer, thisArg, 3))
-      : baseClone(value, isDeep);
-  }
-
-  /**
-   * Checks if `value` is classified as an `arguments` object.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
-   * @example
-   *
-   * _.isArguments(function() { return arguments; }());
-   * // => true
-   *
-   * _.isArguments([1, 2, 3]);
-   * // => false
-   */
-  function isArguments(value) {
-    return isObjectLike(value) && isArrayLike(value) &&
-      hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
-  }
-
-  /**
-   * Checks if `value` is classified as an `Array` object.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
-   * @example
-   *
-   * _.isArray([1, 2, 3]);
-   * // => true
-   *
-   * _.isArray(function() { return arguments; }());
-   * // => false
-   */
-  var isArray = nativeIsArray || function(value) {
-    return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
-  };
-
-  /**
-   * Performs a deep comparison between two values to determine if they are
-   * equivalent. If `customizer` is provided it's invoked to compare values.
-   * If `customizer` returns `undefined` comparisons are handled by the method
-   * instead. The `customizer` is bound to `thisArg` and invoked with up to
-   * three arguments: (value, other [, index|key]).
-   *
-   * **Note:** This method supports comparing arrays, booleans, `Date` objects,
-   * numbers, `Object` objects, regexes, and strings. Objects are compared by
-   * their own, not inherited, enumerable properties. Functions and DOM nodes
-   * are **not** supported. Provide a customizer function to extend support
-   * for comparing other values.
-   *
-   * @static
-   * @memberOf _
-   * @alias eq
-   * @category Lang
-   * @param {*} value The value to compare.
-   * @param {*} other The other value to compare.
-   * @param {Function} [customizer] The function to customize value comparisons.
-   * @param {*} [thisArg] The `this` binding of `customizer`.
-   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
-   * @example
-   *
-   * var object = { 'user': 'fred' };
-   * var other = { 'user': 'fred' };
-   *
-   * object == other;
-   * // => false
-   *
-   * _.isEqual(object, other);
-   * // => true
-   *
-   * // using a customizer callback
-   * var array = ['hello', 'goodbye'];
-   * var other = ['hi', 'goodbye'];
-   *
-   * _.isEqual(array, other, function(value, other) {
-   *   if (_.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) {
-   *     return true;
-   *   }
-   * });
-   * // => true
-   */
-  function isEqual(value, other, customizer, thisArg) {
-    customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
-    var result = customizer ? customizer(value, other) : undefined;
-    return  result === undefined ? baseIsEqual(value, other, customizer) : !!result;
-  }
-
-  /**
-   * Checks if `value` is classified as a `Function` object.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
-   * @example
-   *
-   * _.isFunction(_);
-   * // => true
-   *
-   * _.isFunction(/abc/);
-   * // => false
-   */
-  function isFunction(value) {
-    // The use of `Object#toString` avoids issues with the `typeof` operator
-    // in older versions of Chrome and Safari which return 'function' for regexes
-    // and Safari 8 which returns 'object' for typed array constructors.
-    return isObject(value) && objToString.call(value) == funcTag;
-  }
-
-  /**
-   * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
-   * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is an object, else `false`.
-   * @example
-   *
-   * _.isObject({});
-   * // => true
-   *
-   * _.isObject([1, 2, 3]);
-   * // => true
-   *
-   * _.isObject(1);
-   * // => false
-   */
-  function isObject(value) {
-    // Avoid a V8 JIT bug in Chrome 19-20.
-    // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
-    var type = typeof value;
-    return !!value && (type == 'object' || type == 'function');
-  }
-
-  /**
-   * Checks if `value` is a native function.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
-   * @example
-   *
-   * _.isNative(Array.prototype.push);
-   * // => true
-   *
-   * _.isNative(_);
-   * // => false
-   */
-  function isNative(value) {
-    if (value == null) {
-      return false;
-    }
-    if (isFunction(value)) {
-      return reIsNative.test(fnToString.call(value));
-    }
-    return isObjectLike(value) && reIsHostCtor.test(value);
-  }
-
-  /**
-   * Checks if `value` is a plain object, that is, an object created by the
-   * `Object` constructor or one with a `[[Prototype]]` of `null`.
-   *
-   * **Note:** This method assumes objects created by the `Object` constructor
-   * have no inherited enumerable properties.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
-   * @example
-   *
-   * function Foo() {
-   *   this.a = 1;
-   * }
-   *
-   * _.isPlainObject(new Foo);
-   * // => false
-   *
-   * _.isPlainObject([1, 2, 3]);
-   * // => false
-   *
-   * _.isPlainObject({ 'x': 0, 'y': 0 });
-   * // => true
-   *
-   * _.isPlainObject(Object.create(null));
-   * // => true
-   */
-  function isPlainObject(value) {
-    var Ctor;
-
-    // Exit early for non `Object` objects.
-    if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isArguments(value)) ||
-        (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
-      return false;
-    }
-    // IE < 9 iterates inherited properties before own properties. If the first
-    // iterated property is an object's own property then there are no inherited
-    // enumerable properties.
-    var result;
-    // In most environments an object's own properties are iterated before
-    // its inherited properties. If the last iterated property is an object's
-    // own property then there are no inherited enumerable properties.
-    baseForIn(value, function(subValue, key) {
-      result = key;
-    });
-    return result === undefined || hasOwnProperty.call(value, result);
-  }
-
-  /**
-   * Checks if `value` is classified as a typed array.
-   *
-   * @static
-   * @memberOf _
-   * @category Lang
-   * @param {*} value The value to check.
-   * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
-   * @example
-   *
-   * _.isTypedArray(new Uint8Array);
-   * // => true
-   *
-   * _.isTypedArray([]);
-   * // => false
-   */
-  function isTypedArray(value) {
-    return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * Assigns own enumerable properties of source object(s) to the destination
-   * object. Subsequent sources overwrite property assignments of previous sources.
-   * If `customizer` is provided it's invoked to produce the assigned values.
-   * The `customizer` is bound to `thisArg` and invoked with five arguments:
-   * (objectValue, sourceValue, key, object, source).
-   *
-   * **Note:** This method mutates `object` and is based on
-   * [`Object.assign`](http://ecma-international.org/ecma-262/6.0/#sec-object.assign).
-   *
-   * @static
-   * @memberOf _
-   * @alias extend
-   * @category Object
-   * @param {Object} object The destination object.
-   * @param {...Object} [sources] The source objects.
-   * @param {Function} [customizer] The function to customize assigned values.
-   * @param {*} [thisArg] The `this` binding of `customizer`.
-   * @returns {Object} Returns `object`.
-   * @example
-   *
-   * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
-   * // => { 'user': 'fred', 'age': 40 }
-   *
-   * // using a customizer callback
-   * var defaults = _.partialRight(_.assign, function(value, other) {
-   *   return _.isUndefined(value) ? other : value;
-   * });
-   *
-   * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
-   * // => { 'user': 'barney', 'age': 36 }
-   */
-  var assign = createAssigner(function(object, source, customizer) {
-    return customizer
-      ? assignWith(object, source, customizer)
-      : baseAssign(object, source);
-  });
-
-  /**
-   * Creates an array of the own enumerable property names of `object`.
-   *
-   * **Note:** Non-object values are coerced to objects. See the
-   * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
-   * for more details.
-   *
-   * @static
-   * @memberOf _
-   * @category Object
-   * @param {Object} object The object to query.
-   * @returns {Array} Returns the array of property names.
-   * @example
-   *
-   * function Foo() {
-   *   this.a = 1;
-   *   this.b = 2;
-   * }
-   *
-   * Foo.prototype.c = 3;
-   *
-   * _.keys(new Foo);
-   * // => ['a', 'b'] (iteration order is not guaranteed)
-   *
-   * _.keys('hi');
-   * // => ['0', '1']
-   */
-  var keys = !nativeKeys ? shimKeys : function(object) {
-    var Ctor = object == null ? undefined : object.constructor;
-    if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
-        (typeof object != 'function' && isArrayLike(object))) {
-      return shimKeys(object);
-    }
-    return isObject(object) ? nativeKeys(object) : [];
-  };
-
-  /**
-   * Creates an array of the own and inherited enumerable property names of `object`.
-   *
-   * **Note:** Non-object values are coerced to objects.
-   *
-   * @static
-   * @memberOf _
-   * @category Object
-   * @param {Object} object The object to query.
-   * @returns {Array} Returns the array of property names.
-   * @example
-   *
-   * function Foo() {
-   *   this.a = 1;
-   *   this.b = 2;
-   * }
-   *
-   * Foo.prototype.c = 3;
-   *
-   * _.keysIn(new Foo);
-   * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
-   */
-  function keysIn(object) {
-    if (object == null) {
-      return [];
-    }
-    if (!isObject(object)) {
-      object = Object(object);
-    }
-    var length = object.length;
-    length = (length && isLength(length) &&
-      (isArray(object) || isArguments(object)) && length) || 0;
-
-    var Ctor = object.constructor,
-        index = -1,
-        isProto = typeof Ctor == 'function' && Ctor.prototype === object,
-        result = Array(length),
-        skipIndexes = length > 0;
-
-    while (++index < length) {
-      result[index] = (index + '');
-    }
-    for (var key in object) {
-      if (!(skipIndexes && isIndex(key, length)) &&
-          !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
-        result.push(key);
-      }
-    }
-    return result;
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * This method returns the first argument provided to it.
-   *
-   * @static
-   * @memberOf _
-   * @category Utility
-   * @param {*} value Any value.
-   * @returns {*} Returns `value`.
-   * @example
-   *
-   * var object = { 'user': 'fred' };
-   *
-   * _.identity(object) === object;
-   * // => true
-   */
-  function identity(value) {
-    return value;
-  }
-
-  /*------------------------------------------------------------------------*/
-
-  // Add functions that return wrapped values when chaining.
-  lodash.assign = assign;
-  lodash.keys = keys;
-  lodash.keysIn = keysIn;
-  lodash.restParam = restParam;
-
-  // Add aliases.
-  lodash.extend = assign;
-
-  /*------------------------------------------------------------------------*/
-
-  // Add functions that return unwrapped values when chaining.
-  lodash.clone = clone;
-  lodash.identity = identity;
-  lodash.isArguments = isArguments;
-  lodash.isArray = isArray;
-  lodash.isEqual = isEqual;
-  lodash.isFunction = isFunction;
-  lodash.isNative = isNative;
-  lodash.isObject = isObject;
-  lodash.isPlainObject = isPlainObject;
-  lodash.isTypedArray = isTypedArray;
-  lodash.last = last;
-
-  // Add aliases.
-  lodash.eq = isEqual;
-
-  /*------------------------------------------------------------------------*/
-
-  /**
-   * The semantic version number.
-   *
-   * @static
-   * @memberOf _
-   * @type string
-   */
-  lodash.VERSION = VERSION;
-
-  /*--------------------------------------------------------------------------*/
-
-  // Some AMD build optimizers like r.js check for condition patterns like the following:
-  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
-    // Define as an anonymous module so, through path mapping, it can be
-    // referenced as the "underscore" module.
-    define(function() {
-      return lodash;
-    });
-  }
-}.call(this));

+ 11 - 0
packages/ckeditor5-engine/src/lib/lodash/object.js

@@ -0,0 +1,11 @@
+import assign from './object/assign';
+import extend from './object/extend';
+import keys from './object/keys';
+import keysIn from './object/keysIn';
+
+export default {
+  'assign': assign,
+  'extend': extend,
+  'keys': keys,
+  'keysIn': keysIn
+};

+ 51 - 0
packages/ckeditor5-engine/src/lib/lodash/object/assign.js

@@ -0,0 +1,51 @@
+/**
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize modern exports="es" include="clone,extend,isPlainObject,isObject,isArray,last,isEqual" --development --output src/lib/lodash`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+import assignWith from '../internal/assignWith';
+import baseAssign from '../internal/baseAssign';
+import createAssigner from '../internal/createAssigner';
+
+/**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object. Subsequent sources overwrite property assignments of previous sources.
+ * If `customizer` is provided it's invoked to produce the assigned values.
+ * The `customizer` is bound to `thisArg` and invoked with five arguments:
+ * (objectValue, sourceValue, key, object, source).
+ *
+ * **Note:** This method mutates `object` and is based on
+ * [`Object.assign`](http://ecma-international.org/ecma-262/6.0/#sec-object.assign).
+ *
+ * @static
+ * @memberOf _
+ * @alias extend
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @param {*} [thisArg] The `this` binding of `customizer`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
+ * // => { 'user': 'fred', 'age': 40 }
+ *
+ * // using a customizer callback
+ * var defaults = _.partialRight(_.assign, function(value, other) {
+ *   return _.isUndefined(value) ? other : value;
+ * });
+ *
+ * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+ * // => { 'user': 'barney', 'age': 36 }
+ */
+var assign = createAssigner(function(object, source, customizer) {
+  return customizer
+    ? assignWith(object, source, customizer)
+    : baseAssign(object, source);
+});
+
+export default assign;

+ 2 - 0
packages/ckeditor5-engine/src/lib/lodash/object/extend.js

@@ -0,0 +1,2 @@
+import assign from './assign'
+export default assign;

+ 45 - 0
packages/ckeditor5-engine/src/lib/lodash/object/keys.js

@@ -0,0 +1,45 @@
+import getNative from '../internal/getNative';
+import isArrayLike from '../internal/isArrayLike';
+import isObject from '../lang/isObject';
+import shimKeys from '../internal/shimKeys';
+
+/* Native method references for those with the same name as other `lodash` methods. */
+var nativeKeys = getNative(Object, 'keys');
+
+/**
+ * Creates an array of the own enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects. See the
+ * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+ * for more details.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ *   this.a = 1;
+ *   this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keys(new Foo);
+ * // => ['a', 'b'] (iteration order is not guaranteed)
+ *
+ * _.keys('hi');
+ * // => ['0', '1']
+ */
+var keys = !nativeKeys ? shimKeys : function(object) {
+  var Ctor = object == null ? undefined : object.constructor;
+  if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
+      (typeof object != 'function' && isArrayLike(object))) {
+    return shimKeys(object);
+  }
+  return isObject(object) ? nativeKeys(object) : [];
+};
+
+export default keys;

+ 64 - 0
packages/ckeditor5-engine/src/lib/lodash/object/keysIn.js

@@ -0,0 +1,64 @@
+import isArguments from '../lang/isArguments';
+import isArray from '../lang/isArray';
+import isIndex from '../internal/isIndex';
+import isLength from '../internal/isLength';
+import isObject from '../lang/isObject';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Creates an array of the own and inherited enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ *   this.a = 1;
+ *   this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keysIn(new Foo);
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+ */
+function keysIn(object) {
+  if (object == null) {
+    return [];
+  }
+  if (!isObject(object)) {
+    object = Object(object);
+  }
+  var length = object.length;
+  length = (length && isLength(length) &&
+    (isArray(object) || isArguments(object)) && length) || 0;
+
+  var Ctor = object.constructor,
+      index = -1,
+      isProto = typeof Ctor == 'function' && Ctor.prototype === object,
+      result = Array(length),
+      skipIndexes = length > 0;
+
+  while (++index < length) {
+    result[index] = (index + '');
+  }
+  for (var key in object) {
+    if (!(skipIndexes && isIndex(key, length)) &&
+        !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+      result.push(key);
+    }
+  }
+  return result;
+}
+
+export default keysIn;

+ 5 - 0
packages/ckeditor5-engine/src/lib/lodash/utility.js

@@ -0,0 +1,5 @@
+import identity from './utility/identity';
+
+export default {
+  'identity': identity
+};

+ 20 - 0
packages/ckeditor5-engine/src/lib/lodash/utility/identity.js

@@ -0,0 +1,20 @@
+/**
+ * This method returns the first argument provided to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utility
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * _.identity(object) === object;
+ * // => true
+ */
+function identity(value) {
+  return value;
+}
+
+export default identity;

+ 29 - 31
packages/ckeditor5-engine/src/log.js

@@ -39,36 +39,34 @@
  * @singleton
  */
 
-CKEDITOR.define( () => {
-	const log = {
-		/**
-		 * Logs an error to the console.
-		 *
-		 * Read more about error logging in the {@link core.log} module.
-		 *
-		 * @param {String} message The error message in an `error-name: Error message.` format.
-		 * During the minification process the "Error message" part will be removed to limit the code size
-		 * and a link to this error documentation will be logged to the console.
-		 * @param {Object} [data] Additional data describing the error.
-		 */
-		error( message, data ) {
-			console.error( message, data );
-		},
+const log = {
+	/**
+	 * Logs an error to the console.
+	 *
+	 * Read more about error logging in the {@link core.log} module.
+	 *
+	 * @param {String} message The error message in an `error-name: Error message.` format.
+	 * During the minification process the "Error message" part will be removed to limit the code size
+	 * and a link to this error documentation will be logged to the console.
+	 * @param {Object} [data] Additional data describing the error.
+	 */
+	error( message, data ) {
+		console.error( message, data );
+	},
 
-		/**
-		 * Logs a warning to the console.
-		 *
-		 * Read more about error logging in the {@link core.log} module.
-		 *
-		 * @param {String} message The warning message in a `warning-name: Warning message.` format.
-		 * During the minification process the "Warning message" part will be removed to limit the code size
-		 * and a link to this error documentation will be logged to the console.
-		 * @param {Object} [data] Additional data describing the warning.
-		 */
-		warn( message, data ) {
-			console.warn( message, data );
-		}
-	};
+	/**
+	 * Logs a warning to the console.
+	 *
+	 * Read more about error logging in the {@link core.log} module.
+	 *
+	 * @param {String} message The warning message in a `warning-name: Warning message.` format.
+	 * During the minification process the "Warning message" part will be removed to limit the code size
+	 * and a link to this error documentation will be logged to the console.
+	 * @param {Object} [data] Additional data describing the warning.
+	 */
+	warn( message, data ) {
+		console.warn( message, data );
+	}
+};
 
-	return log;
-} );
+export default log;

+ 420 - 419
packages/ckeditor5-engine/src/model.js

@@ -5,6 +5,11 @@
 
 'use strict';
 
+import EmitterMixin from './emittermixin.js';
+import CKEditorError from './ckeditorerror.js';
+import utilsObject from './lib/lodash/object.js';
+import utilsLang from './lib/lodash/lang.js';
+
 /**
  * The base MVC model class.
  *
@@ -12,499 +17,495 @@
  * @mixins EventEmitter
  */
 
-CKEDITOR.define( [ 'emittermixin', 'ckeditorerror', 'utils' ], ( EmitterMixin, CKEditorError, utils ) => {
-	class Model {
+export default class Model {
+	/**
+	 * Creates a new Model instance.
+	 *
+	 * @param {Object} [attributes] The model state attributes to be set during the instance creation.
+	 * @param {Object} [properties] The properties to be appended to the instance during creation.
+	 * @method constructor
+	 */
+	constructor( attributes, properties ) {
 		/**
-		 * Creates a new Model instance.
+		 * The internal hash containing the model's state.
 		 *
-		 * @param {Object} [attributes] The model state attributes to be set during the instance creation.
-		 * @param {Object} [properties] The properties to be appended to the instance during creation.
-		 * @method constructor
+		 * @property _attributes
+		 * @private
 		 */
-		constructor( attributes, properties ) {
-			/**
-			 * The internal hash containing the model's state.
-			 *
-			 * @property _attributes
-			 * @private
-			 */
-			this._attributes = {};
-
-			/**
-			 * Map containing bindings of this model to external models.
-			 * See {@link #bind}.
-			 *
-			 * @private
-			 * @property {Map}
-			 */
-			this._boundTo = new Map();
-
-			/**
-			 * Object that stores which attributes of this model are bound.
-			 * See {@link #bind}.
-			 *
-			 * @private
-			 * @property {Object}
-			 */
-			this._bound = {};
-
-			// Extend this instance with the additional (out of state) properties.
-			if ( properties ) {
-				utils.extend( this, properties );
-			}
-
-			// Initialize the attributes.
-			if ( attributes ) {
-				this.set( attributes );
-			}
-		}
+		this._attributes = {};
 
 		/**
-		 * Creates and sets the value of a model attribute of this object. This attribute will be part of the model
-		 * state and will be observable.
-		 *
-		 * It accepts also a single object literal containing key/value pairs with attributes to be set.
-		 *
-		 * This method throws the {@link model-set-cannot-override} error if the model instance already
-		 * have a property with a given attribute name. This prevents from mistakenly overriding existing
-		 * properties and methods, but means that `foo.set( 'bar', 1 )` may be slightly slower than `foo.bar = 1`.
+		 * Map containing bindings of this model to external models.
+		 * See {@link #bind}.
 		 *
-		 * @param {String} name The attributes name.
-		 * @param {*} value The attributes value.
+		 * @private
+		 * @property {Map}
 		 */
-		set( name, value ) {
-			// If the first parameter is an Object, we gonna interact through its properties.
-			if ( utils.isObject( name ) ) {
-				Object.keys( name ).forEach( ( attr ) => {
-					this.set( attr, name[ attr ] );
-				}, this );
-
-				return;
-			}
+		this._boundTo = new Map();
 
-			if ( ( name in this ) && !( name in this._attributes ) ) {
-				/**
-				 * Cannot override an existing property.
-				 *
-				 * This error is thrown when trying to {@link Model#set set} an attribute with
-				 * a name of an already existing property. For example:
-				 *
-				 *		let model = new Model();
-				 *		model.property = 1;
-				 *		model.set( 'property', 2 );		// throws
-				 *
-				 *		model.set( 'attr', 1 );
-				 *		model.set( 'attr', 2 );			// ok, because this is an existing attribute.
-				 *
-				 * @error model-set-cannot-override
-				 */
-				throw new CKEditorError( 'model-set-cannot-override: Cannot override an existing property.' );
-			}
+		/**
+		 * Object that stores which attributes of this model are bound.
+		 * See {@link #bind}.
+		 *
+		 * @private
+		 * @property {Object}
+		 */
+		this._bound = {};
 
-			Object.defineProperty( this, name, {
-				enumerable: true,
-				configurable: true,
+		// Extend this instance with the additional (out of state) properties.
+		if ( properties ) {
+			utilsObject.extend( this, properties );
+		}
 
-				get: () => {
-					return this._attributes[ name ];
-				},
+		// Initialize the attributes.
+		if ( attributes ) {
+			this.set( attributes );
+		}
+	}
 
-				set: ( value ) => {
-					const oldValue = this._attributes[ name ];
+	/**
+	 * Creates and sets the value of a model attribute of this object. This attribute will be part of the model
+	 * state and will be observable.
+	 *
+	 * It accepts also a single object literal containing key/value pairs with attributes to be set.
+	 *
+	 * This method throws the {@link model-set-cannot-override} error if the model instance already
+	 * have a property with a given attribute name. This prevents from mistakenly overriding existing
+	 * properties and methods, but means that `foo.set( 'bar', 1 )` may be slightly slower than `foo.bar = 1`.
+	 *
+	 * @param {String} name The attributes name.
+	 * @param {*} value The attributes value.
+	 */
+	set( name, value ) {
+		// If the first parameter is an Object, we gonna interact through its properties.
+		if ( utilsLang.isObject( name ) ) {
+			Object.keys( name ).forEach( ( attr ) => {
+				this.set( attr, name[ attr ] );
+			}, this );
 
-					if ( oldValue !== value ) {
-						this._attributes[ name ] = value;
-						this.fire( 'change', name, value, oldValue );
-						this.fire( 'change:' + name, value, oldValue );
-					}
-				}
-			} );
+			return;
+		}
 
-			this[ name ] = value;
+		if ( ( name in this ) && !( name in this._attributes ) ) {
+			/**
+			 * Cannot override an existing property.
+			 *
+			 * This error is thrown when trying to {@link Model#set set} an attribute with
+			 * a name of an already existing property. For example:
+			 *
+			 *		let model = new Model();
+			 *		model.property = 1;
+			 *		model.set( 'property', 2 );		// throws
+			 *
+			 *		model.set( 'attr', 1 );
+			 *		model.set( 'attr', 2 );			// ok, because this is an existing attribute.
+			 *
+			 * @error model-set-cannot-override
+			 */
+			throw new CKEditorError( 'model-set-cannot-override: Cannot override an existing property.' );
 		}
 
-		/**
-		 * Binds model attributes to another Model instance.
-		 *
-		 * Once bound, the model will immediately share the current state of attributes
-		 * of the model it is bound to and react to the changes to these attributes
-		 * in the future.
-		 *
-		 * To release the binding use {@link #unbind}.
-		 *
-		 *		A.bind( 'a' ).to( B );
-		 *		A.bind( 'a' ).to( B, 'b' );
-		 *		A.bind( 'a', 'b' ).to( B, 'c', 'd' );
-		 *		A.bind( 'a' ).to( B, 'b' ).to( C, 'd' ).as( ( Bb, Cd ) => Bb + Cd );
-		 *
-		 * @param {String...} bindAttrs Model attributes use that will be bound to another model(s).
-		 * @returns {BindChain}
-		 */
-		bind( ...bindAttrs ) {
-			if ( !bindAttrs.length || !isStringArray( bindAttrs ) ) {
-				/**
-				 * All attributes must be strings.
-				 *
-				 * @error model-bind-wrong-attrs
-				 */
-				throw new CKEditorError( 'model-bind-wrong-attrs: All attributes must be strings.' );
-			}
+		Object.defineProperty( this, name, {
+			enumerable: true,
+			configurable: true,
 
-			if ( ( new Set( bindAttrs ) ).size !== bindAttrs.length ) {
-				/**
-				 * Attributes must be unique.
-				 *
-				 * @error model-bind-duplicate-attrs
-				 */
-				throw new CKEditorError( 'model-bind-duplicate-attrs: Attributes must be unique.' );
-			}
+			get: () => {
+				return this._attributes[ name ];
+			},
 
-			bindAttrs.forEach( attrName => {
-				if ( attrName in this._bound ) {
-					/**
-					 * Cannot bind the same attribute more that once.
-					 *
-					 * @error model-bind-rebind
-					 */
-					throw new CKEditorError( 'model-bind-rebind: Cannot bind the same attribute more that once.' );
+			set: ( value ) => {
+				const oldValue = this._attributes[ name ];
+
+				if ( oldValue !== value ) {
+					this._attributes[ name ] = value;
+					this.fire( 'change', name, value, oldValue );
+					this.fire( 'change:' + name, value, oldValue );
 				}
+			}
+		} );
 
-				this._bound[ attrName ] = true;
-			} );
+		this[ name ] = value;
+	}
 
+	/**
+	 * Binds model attributes to another Model instance.
+	 *
+	 * Once bound, the model will immediately share the current state of attributes
+	 * of the model it is bound to and react to the changes to these attributes
+	 * in the future.
+	 *
+	 * To release the binding use {@link #unbind}.
+	 *
+	 *		A.bind( 'a' ).to( B );
+	 *		A.bind( 'a' ).to( B, 'b' );
+	 *		A.bind( 'a', 'b' ).to( B, 'c', 'd' );
+	 *		A.bind( 'a' ).to( B, 'b' ).to( C, 'd' ).as( ( Bb, Cd ) => Bb + Cd );
+	 *
+	 * @param {String...} bindAttrs Model attributes use that will be bound to another model(s).
+	 * @returns {BindChain}
+	 */
+	bind( ...bindAttrs ) {
+		if ( !bindAttrs.length || !isStringArray( bindAttrs ) ) {
 			/**
-			 * @typedef BindChain
-			 * @type Object
-			 * @property {Model} _bindModel The model which initializes the binding.
-			 * @property {Array} _bindAttrs Array of `_bindModel` attributes to be bound.
-			 * @property {Array} _boundTo Array of `to()` model–attributes (`{ model: toModel, attrs: ...toAttrs }`).
-			 * @property {Object} _current The arguments of the last `to( toModel, ...toAttrs )` call, also
-			 * the last item of `_boundTo`.
-			 * @property {Function} to See {@link #_bindTo}.
-			 * @property {Function} as See {@link #_bindAs} (available after `to()` called in chain).
+			 * All attributes must be strings.
+			 *
+			 * @error model-bind-wrong-attrs
 			 */
-			return {
-				_bindModel: this,
-				_bindAttrs: bindAttrs,
-				_boundTo: [],
-				get _current() {
-					return this._boundTo[ this._boundTo.length - 1 ];
-				},
-				to: this._bindTo
-			};
+			throw new CKEditorError( 'model-bind-wrong-attrs: All attributes must be strings.' );
 		}
 
-		/**
-		 * Removes the binding created with {@link #bind}.
-		 *
-		 *		A.unbind( 'a' );
-		 *		A.unbind();
-		 *
-		 * @param {String...} [bindAttrs] Model attributes to unbound. All the bindings will
-		 * be released if not attributes provided.
-		 */
-		unbind( ...unbindAttrs ) {
-			if ( unbindAttrs.length ) {
-				if ( !isStringArray( unbindAttrs ) ) {
-					/**
-					 * Attributes must be strings.
-					 *
-					 * @error model-unbind-wrong-attrs
-					 */
-					throw new CKEditorError( 'model-unbind-wrong-attrs: Attributes must be strings.' );
-				}
-
-				unbindAttrs.forEach( attrName => {
-					for ( let to of this._boundTo ) {
-						// TODO, ES6 destructuring.
-						const boundModel = to[ 0 ];
-						const bindings = to[ 1 ];
-
-						for ( let boundAttrName in bindings ) {
-							if ( bindings[ boundAttrName ].has( attrName ) ) {
-								bindings[ boundAttrName ].delete( attrName );
-							}
-
-							if ( !bindings[ boundAttrName ].size ) {
-								delete bindings[ boundAttrName ];
-							}
-
-							if ( !Object.keys( bindings ).length ) {
-								this._boundTo.delete( boundModel );
-								this.stopListening( boundModel, 'change' );
-							}
-						}
-					}
-
-					delete this._bound[ attrName ];
-				} );
-			} else {
-				this._boundTo.forEach( ( bindings, boundModel ) => {
-					this.stopListening( boundModel, 'change' );
-					this._boundTo.delete( boundModel );
-				} );
-
-				this._bound = {};
-			}
+		if ( ( new Set( bindAttrs ) ).size !== bindAttrs.length ) {
+			/**
+			 * Attributes must be unique.
+			 *
+			 * @error model-bind-duplicate-attrs
+			 */
+			throw new CKEditorError( 'model-bind-duplicate-attrs: Attributes must be unique.' );
 		}
 
-		/**
-		 * A chaining for {@link #bind} providing `.to()` interface.
-		 *
-		 * @protected
-		 * @param {Model} model A model used for binding.
-		 * @param {String...} [toAttrs] Attributes of the model used for binding.
-		 * @returns {BindChain}
-		 */
-		_bindTo( toModel, ...toAttrs ) {
-			if ( !toModel || !( toModel instanceof Model ) ) {
+		bindAttrs.forEach( attrName => {
+			if ( attrName in this._bound ) {
 				/**
-				 * An instance of Model is required.
+				 * Cannot bind the same attribute more that once.
 				 *
-				 * @error model-bind-to-wrong-model
+				 * @error model-bind-rebind
 				 */
-				throw new CKEditorError( 'model-bind-to-wrong-model: An instance of Model is required.' );
+				throw new CKEditorError( 'model-bind-rebind: Cannot bind the same attribute more that once.' );
 			}
 
-			if ( !isStringArray( toAttrs ) ) {
-				/**
-				 * Model attributes must be strings.
-				 *
-				 * @error model-bind-to-wrong-attrs
-				 */
-				throw new CKEditorError( 'model-bind-to-wrong-attrs: Model attributes must be strings.' );
-			}
+			this._bound[ attrName ] = true;
+		} );
 
-			// Eliminate A.bind( 'x' ).to( B, 'y', 'z' )
-			// Eliminate A.bind( 'x', 'y' ).to( B, 'z' )
-			if ( toAttrs.length && toAttrs.length !== this._bindAttrs.length ) {
-				/**
-				 * The number of attributes must match.
-				 *
-				 * @error model-bind-to-attrs-length
-				 */
-				throw new CKEditorError( 'model-bind-to-attrs-length: The number of attributes must match.' );
-			}
+		/**
+		 * @typedef BindChain
+		 * @type Object
+		 * @property {Model} _bindModel The model which initializes the binding.
+		 * @property {Array} _bindAttrs Array of `_bindModel` attributes to be bound.
+		 * @property {Array} _boundTo Array of `to()` model–attributes (`{ model: toModel, attrs: ...toAttrs }`).
+		 * @property {Object} _current The arguments of the last `to( toModel, ...toAttrs )` call, also
+		 * the last item of `_boundTo`.
+		 * @property {Function} to See {@link #_bindTo}.
+		 * @property {Function} as See {@link #_bindAs} (available after `to()` called in chain).
+		 */
+		return {
+			_bindModel: this,
+			_bindAttrs: bindAttrs,
+			_boundTo: [],
+			get _current() {
+				return this._boundTo[ this._boundTo.length - 1 ];
+			},
+			to: this._bindTo
+		};
+	}
 
-			// Eliminate A.bind( 'x' ).to( B, 'y' ), when B.y == undefined.
-			// Eliminate A.bind( 'x' ).to( B ), when B.x == undefined.
-			if ( !hasAttributes( toModel, toAttrs ) || ( !toAttrs.length && !hasAttributes( toModel, this._bindAttrs ) ) ) {
+	/**
+	 * Removes the binding created with {@link #bind}.
+	 *
+	 *		A.unbind( 'a' );
+	 *		A.unbind();
+	 *
+	 * @param {String...} [bindAttrs] Model attributes to unbound. All the bindings will
+	 * be released if not attributes provided.
+	 */
+	unbind( ...unbindAttrs ) {
+		if ( unbindAttrs.length ) {
+			if ( !isStringArray( unbindAttrs ) ) {
 				/**
-				 * Model has no such attribute(s).
+				 * Attributes must be strings.
 				 *
-				 * @error model-bind-to-missing-attr
+				 * @error model-unbind-wrong-attrs
 				 */
-				throw new CKEditorError( 'model-bind-to-missing-attr: Model has no such attribute(s).' );
+				throw new CKEditorError( 'model-unbind-wrong-attrs: Attributes must be strings.' );
 			}
 
-			// Eliminate A.bind( 'x', 'y' ).to( B ).to( C ) when no trailing .as().
-			// Eliminate A.bind( 'x', 'y' ).to( B, 'x', 'y' ).to( C, 'x', 'y' ).
-			if ( this._boundTo.length && ( toAttrs.length > 1 || this._bindAttrs.length > 1 ) ) {
-				/**
-				 * Chaining only allowed for a single attribute.
-				 *
-				 * @error model-bind-to-chain-multiple-attrs
-				 */
-				throw new CKEditorError( 'model-bind-to-chain-multiple-attrs: Chaining only allowed for a single attribute.' );
-			}
+			unbindAttrs.forEach( attrName => {
+				for ( let to of this._boundTo ) {
+					// TODO, ES6 destructuring.
+					const boundModel = to[ 0 ];
+					const bindings = to[ 1 ];
 
-			// When no toAttrs specified, observing MODEL attributes, like MODEL.bind( 'foo' ).to( TOMODEL )
-			if ( !toAttrs.length ) {
-				toAttrs = this._bindAttrs;
-			}
-
-			// Extend current chain with the new binding information.
-			this._boundTo.push( { model: toModel, attrs: toAttrs } );
-
-			setupBinding( this );
-
-			if ( !this.as ) {
-				this.as = this._bindModel._bindAs;
-			}
+					for ( let boundAttrName in bindings ) {
+						if ( bindings[ boundAttrName ].has( attrName ) ) {
+							bindings[ boundAttrName ].delete( attrName );
+						}
 
-			return this;
-		}
+						if ( !bindings[ boundAttrName ].size ) {
+							delete bindings[ boundAttrName ];
+						}
 
-		/**
-		 * A chaining for {@link #bind} providing `.as()` interface.
-		 *
-		 * @protected
-		 * @param {Function} callback A callback to combine model's attributes.
-		 */
-		_bindAs( callback ) {
-			if ( !callback || typeof callback !== 'function' ) {
-				/**
-				 * Callback must be a Function.
-				 *
-				 * @error model-bind-as-wrong-callback
-				 */
-				throw new CKEditorError( 'model-bind-as-wrong-callback: Callback must be a Function.' );
-			}
+						if ( !Object.keys( bindings ).length ) {
+							this._boundTo.delete( boundModel );
+							this.stopListening( boundModel, 'change' );
+						}
+					}
+				}
 
-			this._callback = callback;
+				delete this._bound[ attrName ];
+			} );
+		} else {
+			this._boundTo.forEach( ( bindings, boundModel ) => {
+				this.stopListening( boundModel, 'change' );
+				this._boundTo.delete( boundModel );
+			} );
 
-			updateModelAttrs( this, this._bindAttrs[ 0 ] );
+			this._bound = {};
 		}
 	}
 
 	/**
-	 * Check if the `model` has given `attrs`.
+	 * A chaining for {@link #bind} providing `.to()` interface.
 	 *
-	 * @private
-	 * @param {Model} model Model to be checked.
-	 * @param {Array} arr An array of `String`.
-	 * @returns {Boolean}
+	 * @protected
+	 * @param {Model} model A model used for binding.
+	 * @param {String...} [toAttrs] Attributes of the model used for binding.
+	 * @returns {BindChain}
 	 */
-	function hasAttributes( model, attrs ) {
-		return attrs.every( a => a in model._attributes );
-	}
+	_bindTo( toModel, ...toAttrs ) {
+		if ( !toModel || !( toModel instanceof Model ) ) {
+			/**
+			 * An instance of Model is required.
+			 *
+			 * @error model-bind-to-wrong-model
+			 */
+			throw new CKEditorError( 'model-bind-to-wrong-model: An instance of Model is required.' );
+		}
 
-	/**
-	 * Check if all entries of the array are of `String` type.
-	 *
-	 * @private
-	 * @param {Array} arr An array to be checked.
-	 * @returns {Boolean}
-	 */
-	function isStringArray( arr ) {
-		return arr.every( a => typeof a == 'string' );
-	}
+		if ( !isStringArray( toAttrs ) ) {
+			/**
+			 * Model attributes must be strings.
+			 *
+			 * @error model-bind-to-wrong-attrs
+			 */
+			throw new CKEditorError( 'model-bind-to-wrong-attrs: Model attributes must be strings.' );
+		}
 
-	/**
-	 * Returns all bindings of the `chain._bindModel` to `chain._current.model`
-	 * set by {@link #updateModelBindingsToCurrent}.
-	 *
-	 *		// Given that A == _bindModel and B == _current.model
-	 *		A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
-	 *
-	 *		// The following object is returned
-	 *		{ x: [ 'a', 'c' ], y: [ 'b' ] }
-	 *
-	 *
-	 * @private
-	 * @param {BindChain} chain The chain initialized by {@link Model#bind}.
-	 * @returns {Object}
-	 */
-	function getModelBindingsToCurrent( chain ) {
-		return chain._bindModel._boundTo.get( chain._current.model );
-	}
+		// Eliminate A.bind( 'x' ).to( B, 'y', 'z' )
+		// Eliminate A.bind( 'x', 'y' ).to( B, 'z' )
+		if ( toAttrs.length && toAttrs.length !== this._bindAttrs.length ) {
+			/**
+			 * The number of attributes must match.
+			 *
+			 * @error model-bind-to-attrs-length
+			 */
+			throw new CKEditorError( 'model-bind-to-attrs-length: The number of attributes must match.' );
+		}
 
-	/**
-	 * Updates `chain._bindModel._boundTo` with a binding for `chain._current`.
-	 * The binding can be then retrieved by {@link #getModelBindingsToCurrent}.
-	 *
-	 * @private
-	 * @param {BindChain} chain The chain initialized by {@link Model#bind}.
-	 * @returns {Object}
-	 */
-	function updateModelBindingsToCurrent( chain ) {
-		const currentBindings = getModelBindingsToCurrent( chain );
-		const bindings = currentBindings || {};
+		// Eliminate A.bind( 'x' ).to( B, 'y' ), when B.y == undefined.
+		// Eliminate A.bind( 'x' ).to( B ), when B.x == undefined.
+		if ( !hasAttributes( toModel, toAttrs ) || ( !toAttrs.length && !hasAttributes( toModel, this._bindAttrs ) ) ) {
+			/**
+			 * Model has no such attribute(s).
+			 *
+			 * @error model-bind-to-missing-attr
+			 */
+			throw new CKEditorError( 'model-bind-to-missing-attr: Model has no such attribute(s).' );
+		}
 
-		chain._current.attrs.forEach( ( attrName, index ) => {
-			( bindings[ attrName ] || ( bindings[ attrName ] = new Set() ) )
-				.add( chain._bindAttrs[ index ] );
-		} );
+		// Eliminate A.bind( 'x', 'y' ).to( B ).to( C ) when no trailing .as().
+		// Eliminate A.bind( 'x', 'y' ).to( B, 'x', 'y' ).to( C, 'x', 'y' ).
+		if ( this._boundTo.length && ( toAttrs.length > 1 || this._bindAttrs.length > 1 ) ) {
+			/**
+			 * Chaining only allowed for a single attribute.
+			 *
+			 * @error model-bind-to-chain-multiple-attrs
+			 */
+			throw new CKEditorError( 'model-bind-to-chain-multiple-attrs: Chaining only allowed for a single attribute.' );
+		}
+
+		// When no toAttrs specified, observing MODEL attributes, like MODEL.bind( 'foo' ).to( TOMODEL )
+		if ( !toAttrs.length ) {
+			toAttrs = this._bindAttrs;
+		}
+
+		// Extend current chain with the new binding information.
+		this._boundTo.push( { model: toModel, attrs: toAttrs } );
 
-		if ( !currentBindings ) {
-			chain._bindModel._boundTo.set( chain._current.model, bindings );
+		setupBinding( this );
+
+		if ( !this.as ) {
+			this.as = this._bindModel._bindAs;
 		}
+
+		return this;
 	}
 
 	/**
-	 * Updates the model attribute with given value. If an attribute does not exist,
-	 * it is created on the fly.
+	 * A chaining for {@link #bind} providing `.as()` interface.
 	 *
-	 * @private
-	 * @param {Model} model The model which attribute is updated.
-	 * @param {String} attrName The name of the attribute.
-	 * @param {*} value The value of the attribute.
+	 * @protected
+	 * @param {Function} callback A callback to combine model's attributes.
 	 */
-	function updateModelAttr( model, attrName, value ) {
-		if ( model[ attrName ] ) {
-			model[ attrName ] = value;
-		} else {
-			model.set( attrName, value );
+	_bindAs( callback ) {
+		if ( !callback || typeof callback !== 'function' ) {
+			/**
+			 * Callback must be a Function.
+			 *
+			 * @error model-bind-as-wrong-callback
+			 */
+			throw new CKEditorError( 'model-bind-as-wrong-callback: Callback must be a Function.' );
 		}
+
+		this._callback = callback;
+
+		updateModelAttrs( this, this._bindAttrs[ 0 ] );
 	}
+}
 
-	/**
-	 * Updates all bound attributes of `chain._bindModel` with the `value` of
-	 * `attrName` of `chain._current` model.
-	 *
-	 *		// Given that A == _bindModel and B == _current.model
-	 *		A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
-	 *
-	 *		// The following is updated
-	 *		A.a = A.c = B.x;
-	 *		A.b = B.y;
-	 *
-	 * @private
-	 * @param {BindChain} chain The chain initialized by {@link Model#bind}.
-	 * @param {String} attrName One of the attributes of `chain._current`.
-	 * @param {*} value The value of the attribute.
-	 */
-	function updateModelAttrs( chain, attrName, value ) {
-		const boundAttrs = getModelBindingsToCurrent( chain )[ attrName ];
+/**
+ * Check if the `model` has given `attrs`.
+ *
+ * @private
+ * @param {Model} model Model to be checked.
+ * @param {Array} arr An array of `String`.
+ * @returns {Boolean}
+ */
+function hasAttributes( model, attrs ) {
+	return attrs.every( a => a in model._attributes );
+}
 
-		if ( !boundAttrs ) {
-			return;
-		} else if ( chain._callback ) {
-			// MODEL.bind( 'a' ).to( TOMODEL1, 'b1' )[ .to( TOMODELn, 'bn' ) ].as( callback )
-			//  \-> Collect specific attribute value in the boundTo.model (TOMODELn.bn).
-			//
-			// MODEL.bind( 'a' ).to( TOMODEL1 )[ .to( TOMODELn ) ].as( callback )
-			//  \-> Use model attribute name to collect boundTo attribute value (TOMODELn.a).
-			const values = chain._boundTo.map( boundTo => boundTo.model[ boundTo.attrs[ 0 ] ] );
-
-			// Pass collected attribute values to the callback function.
-			// Whatever is returned it becomes the value of the model's attribute.
-			updateModelAttr(
-				chain._bindModel,
-				chain._bindAttrs[ 0 ],
-				chain._callback.apply( chain._bindModel, values )
-			);
-		} else {
-			// MODEL.bind( 'a' ).to( TOMODEL1 )[ .to( TOMODELn ) ];
-			//  \-> If multiple .to() models but **no** .as( callback ), then the binding is invalid.
-			if ( !chain._callback && chain._boundTo.length > 1 ) {
-				value = undefined;
-			}
+/**
+ * Check if all entries of the array are of `String` type.
+ *
+ * @private
+ * @param {Array} arr An array to be checked.
+ * @returns {Boolean}
+ */
+function isStringArray( arr ) {
+	return arr.every( a => typeof a == 'string' );
+}
 
-			for ( let boundAttrName of boundAttrs ) {
-				updateModelAttr( chain._bindModel, boundAttrName, value );
-			}
-		}
+/**
+ * Returns all bindings of the `chain._bindModel` to `chain._current.model`
+ * set by {@link #updateModelBindingsToCurrent}.
+ *
+ *		// Given that A == _bindModel and B == _current.model
+ *		A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
+ *
+ *		// The following object is returned
+ *		{ x: [ 'a', 'c' ], y: [ 'b' ] }
+ *
+ *
+ * @private
+ * @param {BindChain} chain The chain initialized by {@link Model#bind}.
+ * @returns {Object}
+ */
+function getModelBindingsToCurrent( chain ) {
+	return chain._bindModel._boundTo.get( chain._current.model );
+}
+
+/**
+ * Updates `chain._bindModel._boundTo` with a binding for `chain._current`.
+ * The binding can be then retrieved by {@link #getModelBindingsToCurrent}.
+ *
+ * @private
+ * @param {BindChain} chain The chain initialized by {@link Model#bind}.
+ * @returns {Object}
+ */
+function updateModelBindingsToCurrent( chain ) {
+	const currentBindings = getModelBindingsToCurrent( chain );
+	const bindings = currentBindings || {};
+
+	chain._current.attrs.forEach( ( attrName, index ) => {
+		( bindings[ attrName ] || ( bindings[ attrName ] = new Set() ) )
+			.add( chain._bindAttrs[ index ] );
+	} );
+
+	if ( !currentBindings ) {
+		chain._bindModel._boundTo.set( chain._current.model, bindings );
 	}
+}
 
-	/**
-	 * Starts listening to changes in `chain._current.model` to update `chain._bindModel`
-	 * attributes. Also sets the initial state of `chain._bindModel` bound attributes.
-	 *
-	 * @private
-	 * @param {BindChain} chain The chain initialized by {@link Model#bind}.
-	 */
-	function setupBinding( chain ) {
-		// If there's already a binding between the models (`chain._bindModel` listens to
-		// `chain._current.model`), there's no need to create another `change` event listener.
-		if ( !getModelBindingsToCurrent( chain ) ) {
-			chain._bindModel.listenTo( chain._current.model, 'change', ( evt, attrName, value ) => {
-				updateModelAttrs( chain, attrName, value );
-			} );
+/**
+ * Updates the model attribute with given value. If an attribute does not exist,
+ * it is created on the fly.
+ *
+ * @private
+ * @param {Model} model The model which attribute is updated.
+ * @param {String} attrName The name of the attribute.
+ * @param {*} value The value of the attribute.
+ */
+function updateModelAttr( model, attrName, value ) {
+	if ( model[ attrName ] ) {
+		model[ attrName ] = value;
+	} else {
+		model.set( attrName, value );
+	}
+}
+
+/**
+ * Updates all bound attributes of `chain._bindModel` with the `value` of
+ * `attrName` of `chain._current` model.
+ *
+ *		// Given that A == _bindModel and B == _current.model
+ *		A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
+ *
+ *		// The following is updated
+ *		A.a = A.c = B.x;
+ *		A.b = B.y;
+ *
+ * @private
+ * @param {BindChain} chain The chain initialized by {@link Model#bind}.
+ * @param {String} attrName One of the attributes of `chain._current`.
+ * @param {*} value The value of the attribute.
+ */
+function updateModelAttrs( chain, attrName, value ) {
+	const boundAttrs = getModelBindingsToCurrent( chain )[ attrName ];
+
+	if ( !boundAttrs ) {
+		return;
+	} else if ( chain._callback ) {
+		// MODEL.bind( 'a' ).to( TOMODEL1, 'b1' )[ .to( TOMODELn, 'bn' ) ].as( callback )
+		//  \-> Collect specific attribute value in the boundTo.model (TOMODELn.bn).
+		//
+		// MODEL.bind( 'a' ).to( TOMODEL1 )[ .to( TOMODELn ) ].as( callback )
+		//  \-> Use model attribute name to collect boundTo attribute value (TOMODELn.a).
+		const values = chain._boundTo.map( boundTo => boundTo.model[ boundTo.attrs[ 0 ] ] );
+
+		// Pass collected attribute values to the callback function.
+		// Whatever is returned it becomes the value of the model's attribute.
+		updateModelAttr(
+			chain._bindModel,
+			chain._bindAttrs[ 0 ],
+			chain._callback.apply( chain._bindModel, values )
+		);
+	} else {
+		// MODEL.bind( 'a' ).to( TOMODEL1 )[ .to( TOMODELn ) ];
+		//  \-> If multiple .to() models but **no** .as( callback ), then the binding is invalid.
+		if ( !chain._callback && chain._boundTo.length > 1 ) {
+			value = undefined;
 		}
 
-		updateModelBindingsToCurrent( chain );
+		for ( let boundAttrName of boundAttrs ) {
+			updateModelAttr( chain._bindModel, boundAttrName, value );
+		}
+	}
+}
 
-		// Set initial model state.
-		chain._current.attrs.forEach( attrName => {
-			updateModelAttrs( chain, attrName, chain._current.model[ attrName ] );
+/**
+ * Starts listening to changes in `chain._current.model` to update `chain._bindModel`
+ * attributes. Also sets the initial state of `chain._bindModel` bound attributes.
+ *
+ * @private
+ * @param {BindChain} chain The chain initialized by {@link Model#bind}.
+ */
+function setupBinding( chain ) {
+	// If there's already a binding between the models (`chain._bindModel` listens to
+	// `chain._current.model`), there's no need to create another `change` event listener.
+	if ( !getModelBindingsToCurrent( chain ) ) {
+		chain._bindModel.listenTo( chain._current.model, 'change', ( evt, attrName, value ) => {
+			updateModelAttrs( chain, attrName, value );
 		} );
 	}
 
-	utils.extend( Model.prototype, EmitterMixin );
+	updateModelBindingsToCurrent( chain );
+
+	// Set initial model state.
+	chain._current.attrs.forEach( attrName => {
+		updateModelAttrs( chain, attrName, chain._current.model[ attrName ] );
+	} );
+}
 
-	return Model;
-} );
+utilsObject.extend( Model.prototype, EmitterMixin );
 
 /**
  * Fired when an attribute changed value.

+ 22 - 12
packages/ckeditor5-engine/src/plugin.js

@@ -5,23 +5,33 @@
 
 'use strict';
 
+import Model from './model.js';
+
 /**
  * The base class for CKEditor plugin classes.
  *
- * @class Plugin
- * @extends Model
+ * @class core.Plugin
+ * @extends core.Model
  */
 
-CKEDITOR.define( [ 'model' ], ( Model ) => {
-	class Plugin extends Model {
-		constructor( editor ) {
-			super();
-
-			this.editor = editor;
-		}
+export default class Plugin extends Model {
+	/**
+	 * Creates a new Plugin instance.
+	 *
+	 * @param {core.Editor} editor
+	 */
+	constructor( editor ) {
+		super();
 
-		init() {}
+		/**
+		 * @readonly
+		 * @property {core.Editor}
+		 */
+		this.editor = editor;
 	}
 
-	return Plugin;
-} );
+	/**
+	 * @returns {null/Promise}
+	 */
+	init() {}
+}

+ 131 - 114
packages/ckeditor5-engine/src/plugincollection.js

@@ -5,135 +5,152 @@
 
 'use strict';
 
+import pathUtils from '../ckeditor5/path.js';
+import Plugin from './plugin.js';
+import CKEditorError from './ckeditorerror.js';
+import log from './log.js';
+import load from '../ckeditor5/load.js';
+
 /**
- * Manages a list of CKEditor plugins, including loading, initialization and destruction.
+ * Manages a list of CKEditor plugins, including loading, resolving dependencies and initialization.
  *
  * @class PluginCollection
- * @extends Collection
  */
 
-CKEDITOR.define( [
-	'collection',
-	'plugin',
-	'ckeditorerror',
-	'log'
-], ( Collection, Plugin, CKEditorError, log ) => {
-	class PluginCollection extends Collection {
+export default class PluginCollection {
+	/**
+	 * Creates an instance of the PluginCollection class, initializing it with a set of plugins.
+	 *
+	 * @constructor
+	 * @param {core.Editor} editor
+	 */
+	constructor( editor ) {
 		/**
-		 * Creates an instance of the PluginCollection class, initializing it with a set of plugins.
-		 *
-		 * @constructor
+		 * @protected
+		 * @property {core.Editor}
 		 */
-		constructor( editor ) {
-			super( { idProperty: 'name' } );
-
-			this._editor = editor;
-		}
+		this._editor = editor;
 
 		/**
-		 * Loads a set of plugins and add them to the collection.
-		 *
-		 * @param {String} plugins A comma separated list of plugin names to get loaded.
-		 * @returns {Promise} A promise which gets resolved once all plugins are loaded and available into the
-		 * collection.
-		 * @param {core/Plugin[]} returns.loadedPlugins The array of loaded plugins.
+		 * @protected
+		 * @property {Map}
 		 */
-		load( plugins ) {
-			// The list of plugins which are being loaded (to avoid circular references issues).
-			const loading = {};
-			// Plugins added to the collection (for the purpose of returning an array of loaded plugins).
-			const loaded = [];
-
-			// It may happen that an empty list was passed – don't fail.
-			plugins = plugins ? plugins.split( ',' ) : [];
-
-			// Creates a promise for the loading of each plugin and returns a main promise that resolves when all are
-			// done.
-			return Promise.all( plugins.map( pluginPromise, this ) )
-				.then( () => loaded );
-
-			// Returns a promise that will load the plugin and add it to the collection before resolving.
-			function pluginPromise( plugin ) {
-				return new Promise( ( resolve, reject ) => {
-					// Do nothing if the plugin is already loaded (or if is being loaded right now).
-					if ( this.get( plugin ) || loading[ plugin ] ) {
-						return resolve();
-					}
-
-					CKEDITOR.require( [ 'plugin!' + plugin ],
-						// Success callback.
-						( LoadedPlugin ) => {
-							const deps = getPluginDeps( plugin );
-							const isPluginDep = plugin.indexOf( '/' ) > 0;
-							let loadedPlugin;
-
-							if ( !isPluginDep ) {
-								loadedPlugin = new LoadedPlugin( this._editor );
-
-								if ( !( loadedPlugin instanceof Plugin ) ) {
-									/**
-									 * The plugin is not an instance of Plugin.
-									 *
-									 * @error plugincollection-instance
-									 * @param {String} plugin The name of the plugin that is not an instance of Plugin.
-									 */
-									return reject(
-										new CKEditorError(
-											'plugincollection-instance: The plugin is not an instance of Plugin.',
-											{ plugin: plugin }
-										)
-									);
-								}
-
-								loadedPlugin.name = plugin;
-								loadedPlugin.path = CKEDITOR.getPluginPath( plugin );
-								loadedPlugin.deps = deps;
-							}
-
-							loading[ plugin ] = true;
-
-							// Resolve with a promise that resolves once all dependencies are loaded.
-							resolve(
-								Promise.all( deps.map( pluginPromise, this ) )
-									.then( () => {
-										// Once dependencies are loaded, add the new instance of the loaded plugin to
-										// the collection. This guarantees that dependecies come first in the collection.
-										if ( !isPluginDep ) {
-											this.add( loadedPlugin );
-											loaded.push( loadedPlugin );
-										}
-									} )
-							);
-						},
-						// Error callback.
-						( err ) => {
-							/**
-							 * It was not possible to load the plugin.
-							 *
-							 * @error plugincollection-load
-							 * @param {String} plugin The name of the plugin that could not be loaded.
-							 */
-							log.error( 'plugincollection-load: It was not possible to load the plugin.', { plugin: plugin } );
-							reject( err );
-						}
-					);
-				} );
+		this._plugins = new Map();
+	}
+
+	/**
+	 * Collection iterator. Returns `[ key, plugin ]` pairs. Plugins which are
+	 * kept in the collection twice (under their name and class) will be returned twice.
+	 */
+	[ Symbol.iterator ]() {
+		return this._plugins[ Symbol.iterator ]();
+	}
+
+	/**
+	 * Gets the plugin instance by its name or class.
+	 *
+	 * @param {String/Function} key The name of the plugin or the class.
+	 * @returns {Plugin}
+	 */
+	get( key ) {
+		return this._plugins.get( key );
+	}
+
+	/**
+	 * Loads a set of plugins and add them to the collection.
+	 *
+	 * @param {String[]} plugins An array of plugins to load.
+	 * @returns {Promise} A promise which gets resolved once all plugins are loaded and available into the
+	 * collection.
+	 * @param {core.Plugin[]} returns.loadedPlugins The array of loaded plugins.
+	 */
+	load( plugins ) {
+		const that = this;
+		const editor = this._editor;
+		const loading = new Set();
+		const loaded = [];
+
+		return Promise.all( plugins.map( loadPlugin ) )
+			.then( () => loaded );
+
+		function loadPlugin( pluginClassOrName ) {
+			// The plugin is already loaded or being loaded - do nothing.
+			if ( that.get( pluginClassOrName ) || loading.has( pluginClassOrName ) ) {
+				return;
 			}
 
-			function getPluginDeps( name ) {
-				// Get the list of AMD modules that the plugin depends on.
-				let deps = CKEDITOR._dependencies[ 'plugin!' + name ] || [];
+			let promise = ( typeof pluginClassOrName == 'string' ) ?
+				loadPluginByName( pluginClassOrName ) :
+				loadPluginByClass( pluginClassOrName );
+
+			return promise
+				.catch( ( err ) => {
+					/**
+					 * It was not possible to load the plugin.
+					 *
+					 * @error plugincollection-load
+					 * @param {String} plugin The name of the plugin that could not be loaded.
+					 */
+					log.error( 'plugincollection-load: It was not possible to load the plugin.', { plugin: pluginClassOrName } );
+
+					throw err;
+				} );
+		}
+
+		function loadPluginByName( pluginName ) {
+			return load( pathUtils.getModulePath( pluginName ) )
+				.then( ( PluginModule ) => {
+					return loadPluginByClass( PluginModule.default, pluginName );
+				} );
+		}
+
+		function loadPluginByClass( PluginClass, pluginName ) {
+			return new Promise( ( resolve ) => {
+				loading.add( PluginClass );
+
+				assertIsPlugin( PluginClass );
 
-				deps = deps
-					// Pick only dependencies that are other plugins.
-					.filter( dep => dep.indexOf( 'plugin!' ) === 0 )
-					// Remove the 'plugin!' prefix.
-					.map( dep => dep.substr( 7 ) );
+				if ( PluginClass.requires ) {
+					PluginClass.requires.forEach( loadPlugin );
+				}
 
-				return deps;
+				const plugin = new PluginClass( editor );
+				that._add( PluginClass, plugin );
+				loaded.push( plugin );
+
+				// Expose the plugin also by its name if loaded through load() by name.
+				if ( pluginName ) {
+					that._add( pluginName, plugin );
+				}
+
+				resolve();
+			} );
+		}
+
+		function assertIsPlugin( LoadedPlugin ) {
+			if ( !( LoadedPlugin.prototype instanceof Plugin ) ) {
+				/**
+				 * The loaded plugin module is not an instance of Plugin.
+				 *
+				 * @error plugincollection-instance
+				 * @param {LoadedPlugin} plugin The class which is meant to be loaded as a plugin.
+				 */
+				throw new CKEditorError(
+					'plugincollection-instance: The loaded plugin module is not an instance of Plugin.',
+					{ plugin: LoadedPlugin }
+				);
 			}
 		}
 	}
 
-	return PluginCollection;
-} );
+	/**
+	 * Adds the plugin to the collection. Exposed mainly for testing purposes.
+	 *
+	 * @protected
+	 * @param {String/Function} key The name or the plugin class.
+	 * @param {Plugin} plugin The instance of the plugin.
+	 */
+	_add( key, plugin ) {
+		this._plugins.set( key, plugin );
+	}
+}

+ 91 - 93
packages/ckeditor5-engine/src/treemodel/attribute.js

@@ -5,121 +5,119 @@
 
 'use strict';
 
-CKEDITOR.define( [ 'utils' ], ( utils ) => {
+import langUtils from '../lib/lodash/lang.js';
+
+/**
+ * Attributes can store any additional information for nodes in the data model.
+ *
+ * @class treeModel.Attribute
+ */
+export default class Attribute {
 	/**
-	 * Attributes can store any additional information for nodes in the data model.
+	 * Creates a new instance of the `Attribute` class. Once attribute is created it is immutable.
 	 *
-	 * @class treeModel.Attribute
+	 * @param {String} key Attribute key.
+	 * @param {Mixed} value Attribute value.
+	 * @constructor
 	 */
-	class Attribute {
+	constructor( key, value ) {
 		/**
-		 * Creates a new instance of the `Attribute` class. Once attribute is created it is immutable.
+		 * Attribute key.
 		 *
-		 * @param {String} key Attribute key.
-		 * @param {Mixed} value Attribute value.
-		 * @constructor
+		 * @readonly
+		 * @property {String} key
 		 */
-		constructor( key, value ) {
-			/**
-			 * Attribute key.
-			 *
-			 * @readonly
-			 * @property {String} key
-			 */
-			this.key = key;
-
-			/**
-			 * Attribute value. Note that value may be any type, including objects.
-			 *
-			 * @readonly
-			 * @property {Mixed} value
-			 */
-			this.value = value;
-
-			/**
-			 * Attribute hash. Used to compare attributes. Two attributes with the same key and value will have the same hash.
-			 *
-			 * @readonly
-			 * @private
-			 * @property {String} _hash
-			 */
-			this._hash = this.key + ': ' + JSON.stringify( this.value, sort );
-
-			// If attribute is already registered the registered one should be returned.
-			if ( Attribute._register[ this._hash ] ) {
-				return Attribute._register[ this._hash ];
-			}
-
-			// We do not care about the order, so collections with the same elements should return the same hash.
-			function sort( key, value ) {
-				if ( !utils.isArray( value ) && utils.isObject( value ) ) {
-					const sorted = {};
-
-					// Sort keys and fill up the sorted object.
-					Object.keys( value ).sort().forEach( ( key ) => {
-						sorted[ key ] = value[ key ];
-					} );
-
-					return sorted;
-				} else {
-					return value;
-				}
-			}
-		}
+		this.key = key;
 
 		/**
-		 * Compares two attributes. Returns `true` if two attributes have the same key and value even if the order of keys
-		 * in the value object is different.
+		 * Attribute value. Note that value may be any type, including objects.
 		 *
-		 *		let attr1 = new Attribute( 'foo', { a: 1, b: 2 } );
-		 *		let attr2 = new Attribute( 'foo', { b: 2, a: 1 } );
-		 *		attr1.isEqual( attr2 ); // true
-		 *
-		 * @param {treeModel.Attribute} otherAttr Attribute to compare with.
-		 * @returns {Boolean} True if attributes are equal to each other.
+		 * @readonly
+		 * @property {Mixed} value
 		 */
-		isEqual( otherAttr ) {
-			return this._hash === otherAttr._hash;
-		}
+		this.value = value;
 
 		/**
-		 * To save memory, commonly used attributes may be registered. If an attribute is registered the constructor will
-		 * always return the same instance of this attribute.
+		 * Attribute hash. Used to compare attributes. Two attributes with the same key and value will have the same hash.
 		 *
-		 * Note that attributes are registered globally.
-		 *
-		 *		let attr1 = Attribute.register( 'bold', true );
-		 *		let attr2 = Attribute.register( 'bold', true );
-		 *		let attr3 = new Attribute( 'bold', true );
-		 *		attr1 === attr2 // true
-		 *		attr1 === attr3 // true
-		 *
-		 * @static
-		 * @param {String} key Attribute key.
-		 * @param {Mixed} value Attribute value.
-		 * @returns {treeModel.Attribute} Registered attribute.
+		 * @readonly
+		 * @private
+		 * @property {String} _hash
 		 */
-		static register( key, value ) {
-			const attr = new Attribute( key, value );
+		this._hash = this.key + ': ' + JSON.stringify( this.value, sort );
 
-			if ( this._register[ attr._hash ] ) {
-				return this._register[ attr._hash ];
-			} else {
-				this._register[ attr._hash ] = attr;
+		// If attribute is already registered the registered one should be returned.
+		if ( Attribute._register[ this._hash ] ) {
+			return Attribute._register[ this._hash ];
+		}
 
-				return attr;
+		// We do not care about the order, so collections with the same elements should return the same hash.
+		function sort( key, value ) {
+			if ( !langUtils.isArray( value ) && langUtils.isObject( value ) ) {
+				const sorted = {};
+
+				// Sort keys and fill up the sorted object.
+				Object.keys( value ).sort().forEach( ( key ) => {
+					sorted[ key ] = value[ key ];
+				} );
+
+				return sorted;
+			} else {
+				return value;
 			}
 		}
 	}
 
 	/**
-	 * Register of attributes in which all registered attributes are stored.
+	 * Compares two attributes. Returns `true` if two attributes have the same key and value even if the order of keys
+	 * in the value object is different.
+	 *
+	 *		let attr1 = new Attribute( 'foo', { a: 1, b: 2 } );
+	 *		let attr2 = new Attribute( 'foo', { b: 2, a: 1 } );
+	 *		attr1.isEqual( attr2 ); // true
+	 *
+	 * @param {treeModel.Attribute} otherAttr Attribute to compare with.
+	 * @returns {Boolean} True if attributes are equal to each other.
+	 */
+	isEqual( otherAttr ) {
+		return this._hash === otherAttr._hash;
+	}
+
+	/**
+	 * To save memory, commonly used attributes may be registered. If an attribute is registered the constructor will
+	 * always return the same instance of this attribute.
+	 *
+	 * Note that attributes are registered globally.
+	 *
+	 *		let attr1 = Attribute.register( 'bold', true );
+	 *		let attr2 = Attribute.register( 'bold', true );
+	 *		let attr3 = new Attribute( 'bold', true );
+	 *		attr1 === attr2 // true
+	 *		attr1 === attr3 // true
 	 *
 	 * @static
-	 * @private
-	 * @property {String} _hash
+	 * @param {String} key Attribute key.
+	 * @param {Mixed} value Attribute value.
+	 * @returns {treeModel.Attribute} Registered attribute.
 	 */
-	Attribute._register = {};
+	static register( key, value ) {
+		const attr = new Attribute( key, value );
+
+		if ( this._register[ attr._hash ] ) {
+			return this._register[ attr._hash ];
+		} else {
+			this._register[ attr._hash ] = attr;
+
+			return attr;
+		}
+	}
+}
 
-	return Attribute;
-} );
+/**
+ * Register of attributes in which all registered attributes are stored.
+ *
+ * @static
+ * @private
+ * @property {String} _hash
+ */
+Attribute._register = {};

+ 87 - 89
packages/ckeditor5-engine/src/treemodel/attributelist.js

@@ -5,116 +5,114 @@
 
 'use strict';
 
-CKEDITOR.define( [ 'treemodel/attribute' ], ( Attribute ) => {
+import Attribute from './attribute.js';
+
+/**
+ * List of attributes. Used to manage a set of attributes added to and removed from an object containing
+ * AttributeList.
+ *
+ * @class treeModel.AttributeList
+ */
+export default class AttributeList {
 	/**
-	 * List of attributes. Used to manage a set of attributes added to and removed from an object containing
-	 * AttributeList.
+	 * Creates a list of attributes.
 	 *
-	 * @class treeModel.AttributeList
+	 * @param {Iterable.<treeModel.Attribute>} [attrs] Attributes to initialize this list with.
+	 * @constructor
 	 */
-	class AttributeList {
+	constructor( attrs ) {
 		/**
-		 * Creates a list of attributes.
+		 * Internal set containing the attributes stored by this list.
 		 *
-		 * @param {Iterable.<treeModel.Attribute>} [attrs] Attributes to initialize this list with.
-		 * @constructor
+		 * @private
+		 * @property {Set.<treeModel.Attribute>} _attrs
 		 */
-		constructor( attrs ) {
-			/**
-			 * Internal set containing the attributes stored by this list.
-			 *
-			 * @private
-			 * @property {Set.<treeModel.Attribute>} _attrs
-			 */
 
-			this.setAttrsTo( attrs );
-		}
+		this.setAttrsTo( attrs );
+	}
 
-		/**
-		 * Returns value of an attribute with given key or null if there are no attributes with given key.
-		 *
-		 * @param {String} key The attribute key.
-		 * @returns {*|null} Value of found attribute or null if attribute with given key has not been found.
-		 */
-		getAttr( key ) {
-			for ( let attr of this._attrs ) {
-				if ( attr.key == key ) {
-					return attr.value;
-				}
+	/**
+	 * Returns value of an attribute with given key or null if there are no attributes with given key.
+	 *
+	 * @param {String} key The attribute key.
+	 * @returns {*|null} Value of found attribute or null if attribute with given key has not been found.
+	 */
+	getAttr( key ) {
+		for ( let attr of this._attrs ) {
+			if ( attr.key == key ) {
+				return attr.value;
 			}
-
-			return null;
 		}
 
-		/**
-		 * Returns attribute iterator.
-		 *
-		 * @returns {Iterable.<treeModel.Attribute>} Attribute iterator.
-		 */
-		getAttrs() {
-			return this._attrs[ Symbol.iterator ]();
-		}
+		return null;
+	}
 
-		/**
-		 * Returns `true` if the object contains given {@link treeModel.Attribute attribute} or
-		 * an attribute with the same key if passed parameter was a string.
-		 *
-		 * @param {treeModel.Attribute|String} attrOrKey An attribute or a key to look for.
-		 * @returns {Boolean} True if object contains given attribute or an attribute with the given key.
-		 */
-		hasAttr( attrOrKey ) {
-			if ( attrOrKey instanceof Attribute ) {
-				for ( let attr of this._attrs ) {
-					if ( attr.isEqual( attrOrKey ) ) {
-						return true;
-					}
-				}
-			} else {
-				for ( let attr of this._attrs ) {
-					if ( attr.key == attrOrKey ) {
-						return true;
-					}
+	/**
+	 * Returns attribute iterator.
+	 *
+	 * @returns {Iterable.<treeModel.Attribute>} Attribute iterator.
+	 */
+	getAttrs() {
+		return this._attrs[ Symbol.iterator ]();
+	}
+
+	/**
+	 * Returns `true` if the object contains given {@link treeModel.Attribute attribute} or
+	 * an attribute with the same key if passed parameter was a string.
+	 *
+	 * @param {treeModel.Attribute|String} attrOrKey An attribute or a key to look for.
+	 * @returns {Boolean} True if object contains given attribute or an attribute with the given key.
+	 */
+	hasAttr( attrOrKey ) {
+		if ( attrOrKey instanceof Attribute ) {
+			for ( let attr of this._attrs ) {
+				if ( attr.isEqual( attrOrKey ) ) {
+					return true;
 				}
 			}
-
-			return false;
-		}
-
-		/**
-		 * Removes attribute from the list of attributes.
-		 *
-		 * @param {String} key The attribute key.
-		 */
-		removeAttr( key ) {
+		} else {
 			for ( let attr of this._attrs ) {
-				if ( attr.key == key ) {
-					this._attrs.delete( attr );
-
-					return;
+				if ( attr.key == attrOrKey ) {
+					return true;
 				}
 			}
 		}
 
-		/**
-		 * Sets a given attribute. If the attribute with the same key already exists it will be removed.
-		 *
-		 * @param {treeModel.Attribute} attr Attribute to set.
-		 */
-		setAttr( attr ) {
-			this.removeAttr( attr.key );
+		return false;
+	}
 
-			this._attrs.add( attr );
-		}
+	/**
+	 * Removes attribute from the list of attributes.
+	 *
+	 * @param {String} key The attribute key.
+	 */
+	removeAttr( key ) {
+		for ( let attr of this._attrs ) {
+			if ( attr.key == key ) {
+				this._attrs.delete( attr );
 
-		/**
-		 * Removes all attributes and sets passed attributes.
-		 *
-		 * @param {Iterable.<treeModel.Attribute>} attrs Array of attributes to set.
-		 */
-		setAttrsTo( attrs ) {
-			this._attrs = new Set( attrs );
+				return;
+			}
 		}
 	}
 
-	return AttributeList;
-} );
+	/**
+	 * Sets a given attribute. If the attribute with the same key already exists it will be removed.
+	 *
+	 * @param {treeModel.Attribute} attr Attribute to set.
+	 */
+	setAttr( attr ) {
+		this.removeAttr( attr.key );
+
+		this._attrs.add( attr );
+	}
+
+	/**
+	 * Removes all attributes and sets passed attributes.
+	 *
+	 * @param {Iterable.<treeModel.Attribute>} attrs Array of attributes to set.
+	 */
+	setAttrsTo( attrs ) {
+		this._attrs = new Set( attrs );
+	}
+}

+ 18 - 14
packages/ckeditor5-engine/src/treemodel/batch.js

@@ -9,17 +9,21 @@
 //
 // To solve circular dependencies (deltas need to require Batch class), Batch class body is moved
 // to treemodel/delta/batch-base.
-CKEDITOR.define( [
-	'treemodel/delta/batch-base',
-	'treemodel/delta/insertdelta',
-	'treemodel/delta/weakinsertdelta',
-	'treemodel/delta/movedelta',
-	'treemodel/delta/removedelta',
-	'treemodel/delta/attributedelta',
-	'treemodel/delta/splitdelta',
-	'treemodel/delta/mergedelta',
-	'treemodel/delta/wrapdelta',
-	'treemodel/delta/unwrapdelta'
-], ( Batch ) => {
-	return Batch;
-} );
+
+import Batch from './delta/batch-base.js';
+
+/* jshint ignore:start */
+
+import d1 from './delta/insertdelta.js';
+import d2 from './delta/weakinsertdelta.js';
+import d3 from './delta/movedelta.js';
+import d4 from './delta/removedelta.js';
+import d5 from './delta/attributedelta.js';
+import d6 from './delta/splitdelta.js';
+import d7 from './delta/mergedelta.js';
+import d8 from './delta/wrapdelta.js';
+import d9 from './delta/unwrapdelta.js';
+
+/* jshint ignore:end */
+
+export default Batch;

+ 20 - 22
packages/ckeditor5-engine/src/treemodel/character.js

@@ -5,32 +5,30 @@
 
 'use strict';
 
-CKEDITOR.define( [ 'treemodel/node' ], ( Node ) => {
+import Node from './node.js';
+
+/**
+ * Data structure for character stored in the tree data model.
+ *
+ * @class treeModel.Character
+ */
+export default class Character extends Node {
 	/**
-	 * Data structure for character stored in the tree data model.
+	 * Creates character linear item.
 	 *
-	 * @class treeModel.Character
+	 * @param {String} character Described character.
+	 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
+	 * @constructor
 	 */
-	class Character extends Node {
+	constructor( character, attrs ) {
+		super( attrs );
+
 		/**
-		 * Creates character linear item.
+		 * Described character.
 		 *
-		 * @param {String} character Described character.
-		 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
-		 * @constructor
+		 * @readonly
+		 * @property {String} character
 		 */
-		constructor( character, attrs ) {
-			super( attrs );
-
-			/**
-			 * Described character.
-			 *
-			 * @readonly
-			 * @property {String} character
-			 */
-			this.character = character;
-		}
+		this.character = character;
 	}
-
-	return Character;
-} );
+}

+ 133 - 137
packages/ckeditor5-engine/src/treemodel/delta/attributedelta.js

@@ -5,156 +5,152 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/operation/attributeoperation',
-	'treemodel/position',
-	'treemodel/range',
-	'treemodel/positioniterator',
-	'treemodel/attribute',
-	'treemodel/element'
-], ( Delta, register, AttributeOperation, Position, Range, PositionIterator, Attribute, Element ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, change methods ({@link treeModel.Batch#setAttr}
-	 * and {@link treeModel.Batch#removeAttr}) use `AttributeDelta` class which inherits from the `Delta` class and may
-	 * overwrite some methods.
-	 *
-	 * @class treeModel.delta.AttributeDelta
-	 */
-	class AttributeDelta extends Delta {}
-
-	/**
-	 * Sets the value of the attribute of the node or on the range.
-	 *
-	 * @chainable
-	 * @method setAttr
-	 * @memberOf treeModel.Batch
-	 * @param {String} key Attribute key.
-	 * @param {*} value Attribute new value.
-	 * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range on which the attribute will be set.
-	 */
-	register( 'setAttr', function( key, value, nodeOrRange ) {
-		attribute( this, key, value, nodeOrRange );
-
-		return this;
-	} );
-
-	/**
-	 * Removes an attribute from the range.
-	 *
-	 * @chainable
-	 * @method removeAttr
-	 * @memberOf treeModel.Batch
-	 * @param {String} key Attribute key.
-	 * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range on which the attribute will be removed.
-	 */
-	register( 'removeAttr', function( key, nodeOrRange ) {
-		attribute( this, key, null, nodeOrRange );
-
-		return this;
-	} );
-
-	function attribute( batch, key, value, nodeOrRange ) {
-		const delta = new AttributeDelta();
-
-		if ( nodeOrRange instanceof Range ) {
-			changeRange( batch.doc, delta, key, value, nodeOrRange );
-		} else {
-			changeNode( batch.doc, delta, key, value, nodeOrRange );
-		}
+import Delta from './delta.js';
+import register from './register.js';
+import AttributeOperation from '../operation/attributeoperation.js';
+import Position from '../position.js';
+import Range from '../range.js';
+import PositionIterator from '../positioniterator.js';
+import Attribute from '../attribute.js';
+import Element from '../element.js';
+
+/**
+ * To provide specific OT behavior and better collisions solving, change methods ({@link treeModel.Batch#setAttr}
+ * and {@link treeModel.Batch#removeAttr}) use `AttributeDelta` class which inherits from the `Delta` class and may
+ * overwrite some methods.
+ *
+ * @class treeModel.delta.AttributeDelta
+ */
+export default class AttributeDelta extends Delta {}
+
+/**
+ * Sets the value of the attribute of the node or on the range.
+ *
+ * @chainable
+ * @method setAttr
+ * @memberOf treeModel.Batch
+ * @param {String} key Attribute key.
+ * @param {*} value Attribute new value.
+ * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range on which the attribute will be set.
+ */
+register( 'setAttr', function( key, value, nodeOrRange ) {
+	attribute( this, key, value, nodeOrRange );
+
+	return this;
+} );
+
+/**
+ * Removes an attribute from the range.
+ *
+ * @chainable
+ * @method removeAttr
+ * @memberOf treeModel.Batch
+ * @param {String} key Attribute key.
+ * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range on which the attribute will be removed.
+ */
+register( 'removeAttr', function( key, nodeOrRange ) {
+	attribute( this, key, null, nodeOrRange );
+
+	return this;
+} );
+
+function attribute( batch, key, value, nodeOrRange ) {
+	const delta = new AttributeDelta();
 
-		batch.addDelta( delta );
+	if ( nodeOrRange instanceof Range ) {
+		changeRange( batch.doc, delta, key, value, nodeOrRange );
+	} else {
+		changeNode( batch.doc, delta, key, value, nodeOrRange );
 	}
 
-	function changeNode( doc, delta, key, value, node ) {
-		const previousValue = node.getAttr( key );
-		let range;
-
-		if ( previousValue != value ) {
-			if ( node instanceof Element ) {
-				// If we change the attribute of the element, we do not want to change attributes of its children, so
-				// the end on the range can not be put after the closing tag, it should be inside that element with the
-				// offset 0, so the range will contains only the opening tag...
-				range = new Range( Position.createBefore( node ), Position.createFromParentAndOffset( node, 0 ) );
-			} else {
-				// ...but for characters we can not put the range inside it, so we end the range after that character.
-				range = new Range( Position.createBefore( node ), Position.createAfter( node ) );
-			}
+	batch.addDelta( delta );
+}
 
-			const operation = new AttributeOperation(
-					range,
-					previousValue ? new Attribute( key, previousValue ) : null,
-					value ? new Attribute( key, value ) : null,
-					doc.version
-				);
+function changeNode( doc, delta, key, value, node ) {
+	const previousValue = node.getAttr( key );
+	let range;
 
-			doc.applyOperation( operation );
-			delta.addOperation( operation );
+	if ( previousValue != value ) {
+		if ( node instanceof Element ) {
+			// If we change the attribute of the element, we do not want to change attributes of its children, so
+			// the end on the range can not be put after the closing tag, it should be inside that element with the
+			// offset 0, so the range will contains only the opening tag...
+			range = new Range( Position.createBefore( node ), Position.createFromParentAndOffset( node, 0 ) );
+		} else {
+			// ...but for characters we can not put the range inside it, so we end the range after that character.
+			range = new Range( Position.createBefore( node ), Position.createAfter( node ) );
 		}
-	}
 
-	// Because attribute operation needs to have the same attribute value on the whole range, this function split the range
-	// into smaller parts.
-	function changeRange( doc, delta, key, value, range ) {
-		// Position of the last split, the beginning of the new range.
-		let lastSplitPosition = range.start;
-
-		// Currently position in the scanning range. Because we need value after the position, it is not a current
-		// position of the iterator but the previous one (we need to iterate one more time to get the value after).
-		let position;
-		// Value before the currently position.
-		let valueBefore;
-		// Value after the currently position.
-		let valueAfter;
-
-		// Because we need not only a node, but also a position, we can not use ( value of range ).
-		const iterator = range[ Symbol.iterator ]();
-		// Iterator state.
-		let next = iterator.next();
-
-		while ( !next.done ) {
-			// We check values only when the range contains given element, that is when the iterator "enters" the element.
-			// To prevent double-checking or not needed checking, we filter-out iterator values for ELEMENT_LEAVE position.
-			if ( next.value.type != PositionIterator.ELEMENT_LEAVE ) {
-				valueAfter = next.value.node.getAttr( key );
-
-				// At the first run of the iterator the position in undefined. We also do not have a valueBefore, but
-				// because valueAfter may be null, valueBefore may be equal valueAfter ( undefined == null ).
-				if ( position && valueBefore != valueAfter ) {
-					// if valueBefore == value there is nothing to change, so we add operation only if these values are different.
-					if ( valueBefore != value ) {
-						addOperation();
-					}
-
-					lastSplitPosition = position;
+		const operation = new AttributeOperation(
+				range,
+				previousValue ? new Attribute( key, previousValue ) : null,
+				value ? new Attribute( key, value ) : null,
+				doc.version
+			);
+
+		doc.applyOperation( operation );
+		delta.addOperation( operation );
+	}
+}
+
+// Because attribute operation needs to have the same attribute value on the whole range, this function split the range
+// into smaller parts.
+function changeRange( doc, delta, key, value, range ) {
+	// Position of the last split, the beginning of the new range.
+	let lastSplitPosition = range.start;
+
+	// Currently position in the scanning range. Because we need value after the position, it is not a current
+	// position of the iterator but the previous one (we need to iterate one more time to get the value after).
+	let position;
+	// Value before the currently position.
+	let valueBefore;
+	// Value after the currently position.
+	let valueAfter;
+
+	// Because we need not only a node, but also a position, we can not use ( value of range ).
+	const iterator = range[ Symbol.iterator ]();
+	// Iterator state.
+	let next = iterator.next();
+
+	while ( !next.done ) {
+		// We check values only when the range contains given element, that is when the iterator "enters" the element.
+		// To prevent double-checking or not needed checking, we filter-out iterator values for ELEMENT_LEAVE position.
+		if ( next.value.type != PositionIterator.ELEMENT_LEAVE ) {
+			valueAfter = next.value.node.getAttr( key );
+
+			// At the first run of the iterator the position in undefined. We also do not have a valueBefore, but
+			// because valueAfter may be null, valueBefore may be equal valueAfter ( undefined == null ).
+			if ( position && valueBefore != valueAfter ) {
+				// if valueBefore == value there is nothing to change, so we add operation only if these values are different.
+				if ( valueBefore != value ) {
+					addOperation();
 				}
 
-				position = iterator.position;
-				valueBefore = valueAfter;
+				lastSplitPosition = position;
 			}
 
-			next = iterator.next();
+			position = iterator.position;
+			valueBefore = valueAfter;
 		}
 
-		// Because position in the loop is not the iterator position (see let position comment), the last position in
-		// the while loop will be last but one position in the range. We need to check the last position manually.
-		if ( position instanceof Position && position != lastSplitPosition && valueBefore != value ) {
-			addOperation();
-		}
-
-		function addOperation() {
-			const operation = new AttributeOperation(
-					new Range( lastSplitPosition, position ),
-					valueBefore ? new Attribute( key, valueBefore ) : null,
-					value ? new Attribute( key, value ) : null,
-					doc.version
-				);
+		next = iterator.next();
+	}
 
-			doc.applyOperation( operation );
-			delta.addOperation( operation );
-		}
+	// Because position in the loop is not the iterator position (see let position comment), the last position in
+	// the while loop will be last but one position in the range. We need to check the last position manually.
+	if ( position instanceof Position && position != lastSplitPosition && valueBefore != value ) {
+		addOperation();
 	}
 
-	return AttributeDelta;
-} );
+	function addOperation() {
+		const operation = new AttributeOperation(
+				new Range( lastSplitPosition, position ),
+				valueBefore ? new Attribute( key, valueBefore ) : null,
+				value ? new Attribute( key, value ) : null,
+				doc.version
+			);
+
+		doc.applyOperation( operation );
+		delta.addOperation( operation );
+	}
+}

+ 99 - 101
packages/ckeditor5-engine/src/treemodel/delta/batch-base.js

@@ -5,119 +5,117 @@
 
 'use strict';
 
-CKEDITOR.define( [ 'ckeditorerror' ], ( CKEditorError ) => {
+import CKEditorError from '../../ckeditorerror.js';
+
+/**
+ * The Batch class groups document changes (deltas). All deltas grouped in a single Batch can be
+ * reverted together, so you can think about the Batch as a single undo step. If you want to extend one
+ * undo step you can call another method on the same Batch object. If you want to create a separate undo step
+ * you can create a new Batch.
+ *
+ * For example to create two separate undo steps you can call:
+ *
+ *		doc.batch().insert( firstPosition, 'foo' );
+ *		doc.batch().insert( secondPosition, 'bar' );
+ *
+ * To create a single undo step:
+ *
+ *		const batch = doc.batch();
+ *		batch.insert( firstPosition, 'foo' );
+ *		batch.insert( secondPosition, 'bar' );
+ *
+ * Note that all document modification methods (insert, remove, split, etc.) are chainable so you can shorten code to:
+ *
+ *		doc.batch().insert( firstPosition, 'foo' ).insert( secondPosition, 'bar' );
+ *
+ * @class treeModel.Batch
+ */
+export default class Batch {
 	/**
-	 * The Batch class groups document changes (deltas). All deltas grouped in a single Batch can be
-	 * reverted together, so you can think about the Batch as a single undo step. If you want to extend one
-	 * undo step you can call another method on the same Batch object. If you want to create a separate undo step
-	 * you can create a new Batch.
-	 *
-	 * For example to create two separate undo steps you can call:
-	 *
-	 *		doc.batch().insert( firstPosition, 'foo' );
-	 *		doc.batch().insert( secondPosition, 'bar' );
-	 *
-	 * To create a single undo step:
-	 *
-	 *		const batch = doc.batch();
-	 *		batch.insert( firstPosition, 'foo' );
-	 *		batch.insert( secondPosition, 'bar' );
+	 * Creates Batch instance. Not recommended to use directly, use {@link treeModel.Document#batch} instead.
 	 *
-	 * Note that all document modification methods (insert, remove, split, etc.) are chainable so you can shorten code to:
-	 *
-	 *		doc.batch().insert( firstPosition, 'foo' ).insert( secondPosition, 'bar' );
-	 *
-	 * @class treeModel.Batch
+	 * @constructor
+	 * @param {treeModel.Document} doc Document which this Batch changes.
 	 */
-	class Batch {
+	constructor( doc ) {
 		/**
-		 * Creates Batch instance. Not recommended to use directly, use {@link treeModel.Document#batch} instead.
+		 * Document which this Batch changes.
 		 *
-		 * @constructor
-		 * @param {treeModel.Document} doc Document which this Batch changes.
+		 * @readonly
+		 * @type {treeModel.Document}
 		 */
-		constructor( doc ) {
-			/**
-			 * Document which this Batch changes.
-			 *
-			 * @readonly
-			 * @type {treeModel.Document}
-			 */
-			this.doc = doc;
-
-			/**
-			 * Array of deltas which compose Batch.
-			 *
-			 * @readonly
-			 * @type {Array.<treeModel.delta.Delta>}
-			 */
-			this.deltas = [];
-		}
+		this.doc = doc;
 
 		/**
-		 * Adds delta to the Batch instance. All modification methods (insert, remove, split, etc.) use this method
-		 * to add created deltas.
+		 * Array of deltas which compose Batch.
 		 *
-		 * @param {treeModel.delta.Delta} delta Delta to add.
-		 * @return {treeModel.delta.Delta} Added delta.
+		 * @readonly
+		 * @type {Array.<treeModel.delta.Delta>}
 		 */
-		addDelta( delta ) {
-			delta.batch = this;
-			this.deltas.push( delta );
+		this.deltas = [];
+	}
 
-			return delta;
-		}
+	/**
+	 * Adds delta to the Batch instance. All modification methods (insert, remove, split, etc.) use this method
+	 * to add created deltas.
+	 *
+	 * @param {treeModel.delta.Delta} delta Delta to add.
+	 * @return {treeModel.delta.Delta} Added delta.
+	 */
+	addDelta( delta ) {
+		delta.batch = this;
+		this.deltas.push( delta );
 
-		/**
-		 * Static method to register Batch methods. To make code scalable Batch do not have modification
-		 * methods built in. They can be registered using this method.
-		 *
-		 * This method checks if there is no naming collision and throws `batch-register-taken` if the method name
-		 * is already taken.
-		 *
-		 * Besides that no magic happens here, the method is added to the `Batch` class prototype.
-		 *
-		 * For example:
-		 *
-		 *		Batch.register( 'insert', function( position, nodes ) {
-		 *			// You can use a class inherit from Delta if that class should handle OT in the special way.
-		 *			const delta = new Delta();
-		 *
-		 * 			// Create operations which should be components of this delta.
-		 *			const operation = new InsertOperation( position, nodes, this.doc.version );
-		 *
-		 *			// Remember to apply every operation, no magic, you need to do it manually.
-		 *			this.doc.applyOperation( operation );
-		 *
-		 *			// Add operation to the delta.
-		 *			delta.addOperation( operation );
-		 *
-		 *			// Add delta to the Batch instance.
-		 *			this.addDelta( delta );
-		 *
-		 * 			// Make this method chainable.
-		 * 			return this;
-		 *		} );
-		 *
-		 * @param {String} name Method name.
-		 * @param {Function} creator Method body.
-		 */
-		static register( name, creator ) {
-			if ( Batch.prototype[ name ] ) {
-				/**
-				 * This batch method name is already taken.
-				 *
-				 * @error batch-register-taken
-				 * @param {String} name
-				 */
-				throw new CKEditorError(
-					'batch-register-taken: This batch method name is already taken.',
-					{ name: name } );
-			}
+		return delta;
+	}
 
-			Batch.prototype[ name ] = creator;
+	/**
+	 * Static method to register Batch methods. To make code scalable Batch do not have modification
+	 * methods built in. They can be registered using this method.
+	 *
+	 * This method checks if there is no naming collision and throws `batch-register-taken` if the method name
+	 * is already taken.
+	 *
+	 * Besides that no magic happens here, the method is added to the `Batch` class prototype.
+	 *
+	 * For example:
+	 *
+	 *		Batch.register( 'insert', function( position, nodes ) {
+	 *			// You can use a class inherit from Delta if that class should handle OT in the special way.
+	 *			const delta = new Delta();
+	 *
+	 * 			// Create operations which should be components of this delta.
+	 *			const operation = new InsertOperation( position, nodes, this.doc.version );
+	 *
+	 *			// Remember to apply every operation, no magic, you need to do it manually.
+	 *			this.doc.applyOperation( operation );
+	 *
+	 *			// Add operation to the delta.
+	 *			delta.addOperation( operation );
+	 *
+	 *			// Add delta to the Batch instance.
+	 *			this.addDelta( delta );
+	 *
+	 * 			// Make this method chainable.
+	 * 			return this;
+	 *		} );
+	 *
+	 * @param {String} name Method name.
+	 * @param {Function} creator Method body.
+	 */
+	static register( name, creator ) {
+		if ( Batch.prototype[ name ] ) {
+			/**
+			 * This batch method name is already taken.
+			 *
+			 * @error batch-register-taken
+			 * @param {String} name
+			 */
+			throw new CKEditorError(
+				'batch-register-taken: This batch method name is already taken.',
+				{ name: name } );
 		}
-	}
 
-	return Batch;
-} );
+		Batch.prototype[ name ] = creator;
+	}
+}

+ 35 - 39
packages/ckeditor5-engine/src/treemodel/delta/delta.js

@@ -5,54 +5,50 @@
 
 'use strict';
 
-CKEDITOR.define( [], () => {
+/**
+ * Base class for all deltas.
+ *
+ * Delta is a single, from the user action point of view, change in the editable document, like insert, split or
+ * rename element. Delta is composed of operations, which are unit changes needed to be done to execute user action.
+ *
+ * Multiple deltas are grouped into a single {@link treeModel.Batch}.
+ *
+ * @class treeModel.delta.Delta
+ */
+export default class Delta {
 	/**
-	 * Base class for all deltas.
-	 *
-	 * Delta is a single, from the user action point of view, change in the editable document, like insert, split or
-	 * rename element. Delta is composed of operations, which are unit changes needed to be done to execute user action.
+	 * Creates a delta instance.
 	 *
-	 * Multiple deltas are grouped into a single {@link treeModel.Batch}.
-	 *
-	 * @class treeModel.delta.Delta
+	 * @constructor
 	 */
-	class Delta {
+	constructor() {
 		/**
-		 * Creates a delta instance.
+		 * {@link treeModel.Batch} which delta is a part of. This property is null by default and set by the
+		 * {@link treeModel.Batch#addDelta} method.
 		 *
-		 * @constructor
+		 * @readonly
+		 * @type {treeModel.Batch}
 		 */
-		constructor() {
-			/**
-			 * {@link treeModel.Batch} which delta is a part of. This property is null by default and set by the
-			 * {@link treeModel.Batch#addDelta} method.
-			 *
-			 * @readonly
-			 * @type {treeModel.Batch}
-			 */
-			this.batch = null;
-
-			/**
-			 * Array of operations which compose delta.
-			 *
-			 * @readonly
-			 * @type {treeModel.operation.Operation[]}
-			 */
-			this.operations = [];
-		}
+		this.batch = null;
 
 		/**
-		 * Add operation to the delta.
+		 * Array of operations which compose delta.
 		 *
-		 * @param {treeModel.operation.Operation} operation Operation instance.
+		 * @readonly
+		 * @type {treeModel.operation.Operation[]}
 		 */
-		addOperation( operation ) {
-			operation.delta = this;
-			this.operations.push( operation );
-
-			return operation;
-		}
+		this.operations = [];
 	}
 
-	return Delta;
-} );
+	/**
+	 * Add operation to the delta.
+	 *
+	 * @param {treeModel.operation.Operation} operation Operation instance.
+	 */
+	addOperation( operation ) {
+		operation.delta = this;
+		this.operations.push( operation );
+
+		return operation;
+	}
+}

+ 28 - 32
packages/ckeditor5-engine/src/treemodel/delta/insertdelta.js

@@ -5,40 +5,36 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/operation/insertoperation'
-], ( Delta, register, InsertOperation ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, the {@link treeModel.Batch#insert} method
-	 * uses the `InsertDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.InsertDelta
-	 */
-	class InsertDelta extends Delta {}
+import Delta from './delta.js';
+import register from './register.js';
+import InsertOperation from '../operation/insertoperation.js';
 
-	/**
-	 * Inserts a node or nodes at the given position.
-	 *
-	 * @chainable
-	 * @memberOf treeModel.Batch
-	 * @method insert
-	 * @param {treeModel.Position} position Position of insertion.
-	 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
-	 * List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
-	 */
-	register( 'insert', function( position, nodes ) {
-		const delta = new InsertDelta();
+/**
+ * To provide specific OT behavior and better collisions solving, the {@link treeModel.Batch#insert} method
+ * uses the `InsertDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.InsertDelta
+ */
+export default class InsertDelta extends Delta {}
 
-		const operation = new InsertOperation( position, nodes, this.doc.version );
-		this.doc.applyOperation( operation );
-		delta.addOperation( operation );
+/**
+ * Inserts a node or nodes at the given position.
+ *
+ * @chainable
+ * @memberOf treeModel.Batch
+ * @method insert
+ * @param {treeModel.Position} position Position of insertion.
+ * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
+ * List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
+ */
+register( 'insert', function( position, nodes ) {
+	const delta = new InsertDelta();
 
-		this.addDelta( delta );
+	const operation = new InsertOperation( position, nodes, this.doc.version );
+	this.doc.applyOperation( operation );
+	delta.addOperation( operation );
 
-		return this;
-	} );
+	this.addDelta( delta );
 
-	return InsertDelta;
-} );
+	return this;
+} );

+ 57 - 61
packages/ckeditor5-engine/src/treemodel/delta/mergedelta.js

@@ -5,74 +5,70 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/position',
-	'treemodel/element',
-	'treemodel/operation/removeoperation',
-	'treemodel/operation/moveoperation',
-	'ckeditorerror'
-], ( Delta, register, Position, Element, RemoveOperation, MoveOperation, CKEditorError ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
-	 * uses the `MergeDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.MergeDelta
-	 */
-	class MergeDelta extends Delta {}
+import Delta from './delta.js';
+import register from './register.js';
+import Position from '../position.js';
+import Element from '../element.js';
+import RemoveOperation from '../operation/removeoperation.js';
+import MoveOperation from '../operation/moveoperation.js';
+import CKEditorError from '../../ckeditorerror.js';
 
-	/**
-	 * Merges two siblings at the given position.
-	 *
-	 * Node before and after the position have to be an element. Otherwise `batch-merge-no-element-before` or
-	 * `batch-merge-no-element-after` error will be thrown.
-	 *
-	 * @chainable
-	 * @method merge
-	 * @memberOf treeModel.Batch
-	 * @param {treeModel.Position} position Position of merge.
-	 */
-	register( 'merge', function( position ) {
-		const delta = new MergeDelta();
-		const nodeBefore = position.nodeBefore;
-		const nodeAfter = position.nodeAfter;
+/**
+ * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
+ * uses the `MergeDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.MergeDelta
+ */
+export default class MergeDelta extends Delta {}
 
-		if ( !( nodeBefore instanceof Element ) ) {
-			/**
-			 * Node before merge position must be an element.
-			 *
-			 * @error batch-merge-no-element-before
-			 */
-			throw new CKEditorError(
-				'batch-merge-no-element-before: Node before merge position must be an element.' );
-		}
+/**
+ * Merges two siblings at the given position.
+ *
+ * Node before and after the position have to be an element. Otherwise `batch-merge-no-element-before` or
+ * `batch-merge-no-element-after` error will be thrown.
+ *
+ * @chainable
+ * @method merge
+ * @memberOf treeModel.Batch
+ * @param {treeModel.Position} position Position of merge.
+ */
+register( 'merge', function( position ) {
+	const delta = new MergeDelta();
+	const nodeBefore = position.nodeBefore;
+	const nodeAfter = position.nodeAfter;
 
-		if ( !( nodeAfter instanceof Element ) ) {
-			/**
-			 * Node after merge position must be an element.
-			 *
-			 * @error batch-merge-no-element-after
-			 */
-			throw new CKEditorError(
-				'batch-merge-no-element-after: Node after merge position must be an element.' );
-		}
+	if ( !( nodeBefore instanceof Element ) ) {
+		/**
+		 * Node before merge position must be an element.
+		 *
+		 * @error batch-merge-no-element-before
+		 */
+		throw new CKEditorError(
+			'batch-merge-no-element-before: Node before merge position must be an element.' );
+	}
 
-		const positionAfter = Position.createFromParentAndOffset( nodeAfter, 0 );
-		const positionBefore = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
+	if ( !( nodeAfter instanceof Element ) ) {
+		/**
+		 * Node after merge position must be an element.
+		 *
+		 * @error batch-merge-no-element-after
+		 */
+		throw new CKEditorError(
+			'batch-merge-no-element-after: Node after merge position must be an element.' );
+	}
 
-		const move = new MoveOperation( positionAfter, nodeAfter.getChildCount(), positionBefore, this.doc.version );
-		this.doc.applyOperation( move );
-		delta.addOperation( move );
+	const positionAfter = Position.createFromParentAndOffset( nodeAfter, 0 );
+	const positionBefore = Position.createFromParentAndOffset( nodeBefore, nodeBefore.getChildCount() );
 
-		const remove = new RemoveOperation( position, 1, this.doc.version );
-		this.doc.applyOperation( remove );
-		delta.addOperation( remove );
+	const move = new MoveOperation( positionAfter, nodeAfter.getChildCount(), positionBefore, this.doc.version );
+	this.doc.applyOperation( move );
+	delta.addOperation( move );
 
-		this.addDelta( delta );
+	const remove = new RemoveOperation( position, 1, this.doc.version );
+	this.doc.applyOperation( remove );
+	delta.addOperation( remove );
 
-		return this;
-	} );
+	this.addDelta( delta );
 
-	return MergeDelta;
+	return this;
 } );

+ 47 - 51
packages/ckeditor5-engine/src/treemodel/delta/movedelta.js

@@ -5,59 +5,55 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/operation/moveoperation',
-	'treemodel/position',
-	'treemodel/range',
-	'ckeditorerror'
-], ( Delta, register, MoveOperation, Position, Range, CKEditorError ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#move} method
-	 * uses the `MoveDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.MoveDelta
-	 */
-	class MoveDelta extends Delta {}
-
-	function addMoveOperation( batch, delta, sourcePosition, howMany, targetPosition ) {
-		const operation = new MoveOperation( sourcePosition, howMany, targetPosition, batch.doc.version );
-		batch.doc.applyOperation( operation );
-		delta.addOperation( operation );
-	}
+import Delta from './delta.js';
+import register from './register.js';
+import MoveOperation from '../operation/moveoperation.js';
+import Position from '../position.js';
+import Range from '../range.js';
+import CKEditorError from '../../ckeditorerror.js';
+
+/**
+ * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#move} method
+ * uses the `MoveDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.MoveDelta
+ */
+export default class MoveDelta extends Delta {}
 
-	/**
-	 * Moves given node or given range of nodes to target position.
-	 *
-	 * @chainable
-	 * @method move
-	 * @memberOf treeModel.Batch
-	 * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range of nodes to move.
-	 * @param {treeModel.Position} targetPosition Position where moved nodes will be inserted.
-	 */
-	register( 'move', function( nodeOrRange, targetPosition ) {
-		const delta = new MoveDelta();
-
-		if ( nodeOrRange instanceof Range ) {
-			if ( !nodeOrRange.isFlat ) {
-				/**
-				 * Range to move is not flat.
-				 *
-				 * @error batch-move-range-not-flat
-				 */
-				throw new CKEditorError( 'batch-move-range-not-flat: Range to move is not flat.' );
-			}
-
-			addMoveOperation( this, delta, nodeOrRange.start, nodeOrRange.end.offset - nodeOrRange.start.offset, targetPosition );
-		} else {
-			addMoveOperation( this, delta, Position.createBefore( nodeOrRange ), 1, targetPosition );
+function addMoveOperation( batch, delta, sourcePosition, howMany, targetPosition ) {
+	const operation = new MoveOperation( sourcePosition, howMany, targetPosition, batch.doc.version );
+	batch.doc.applyOperation( operation );
+	delta.addOperation( operation );
+}
+
+/**
+ * Moves given node or given range of nodes to target position.
+ *
+ * @chainable
+ * @method move
+ * @memberOf treeModel.Batch
+ * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range of nodes to move.
+ * @param {treeModel.Position} targetPosition Position where moved nodes will be inserted.
+ */
+register( 'move', function( nodeOrRange, targetPosition ) {
+	const delta = new MoveDelta();
+
+	if ( nodeOrRange instanceof Range ) {
+		if ( !nodeOrRange.isFlat ) {
+			/**
+			 * Range to move is not flat.
+			 *
+			 * @error batch-move-range-not-flat
+			 */
+			throw new CKEditorError( 'batch-move-range-not-flat: Range to move is not flat.' );
 		}
 
-		this.addDelta( delta );
+		addMoveOperation( this, delta, nodeOrRange.start, nodeOrRange.end.offset - nodeOrRange.start.offset, targetPosition );
+	} else {
+		addMoveOperation( this, delta, Position.createBefore( nodeOrRange ), 1, targetPosition );
+	}
 
-		return this;
-	} );
+	this.addDelta( delta );
 
-	return MoveDelta;
-} );
+	return this;
+} );

+ 4 - 5
packages/ckeditor5-engine/src/treemodel/delta/register.js

@@ -7,8 +7,7 @@
 
 // Register method exposed for deltas, which needs only this method, to make code simpler, more beautiful and, first of
 // all, to solve circular dependencies.
-CKEDITOR.define( [
-	'treemodel/delta/batch-base'
-], ( Batch ) => {
-	return Batch.register;
-} );
+
+import Batch from './batch-base.js';
+
+export default Batch.register;

+ 41 - 45
packages/ckeditor5-engine/src/treemodel/delta/removedelta.js

@@ -5,53 +5,49 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/operation/removeoperation',
-	'treemodel/position',
-	'treemodel/range'
-], ( Delta, register, RemoveOperation, Position, Range ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#remove} method
-	 * uses the `RemoveDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.RemoveDelta
-	 */
-	class RemoveDelta extends Delta {}
-
-	function addRemoveOperation( batch, delta, position, howMany ) {
-		const operation = new RemoveOperation( position, howMany, batch.doc.version );
-		batch.doc.applyOperation( operation );
-		delta.addOperation( operation );
-	}
+import Delta from './delta.js';
+import register from './register.js';
+import RemoveOperation from '../operation/removeoperation.js';
+import Position from '../position.js';
+import Range from '../range.js';
 
-	/**
-	 * Removes given node or range of nodes.
-	 *
-	 * @chainable
-	 * @method remove
-	 * @memberOf treeModel.Batch
-	 * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range of nodes to remove.
-	 */
-	register( 'remove', function( nodeOrRange ) {
-		const delta = new RemoveDelta();
-
-		if ( nodeOrRange instanceof Range ) {
-			// The array is reversed, so the ranges are correct and do not have to be updated.
-			let ranges = nodeOrRange.getMinimalFlatRanges().reverse();
-
-			for ( let flat of ranges ) {
-				addRemoveOperation( this, delta, flat.start, flat.end.offset - flat.start.offset );
-			}
-		} else {
-			addRemoveOperation( this, delta, Position.createBefore( nodeOrRange ), 1 );
-		}
+/**
+ * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#remove} method
+ * uses the `RemoveDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.RemoveDelta
+ */
+export default class RemoveDelta extends Delta {}
+
+function addRemoveOperation( batch, delta, position, howMany ) {
+	const operation = new RemoveOperation( position, howMany, batch.doc.version );
+	batch.doc.applyOperation( operation );
+	delta.addOperation( operation );
+}
 
-		this.addDelta( delta );
+/**
+ * Removes given node or range of nodes.
+ *
+ * @chainable
+ * @method remove
+ * @memberOf treeModel.Batch
+ * @param {treeModel.Node|treeModel.Range} nodeOrRange Node or range of nodes to remove.
+ */
+register( 'remove', function( nodeOrRange ) {
+	const delta = new RemoveDelta();
+
+	if ( nodeOrRange instanceof Range ) {
+		// The array is reversed, so the ranges are correct and do not have to be updated.
+		let ranges = nodeOrRange.getMinimalFlatRanges().reverse();
+
+		for ( let flat of ranges ) {
+			addRemoveOperation( this, delta, flat.start, flat.end.offset - flat.start.offset );
+		}
+	} else {
+		addRemoveOperation( this, delta, Position.createBefore( nodeOrRange ), 1 );
+	}
 
-		return this;
-	} );
+	this.addDelta( delta );
 
-	return RemoveDelta;
+	return this;
 } );

+ 50 - 54
packages/ckeditor5-engine/src/treemodel/delta/splitdelta.js

@@ -5,67 +5,63 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/position',
-	'treemodel/element',
-	'treemodel/operation/insertoperation',
-	'treemodel/operation/moveoperation',
-	'ckeditorerror'
-], ( Delta, register, Position, Element, InsertOperation, MoveOperation, CKEditorError ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, the {@link treeModel.Batch#split} method
-	 * uses `SplitDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.SplitDelta
-	 */
-	class SplitDelta extends Delta {}
+import Delta from './delta.js';
+import register from './register.js';
+import Position from '../position.js';
+import Element from '../element.js';
+import InsertOperation from '../operation/insertoperation.js';
+import MoveOperation from '../operation/moveoperation.js';
+import CKEditorError from '../../ckeditorerror.js';
 
-	/**
-	 * Splits a node at the given position.
-	 *
-	 * This cannot be a position inside the root element. The `batch-split-root` error will be thrown if
-	 * you try to split the root element.
-	 *
-	 * @chainable
-	 * @method split
-	 * @memberOf treeModel.Batch
-	 * @param {treeModel.Position} position Position of split.
-	 */
-	register( 'split', function( position ) {
-		const delta = new SplitDelta();
-		const splitElement = position.parent;
+/**
+ * To provide specific OT behavior and better collisions solving, the {@link treeModel.Batch#split} method
+ * uses `SplitDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.SplitDelta
+ */
+export default class SplitDelta extends Delta {}
 
-		if ( !splitElement.parent ) {
-			/**
-			 * Root element can not be split.
-			 *
-			 * @error batch-split-root
-			 */
-			throw new CKEditorError( 'batch-split-root: Root element can not be split.' );
-		}
+/**
+ * Splits a node at the given position.
+ *
+ * This cannot be a position inside the root element. The `batch-split-root` error will be thrown if
+ * you try to split the root element.
+ *
+ * @chainable
+ * @method split
+ * @memberOf treeModel.Batch
+ * @param {treeModel.Position} position Position of split.
+ */
+register( 'split', function( position ) {
+	const delta = new SplitDelta();
+	const splitElement = position.parent;
 
-		const copy = new Element( splitElement.name, splitElement.getAttrs() );
-		const insert = new InsertOperation( Position.createAfter( splitElement ), copy, this.doc.version );
+	if ( !splitElement.parent ) {
+		/**
+		 * Root element can not be split.
+		 *
+		 * @error batch-split-root
+		 */
+		throw new CKEditorError( 'batch-split-root: Root element can not be split.' );
+	}
 
-		this.doc.applyOperation( insert );
-		delta.addOperation( insert );
+	const copy = new Element( splitElement.name, splitElement.getAttrs() );
+	const insert = new InsertOperation( Position.createAfter( splitElement ), copy, this.doc.version );
 
-		const move = new MoveOperation(
-			position,
-			splitElement.getChildCount() - position.offset,
-			Position.createFromParentAndOffset( copy, 0 ),
-			this.doc.version
-		);
+	this.doc.applyOperation( insert );
+	delta.addOperation( insert );
 
-		this.doc.applyOperation( move );
-		delta.addOperation( move );
+	const move = new MoveOperation(
+		position,
+		splitElement.getChildCount() - position.offset,
+		Position.createFromParentAndOffset( copy, 0 ),
+		this.doc.version
+	);
 
-		this.addDelta( delta );
+	this.doc.applyOperation( move );
+	delta.addOperation( move );
 
-		return this;
-	} );
+	this.addDelta( delta );
 
-	return SplitDelta;
+	return this;
 } );

+ 50 - 55
packages/ckeditor5-engine/src/treemodel/delta/unwrapdelta.js

@@ -5,61 +5,56 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/position',
-	'treemodel/element',
-	'treemodel/operation/removeoperation',
-	'treemodel/operation/moveoperation',
-	'ckeditorerror'
-], ( Delta, register, Position, Element, RemoveOperation, MoveOperation, CKEditorError ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
-	 * uses the `UnwrapDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.UnwrapDelta
-	 */
-	class UnwrapDelta extends Delta {}
+import Delta from './delta.js';
+import register from './register.js';
+import Position from '../position.js';
+import RemoveOperation from '../operation/removeoperation.js';
+import MoveOperation from '../operation/moveoperation.js';
+import CKEditorError from '../../ckeditorerror.js';
 
-	/**
-	 * Unwraps specified element, that is moves all it's children before it and then removes it. Throws
-	 * error if you try to unwrap an element that does not have a parent.
-	 *
-	 * @chainable
-	 * @method unwrap
-	 * @memberOf treeModel.Batch
-	 * @param {treeModel.Element} position Element to unwrap.
-	 */
-	register( 'unwrap', function( element ) {
-		if ( element.parent === null ) {
-			/**
-			 * Trying to unwrap an element that has no parent.
-			 *
-			 * @error batch-unwrap-element-no-parent
-			 */
-			throw new CKEditorError(
-				'batch-unwrap-element-no-parent: Trying to unwrap an element that has no parent.' );
-		}
-
-		const delta = new UnwrapDelta();
-
-		let sourcePosition = Position.createFromParentAndOffset( element, 0 );
-
-		const move = new MoveOperation( sourcePosition, element.getChildCount(), Position.createBefore( element ), this.doc.version );
-		this.doc.applyOperation( move );
-		delta.addOperation( move );
-
-		// Computing new position because we moved some nodes before `element`.
-		// If we would cache `Position.createBefore( element )` we remove wrong node.
-		const remove = new RemoveOperation( Position.createBefore( element ), 1, this.doc.version );
-		this.doc.applyOperation( remove );
-		delta.addOperation( remove );
-
-		this.addDelta( delta );
-
-		return this;
-	} );
+/**
+ * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
+ * uses the `UnwrapDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.UnwrapDelta
+ */
+export default class UnwrapDelta extends Delta {}
 
-	return UnwrapDelta;
+/**
+ * Unwraps specified element, that is moves all it's children before it and then removes it. Throws
+ * error if you try to unwrap an element that does not have a parent.
+ *
+ * @chainable
+ * @method unwrap
+ * @memberOf treeModel.Batch
+ * @param {treeModel.Element} position Element to unwrap.
+ */
+register( 'unwrap', function( element ) {
+	if ( element.parent === null ) {
+		/**
+		 * Trying to unwrap an element that has no parent.
+		 *
+		 * @error batch-unwrap-element-no-parent
+		 */
+		throw new CKEditorError(
+			'batch-unwrap-element-no-parent: Trying to unwrap an element that has no parent.' );
+	}
+
+	const delta = new UnwrapDelta();
+
+	let sourcePosition = Position.createFromParentAndOffset( element, 0 );
+
+	const move = new MoveOperation( sourcePosition, element.getChildCount(), Position.createBefore( element ), this.doc.version );
+	this.doc.applyOperation( move );
+	delta.addOperation( move );
+
+	// Computing new position because we moved some nodes before `element`.
+	// If we would cache `Position.createBefore( element )` we remove wrong node.
+	const remove = new RemoveOperation( Position.createBefore( element ), 1, this.doc.version );
+	this.doc.applyOperation( remove );
+	delta.addOperation( remove );
+
+	this.addDelta( delta );
+
+	return this;
 } );

+ 47 - 51
packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js

@@ -5,55 +5,51 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/operation/insertoperation',
-	'treemodel/nodelist'
-], ( Delta, register, InsertOperation, NodeList ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, the {@link treeModel.Batch#insert} method
-	 * uses the `WeakInsertDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.WeakInsertDelta
-	 */
-	class WeakInsertDelta extends Delta {}
-
-	/**
-	 * Inserts a node or nodes at the given position. {@link treeModel.Batch#weakInsert} is commonly used for actions
-	 * like typing or plain-text paste (without formatting). There are two differences between
-	 * {@link treeModel.Batch#insert} and {@link treeModel.Batch#weakInsert}:
-	 * * When using `weakInsert`, inserted nodes will have same attributes as the current attributes of
-	 * {@link treeModel.Document#selection document selection}.
-	 * * The above has to be reflected during {@link treeModel.operation.transform operational transformation}. Normal
-	 * behavior is that inserting inside range changed by {@link treeModel.operation.AttributeOperation} splits the operation
-	 * into two operations, which "omit" the inserted nodes. The correct behavior for `WeakInsertDelta` is that
-	 * {@link treeModel.operation.AttributeOperation} does not "break" and also applies attributes for inserted nodes.
-	 *
-	 * @chainable
-	 * @memberOf treeModel.Batch
-	 * @method weakInsert
-	 * @param {treeModel.Position} position Position of insertion.
-	 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
-	 * List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
-	 */
-	register( 'weakInsert', function( position, nodes ) {
-		const delta = new WeakInsertDelta();
-
-		nodes = new NodeList( nodes );
-
-		for ( let node of nodes ) {
-			node.setAttrsTo( this.doc.selection.getAttrs() );
-		}
-
-		const operation = new InsertOperation( position, nodes, this.doc.version );
-		this.doc.applyOperation( operation );
-		delta.addOperation( operation );
-
-		this.addDelta( delta );
-
-		return this;
-	} );
-
-	return WeakInsertDelta;
+import Delta from './delta.js';
+import register from './register.js';
+import InsertOperation from '../operation/insertoperation.js';
+import NodeList from '../nodelist.js';
+
+/**
+ * To provide specific OT behavior and better collisions solving, the {@link treeModel.Batch#insert} method
+ * uses the `WeakInsertDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.WeakInsertDelta
+ */
+export default class WeakInsertDelta extends Delta {}
+
+/**
+ * Inserts a node or nodes at the given position. {@link treeModel.Batch#weakInsert} is commonly used for actions
+ * like typing or plain-text paste (without formatting). There are two differences between
+ * {@link treeModel.Batch#insert} and {@link treeModel.Batch#weakInsert}:
+ * * When using `weakInsert`, inserted nodes will have same attributes as the current attributes of
+ * {@link treeModel.Document#selection document selection}.
+ * * The above has to be reflected during {@link treeModel.operation.transform operational transformation}. Normal
+ * behavior is that inserting inside range changed by {@link treeModel.operation.AttributeOperation} splits the operation
+ * into two operations, which "omit" the inserted nodes. The correct behavior for `WeakInsertDelta` is that
+ * {@link treeModel.operation.AttributeOperation} does not "break" and also applies attributes for inserted nodes.
+ *
+ * @chainable
+ * @memberOf treeModel.Batch
+ * @method weakInsert
+ * @param {treeModel.Position} position Position of insertion.
+ * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
+ * List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
+ */
+register( 'weakInsert', function( position, nodes ) {
+	const delta = new WeakInsertDelta();
+
+	nodes = new NodeList( nodes );
+
+	for ( let node of nodes ) {
+		node.setAttrsTo( this.doc.selection.getAttrs() );
+	}
+
+	const operation = new InsertOperation( position, nodes, this.doc.version );
+	this.doc.applyOperation( operation );
+	delta.addOperation( operation );
+
+	this.addDelta( delta );
+
+	return this;
 } );

+ 60 - 64
packages/ckeditor5-engine/src/treemodel/delta/wrapdelta.js

@@ -5,78 +5,74 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/delta/delta',
-	'treemodel/delta/register',
-	'treemodel/position',
-	'treemodel/element',
-	'treemodel/operation/insertoperation',
-	'treemodel/operation/moveoperation',
-	'ckeditorerror'
-], ( Delta, register, Position, Element, InsertOperation, MoveOperation, CKEditorError ) => {
-	/**
-	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
-	 * uses the `WrapDelta` class which inherits from the `Delta` class and may overwrite some methods.
-	 *
-	 * @class treeModel.delta.WrapDelta
-	 */
-	class WrapDelta extends Delta {}
+import Delta from './delta.js';
+import register from './register.js';
+import Position from '../position.js';
+import Element from '../element.js';
+import InsertOperation from '../operation/insertoperation.js';
+import MoveOperation from '../operation/moveoperation.js';
+import CKEditorError from '../../ckeditorerror.js';
 
-	/**
-	 * Wraps given range with given element or with a new element of specified name if string has been passed.
-	 * **Note:** given range should be a "flat range" (see {@link treeModel.Range#isFlat}). If not, error will be thrown.
-	 *
-	 * @chainable
-	 * @method wrap
-	 * @memberOf treeModel.Batch
-	 * @param {treeModel.Range} range Range to wrap.
-	 * @param {treeModel.Element|String} elementOrString Element or name of element to wrap the range with.
-	 */
-	register( 'wrap', function( range, elementOrString ) {
-		if ( !range.isFlat ) {
-			/**
-			 * Range to wrap is not flat.
-			 *
-			 * @error batch-wrap-range-not-flat
-			 */
-			throw new CKEditorError( 'batch-wrap-range-not-flat: Range to wrap is not flat.' );
-		}
+/**
+ * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
+ * uses the `WrapDelta` class which inherits from the `Delta` class and may overwrite some methods.
+ *
+ * @class treeModel.delta.WrapDelta
+ */
+export default class WrapDelta extends Delta {}
 
-		let element = elementOrString instanceof Element ? elementOrString : new Element( elementOrString );
+/**
+ * Wraps given range with given element or with a new element of specified name if string has been passed.
+ * **Note:** given range should be a "flat range" (see {@link treeModel.Range#isFlat}). If not, error will be thrown.
+ *
+ * @chainable
+ * @method wrap
+ * @memberOf treeModel.Batch
+ * @param {treeModel.Range} range Range to wrap.
+ * @param {treeModel.Element|String} elementOrString Element or name of element to wrap the range with.
+ */
+register( 'wrap', function( range, elementOrString ) {
+	if ( !range.isFlat ) {
+		/**
+		 * Range to wrap is not flat.
+		 *
+		 * @error batch-wrap-range-not-flat
+		 */
+		throw new CKEditorError( 'batch-wrap-range-not-flat: Range to wrap is not flat.' );
+	}
 
-		if ( element.getChildCount() > 0 ) {
-			/**
-			 * Element to wrap with is not empty.
-			 *
-			 * @error batch-wrap-element-not-empty
-			 */
-			throw new CKEditorError( 'batch-wrap-element-not-empty: Element to wrap with is not empty.' );
-		}
+	let element = elementOrString instanceof Element ? elementOrString : new Element( elementOrString );
 
-		if ( element.parent !== null ) {
-			/**
-			 * Element to wrap with is already attached to a tree model.
-			 *
-			 * @error batch-wrap-element-attached
-			 */
-			throw new CKEditorError( 'batch-wrap-element-attached: Element to wrap with is already attached to tree model.' );
-		}
+	if ( element.getChildCount() > 0 ) {
+		/**
+		 * Element to wrap with is not empty.
+		 *
+		 * @error batch-wrap-element-not-empty
+		 */
+		throw new CKEditorError( 'batch-wrap-element-not-empty: Element to wrap with is not empty.' );
+	}
 
-		const delta = new WrapDelta();
+	if ( element.parent !== null ) {
+		/**
+		 * Element to wrap with is already attached to a tree model.
+		 *
+		 * @error batch-wrap-element-attached
+		 */
+		throw new CKEditorError( 'batch-wrap-element-attached: Element to wrap with is already attached to tree model.' );
+	}
 
-		let insert = new InsertOperation( range.end, element, this.doc.version );
-		this.doc.applyOperation( insert );
-		delta.addOperation( insert );
+	const delta = new WrapDelta();
 
-		let targetPosition = Position.createFromParentAndOffset( element, 0 );
-		let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.doc.version );
-		this.doc.applyOperation( move );
-		delta.addOperation( move );
+	let insert = new InsertOperation( range.end, element, this.doc.version );
+	this.doc.applyOperation( insert );
+	delta.addOperation( insert );
 
-		this.addDelta( delta );
+	let targetPosition = Position.createFromParentAndOffset( element, 0 );
+	let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.doc.version );
+	this.doc.applyOperation( move );
+	delta.addOperation( move );
 
-		return this;
-	} );
+	this.addDelta( delta );
 
-	return WrapDelta;
+	return this;
 } );

+ 188 - 193
packages/ckeditor5-engine/src/treemodel/document.js

@@ -5,233 +5,228 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/element',
-	'treemodel/rootelement',
-	'treemodel/batch',
-	'treemodel/selection',
-	'emittermixin',
-	'utils',
-	'ckeditorerror'
-], ( Element, RootElement, Batch, Selection, EmitterMixin, utils, CKEditorError ) => {
-	const graveyardSymbol = Symbol( 'graveyard' );
+import RootElement from './rootelement.js';
+import Batch from './batch.js';
+import Selection from './selection.js';
+import EmitterMixin from '../emittermixin.js';
+import CKEditorError from '../ckeditorerror.js';
+import objectUtils from '../lib/lodash/object.js';
 
+const graveyardSymbol = Symbol( 'graveyard' );
+
+/**
+ * Document tree model describes all editable data in the editor. It may contain multiple {@link #roots root elements},
+ * for example if the editor have multiple editable areas, each area will be represented by the separate root.
+ *
+ * All changes in the document are done by {@link treeModel.operation.Operation operations}. To create operations in
+ * the simple way use use the {@link treeModel.Batch} API, for example:
+ *
+ *		doc.batch().insert( position, nodes ).split( otherPosition );
+ *
+ * @see #batch
+ *
+ * @class treeModel.Document
+ */
+export default class Document {
 	/**
-	 * Document tree model describes all editable data in the editor. It may contain multiple {@link #roots root elements},
-	 * for example if the editor have multiple editable areas, each area will be represented by the separate root.
-	 *
-	 * All changes in the document are done by {@link treeModel.operation.Operation operations}. To create operations in
-	 * the simple way use use the {@link treeModel.Batch} API, for example:
+	 * Creates an empty document instance with no {@link #roots}.
 	 *
-	 *		doc.batch().insert( position, nodes ).split( otherPosition );
-	 *
-	 * @see #batch
-	 *
-	 * @class treeModel.Document
+	 * @constructor
 	 */
-	class Document {
-		/**
-		 * Creates an empty document instance with no {@link #roots}.
-		 *
-		 * @constructor
-		 */
-		constructor() {
-			/**
-			 * List of roots that are owned and managed by this document.
-			 *
-			 * @readonly
-			 * @property {Map} roots
-			 */
-			this.roots = new Map();
-
-			/**
-			 * Document version. It starts from `0` and every operation increases the version number. It is used to ensure that
-			 * operations are applied on the proper document version. If the {@link treeModel.operation.Operation#baseVersion} will
-			 * not match document version the {@link document-applyOperation-wrong-version} error is thrown.
-			 *
-			 * @readonly
-			 * @property {Number} version
-			 */
-			this.version = 0;
-
-			// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
-			this.createRoot( graveyardSymbol );
-
-			/**
-			 * Array of pending changes. See: {@link #enqueueChanges}.
-			 *
-			 * @private
-			 * @property {Array.<Function>}
-			 */
-			this._pendingChanges = [];
-
-			/**
-			 * Selection done on this document.
-			 *
-			 * @readonly
-			 * @property {treeModel.Selection}
-			 */
-			this.selection = new Selection();
-		}
-
+	constructor() {
 		/**
-		 * Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
+		 * List of roots that are owned and managed by this document.
 		 *
 		 * @readonly
-		 * @property {treeModel.RootElement} graveyard
+		 * @property {Map} roots
 		 */
-		get graveyard() {
-			return this.getRoot( graveyardSymbol );
-		}
+		this.roots = new Map();
 
 		/**
-		 * This is the entry point for all document changes. All changes on the document are done using
-		 * {@link treeModel.operation.Operation operations}. To create operations in the simple way use the
-		 * {@link treeModel.Batch} API available via {@link #batch} method.
-		 *
-		 * This method calls {@link #change} event.
+		 * Document version. It starts from `0` and every operation increases the version number. It is used to ensure that
+		 * operations are applied on the proper document version. If the {@link treeModel.operation.Operation#baseVersion} will
+		 * not match document version the {@link document-applyOperation-wrong-version} error is thrown.
 		 *
-		 * @param {treeModel.operation.Operation} operation Operation to be applied.
+		 * @readonly
+		 * @property {Number} version
 		 */
-		applyOperation( operation ) {
-			if ( operation.baseVersion !== this.version ) {
-				/**
-				 * Only operations with matching versions can be applied.
-				 *
-				 * @error document-applyOperation-wrong-version
-				 * @param {treeModel.operation.Operation} operation
-				 */
-				throw new CKEditorError(
-					'document-applyOperation-wrong-version: Only operations with matching versions can be applied.',
-					{ operation: operation } );
-			}
+		this.version = 0;
 
-			let changes = operation._execute();
-
-			this.version++;
-
-			const batch = operation.delta && operation.delta.batch;
-			this.fire( 'change', operation.type, changes, batch );
-		}
+		// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
+		this.createRoot( graveyardSymbol );
 
 		/**
-		 * Creates a {@link treeModel.Batch} instance which allows to change the document.
+		 * Array of pending changes. See: {@link #enqueueChanges}.
 		 *
-		 * @returns {treeModel.Batch} Batch instance.
+		 * @private
+		 * @property {Array.<Function>}
 		 */
-		batch() {
-			return new Batch( this );
-		}
+		this._pendingChanges = [];
 
 		/**
-		 * Creates a new top-level root.
+		 * Selection done on this document.
 		 *
-		 * @param {String|Symbol} name Unique root name.
-		 * @returns {treeModel.RootElement} Created root.
+		 * @readonly
+		 * @property {treeModel.Selection}
 		 */
-		createRoot( name ) {
-			if ( this.roots.has( name ) ) {
-				/**
-				 * Root with specified name already exists.
-				 *
-				 * @error document-createRoot-name-exists
-				 * @param {treeModel.Document} doc
-				 * @param {String} name
-				 */
-				throw new CKEditorError(
-					'document-createRoot-name-exists: Root with specified name already exists.',
-					{ name: name }
-				);
-			}
+		this.selection = new Selection();
+	}
 
-			const root = new RootElement( this );
-			this.roots.set( name, root );
+	/**
+	 * Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
+	 *
+	 * @readonly
+	 * @property {treeModel.RootElement} graveyard
+	 */
+	get graveyard() {
+		return this.getRoot( graveyardSymbol );
+	}
 
-			return root;
+	/**
+	 * This is the entry point for all document changes. All changes on the document are done using
+	 * {@link treeModel.operation.Operation operations}. To create operations in the simple way use the
+	 * {@link treeModel.Batch} API available via {@link #batch} method.
+	 *
+	 * This method calls {@link #change} event.
+	 *
+	 * @param {treeModel.operation.Operation} operation Operation to be applied.
+	 */
+	applyOperation( operation ) {
+		if ( operation.baseVersion !== this.version ) {
+			/**
+			 * Only operations with matching versions can be applied.
+			 *
+			 * @error document-applyOperation-wrong-version
+			 * @param {treeModel.operation.Operation} operation
+			 */
+			throw new CKEditorError(
+				'document-applyOperation-wrong-version: Only operations with matching versions can be applied.',
+				{ operation: operation } );
 		}
 
-		/**
-		 * Enqueue a callback with document changes. Any changes to be done on document (mostly using {@link #batch} should
-		 * be placed in the queued callback. If no other plugin is changing document at the moment, the callback will be
-		 * called immediately. Otherwise it will wait for all previously queued changes to finish happening. This way
-		 * queued callback will not interrupt other callbacks.
-		 *
-		 * When all queued changes are done {@link #changesDone} event is fired.
-		 *
-		 * @param {Function} callback Callback to enqueue.
-		 */
-		enqueueChanges( callback ) {
-			this._pendingChanges.push( callback );
+		let changes = operation._execute();
 
-			if ( this._pendingChanges.length == 1 ) {
-				while ( this._pendingChanges.length ) {
-					this._pendingChanges[ 0 ]();
-					this._pendingChanges.shift();
-				}
+		this.version++;
 
-				this.fire( 'changesDone' );
-			}
+		const batch = operation.delta && operation.delta.batch;
+		this.fire( 'change', operation.type, changes, batch );
+	}
+
+	/**
+	 * Creates a {@link treeModel.Batch} instance which allows to change the document.
+	 *
+	 * @returns {treeModel.Batch} Batch instance.
+	 */
+	batch() {
+		return new Batch( this );
+	}
+
+	/**
+	 * Creates a new top-level root.
+	 *
+	 * @param {String|Symbol} name Unique root name.
+	 * @returns {treeModel.RootElement} Created root.
+	 */
+	createRoot( name ) {
+		if ( this.roots.has( name ) ) {
+			/**
+			 * Root with specified name already exists.
+			 *
+			 * @error document-createRoot-name-exists
+			 * @param {treeModel.Document} doc
+			 * @param {String} name
+			 */
+			throw new CKEditorError(
+				'document-createRoot-name-exists: Root with specified name already exists.',
+				{ name: name }
+			);
 		}
 
-		/**
-		 * Returns top-level root by it's name.
-		 *
-		 * @param {String|Symbol} name Name of the root to get.
-		 * @returns {treeModel.RootElement} Root registered under given name.
-		 */
-		getRoot( name ) {
-			if ( !this.roots.has( name ) ) {
-				/**
-				 * Root with specified name does not exist.
-				 *
-				 * @error document-createRoot-root-not-exist
-				 * @param {String} name
-				 */
-				throw new CKEditorError(
-					'document-createRoot-root-not-exist: Root with specified name does not exist.',
-					{ name: name }
-				);
+		const root = new RootElement( this );
+		this.roots.set( name, root );
+
+		return root;
+	}
+
+	/**
+	 * Enqueue a callback with document changes. Any changes to be done on document (mostly using {@link #batch} should
+	 * be placed in the queued callback. If no other plugin is changing document at the moment, the callback will be
+	 * called immediately. Otherwise it will wait for all previously queued changes to finish happening. This way
+	 * queued callback will not interrupt other callbacks.
+	 *
+	 * When all queued changes are done {@link #changesDone} event is fired.
+	 *
+	 * @param {Function} callback Callback to enqueue.
+	 */
+	enqueueChanges( callback ) {
+		this._pendingChanges.push( callback );
+
+		if ( this._pendingChanges.length == 1 ) {
+			while ( this._pendingChanges.length ) {
+				this._pendingChanges[ 0 ]();
+				this._pendingChanges.shift();
 			}
 
-			return this.roots.get( name );
+			this.fire( 'changesDone' );
 		}
+	}
 
-		/**
-		 * Fired when document changes by applying an operation.
-		 *
-		 * There are 5 types of change:
-		 *
-		 * * 'insert' when nodes are inserted,
-		 * * 'remove' when nodes are removed,
-		 * * 'reinsert' when remove is undone,
-		 * * 'move' when nodes are moved,
-		 * * 'attr' when attributes change.
-		 *
-		 * Change event is fired after the change is done. This means that any ranges or positions passed in
-		 * `changeInfo` are referencing nodes and paths in updated tree model.
-		 *
-		 * @event change
-		 * @param {String} type Change type, possible option: 'insert', 'remove', 'reinsert', 'move', 'attr'.
-		 * @param {Object} changeInfo Additional information about the change.
-		 * @param {treeModel.Range} changeInfo.range Range containing changed nodes. Note that for 'remove' the range will be in the
-		 * {@link #graveyard graveyard root}.
-		 * @param {treeModel.Position} [changeInfo.sourcePosition] Change source position. Exists for 'remove', 'reinsert' and 'move'.
-		 * Note that for 'reinsert' the source position will be in the {@link #graveyard graveyard root}.
-		 * @param {treeModel.Attribute} [changeInfo.oldAttr] Only for 'attr' type. If the type is 'attr' and `oldAttr`
-		 * is `undefined` it means that new attribute was inserted. Otherwise it contains changed or removed attribute.
-		 * @param {treeModel.Attribute} [changeInfo.newAttr] Only for 'attr' type. If the type is 'attr' and `newAttr`
-		 * is `undefined` it means that attribute was removed. Otherwise it contains changed or inserted attribute.
-		 * @param {treeModel.Batch} {@link treeModel.Batch} of changes which this change is a part of.
-		 */
+	/**
+	 * Returns top-level root by it's name.
+	 *
+	 * @param {String|Symbol} name Name of the root to get.
+	 * @returns {treeModel.RootElement} Root registered under given name.
+	 */
+	getRoot( name ) {
+		if ( !this.roots.has( name ) ) {
+			/**
+			 * Root with specified name does not exist.
+			 *
+			 * @error document-createRoot-root-not-exist
+			 * @param {String} name
+			 */
+			throw new CKEditorError(
+				'document-createRoot-root-not-exist: Root with specified name does not exist.',
+				{ name: name }
+			);
+		}
 
-		/**
-		 * Fired when all queued document changes are done. See {@link #enqueueChanges}.
-		 *
-		 * @event changesDone
-		 */
+		return this.roots.get( name );
 	}
 
-	utils.extend( Document.prototype, EmitterMixin );
+	/**
+	 * Fired when document changes by applying an operation.
+	 *
+	 * There are 5 types of change:
+	 *
+	 * * 'insert' when nodes are inserted,
+	 * * 'remove' when nodes are removed,
+	 * * 'reinsert' when remove is undone,
+	 * * 'move' when nodes are moved,
+	 * * 'attr' when attributes change.
+	 *
+	 * Change event is fired after the change is done. This means that any ranges or positions passed in
+	 * `changeInfo` are referencing nodes and paths in updated tree model.
+	 *
+	 * @event change
+	 * @param {String} type Change type, possible option: 'insert', 'remove', 'reinsert', 'move', 'attr'.
+	 * @param {Object} changeInfo Additional information about the change.
+	 * @param {treeModel.Range} changeInfo.range Range containing changed nodes. Note that for 'remove' the range will be in the
+	 * {@link #graveyard graveyard root}.
+	 * @param {treeModel.Position} [changeInfo.sourcePosition] Change source position. Exists for 'remove', 'reinsert' and 'move'.
+	 * Note that for 'reinsert' the source position will be in the {@link #graveyard graveyard root}.
+	 * @param {treeModel.Attribute} [changeInfo.oldAttr] Only for 'attr' type. If the type is 'attr' and `oldAttr`
+	 * is `undefined` it means that new attribute was inserted. Otherwise it contains changed or removed attribute.
+	 * @param {treeModel.Attribute} [changeInfo.newAttr] Only for 'attr' type. If the type is 'attr' and `newAttr`
+	 * is `undefined` it means that attribute was removed. Otherwise it contains changed or inserted attribute.
+	 * @param {treeModel.Batch} {@link treeModel.Batch} of changes which this change is a part of.
+	 */
+
+	/**
+	 * Fired when all queued document changes are done. See {@link #enqueueChanges}.
+	 *
+	 * @event changesDone
+	 */
+}
 
-	return Document;
-} );
+objectUtils.extend( Document.prototype, EmitterMixin );

+ 89 - 93
packages/ckeditor5-engine/src/treemodel/element.js

@@ -5,117 +5,113 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/node',
-	'treemodel/nodelist'
-], ( Node, NodeList ) => {
+import Node from './node.js';
+import NodeList from './nodelist.js';
+
+/**
+ * Tree data model element.
+ *
+ * @class treeModel.Element
+ */
+export default class Element extends Node {
 	/**
-	 * Tree data model element.
+	 * Creates a tree data model element.
+	 *
+	 * This constructor should be used only internally by the document.
 	 *
-	 * @class treeModel.Element
+	 * @param {String} name Node name.
+	 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
+	 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} children List of nodes to be inserted
+	 * into created element. List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
+	 * @constructor
 	 */
-	class Element extends Node {
-		/**
-		 * Creates a tree data model element.
-		 *
-		 * This constructor should be used only internally by the document.
-		 *
-		 * @param {String} name Node name.
-		 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
-		 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} children List of nodes to be inserted
-		 * into created element. List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
-		 * @constructor
-		 */
-		constructor( name, attrs, children ) {
-			super( attrs );
-
-			/**
-			 * Element name.
-			 *
-			 * @readonly
-			 * @property {String} name
-			 */
-			this.name = name;
-
-			/**
-			 * List of children nodes.
-			 *
-			 * @protected
-			 * @property {treeModel.NodeList} _children
-			 */
-			this._children = new NodeList();
-
-			if ( children ) {
-				this.insertChildren( 0, children );
-			}
-		}
+	constructor( name, attrs, children ) {
+		super( attrs );
 
 		/**
-		 * Gets child at the given index.
+		 * Element name.
 		 *
-		 * @param {Number} index Index of child.
-		 * @returns {treeModel.Node} Child node.
+		 * @readonly
+		 * @property {String} name
 		 */
-		getChild( index ) {
-			return this._children.get( index );
-		}
+		this.name = name;
 
 		/**
-		 * Gets the number of element's children.
+		 * List of children nodes.
 		 *
-		 * @returns {Number} The number of element's children.
+		 * @protected
+		 * @property {treeModel.NodeList} _children
 		 */
-		getChildCount() {
-			return this._children.length;
-		}
+		this._children = new NodeList();
 
-		/**
-		 * Gets index of the given child node.
-		 *
-		 * @param {treeModel.Node} node Child node.
-		 * @returns {Number} Index of the child node.
-		 */
-		getChildIndex( node ) {
-			return this._children.indexOf( node );
+		if ( children ) {
+			this.insertChildren( 0, children );
 		}
+	}
 
-		/**
-		 * Inserts a list of child nodes on the given index and sets the parent of these nodes to this element.
-		 *
-		 * Note that the list of children can be modified only in elements not yet attached to the document.
-		 * All attached nodes should be modified using the {@link treeModel.operation.InsertOperation}.
-		 *
-		 * @param {Number} index Position where nodes should be inserted.
-		 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
-		 * The list of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
-		 */
-		insertChildren( index, nodes ) {
-			this._children.insert( index, new NodeList( nodes ) );
+	/**
+	 * Gets child at the given index.
+	 *
+	 * @param {Number} index Index of child.
+	 * @returns {treeModel.Node} Child node.
+	 */
+	getChild( index ) {
+		return this._children.get( index );
+	}
 
-			for ( let node of this._children ) {
-				node.parent = this;
-			}
-		}
+	/**
+	 * Gets the number of element's children.
+	 *
+	 * @returns {Number} The number of element's children.
+	 */
+	getChildCount() {
+		return this._children.length;
+	}
 
-		/**
-		 * Removes number of child nodes starting at the given index and set the parent of these nodes to `null`.
-		 *
-		 * Note that the list of children can be modified only in elements not yet attached to the document.
-		 * All attached nodes should be modified using the {@link treeModel.operation.RemoveOperation}.
-		 *
-		 * @param {Number} index Position of the first node to remove.
-		 * @param {Number} number Number of nodes to remove.
-		 * @returns {treeModel.NodeList} The list of removed nodes.
-		 */
+	/**
+	 * Gets index of the given child node.
+	 *
+	 * @param {treeModel.Node} node Child node.
+	 * @returns {Number} Index of the child node.
+	 */
+	getChildIndex( node ) {
+		return this._children.indexOf( node );
+	}
 
-		removeChildren( index, number ) {
-			for ( let i = index; i < index + number; i++ ) {
-				this._children.get( i ).parent = null;
-			}
+	/**
+	 * Inserts a list of child nodes on the given index and sets the parent of these nodes to this element.
+	 *
+	 * Note that the list of children can be modified only in elements not yet attached to the document.
+	 * All attached nodes should be modified using the {@link treeModel.operation.InsertOperation}.
+	 *
+	 * @param {Number} index Position where nodes should be inserted.
+	 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
+	 * The list of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
+	 */
+	insertChildren( index, nodes ) {
+		this._children.insert( index, new NodeList( nodes ) );
 
-			return this._children.remove( index, number );
+		for ( let node of this._children ) {
+			node.parent = this;
 		}
 	}
 
-	return Element;
-} );
+	/**
+	 * Removes number of child nodes starting at the given index and set the parent of these nodes to `null`.
+	 *
+	 * Note that the list of children can be modified only in elements not yet attached to the document.
+	 * All attached nodes should be modified using the {@link treeModel.operation.RemoveOperation}.
+	 *
+	 * @param {Number} index Position of the first node to remove.
+	 * @param {Number} number Number of nodes to remove.
+	 * @returns {treeModel.NodeList} The list of removed nodes.
+	 */
+
+	removeChildren( index, number ) {
+		for ( let i = index; i < index + number; i++ ) {
+			this._children.get( i ).parent = null;
+		}
+
+		return this._children.remove( index, number );
+	}
+}

+ 151 - 155
packages/ckeditor5-engine/src/treemodel/liveposition.js

@@ -5,184 +5,180 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/position',
-	'treemodel/range',
-	'emittermixin',
-	'utils'
-], ( Position, Range, EmitterMixin, utils ) => {
-	const STICKS_TO_NEXT = 0;
-	const STICKS_TO_PREVIOUS = 1;
+import Position from './position.js';
+import Range from './range.js';
+import EmitterMixin from '../emittermixin.js';
+import objectUtils from '../lib/lodash/object.js';
 
+const STICKS_TO_NEXT = 0;
+const STICKS_TO_PREVIOUS = 1;
+
+/**
+ * LivePosition is a position in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
+ * **Note:** Be very careful when dealing with LivePosition. Each LivePosition instance bind events that might
+ * have to be unbound. Use {@link #detach} whenever you don't need LivePosition anymore.
+ *
+ * @class treeModel.LivePosition
+ */
+
+export default class LivePosition extends Position {
 	/**
-	 * LivePosition is a position in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
-	 * **Note:** Be very careful when dealing with LivePosition. Each LivePosition instance bind events that might
-	 * have to be unbound. Use {@link #detach} whenever you don't need LivePosition anymore.
+	 * Creates a live position.
 	 *
-	 * @class treeModel.LivePosition
+	 * @see {@link treeModel.Position}
+	 * @param root
+	 * @param path
+	 * @param {Number} [stickiness] Flag representing how live position is "sticking" with their neighbour nodes.
+	 * Defaults to {@link #STICKS_TO_NEXT}. See {@link #stickiness}.
+	 * @constructor
 	 */
+	constructor( root, path, stickiness ) {
+		super( root, path );
 
-	class LivePosition extends Position {
 		/**
-		 * Creates a live position.
+		 * Flag representing LivePosition stickiness. LivePosition might be sticking to previous node or next node.
+		 * Whenever some nodes are inserted at the same position as LivePosition, `stickiness` is checked to decide if
+		 * LivePosition should be moved. Similar applies when a range of nodes is moved and one of it's boundary
+		 * position is same as LivePosition.
 		 *
-		 * @see {@link treeModel.Position}
-		 * @param root
-		 * @param path
-		 * @param {Number} [stickiness] Flag representing how live position is "sticking" with their neighbour nodes.
-		 * Defaults to {@link #STICKS_TO_NEXT}. See {@link #stickiness}.
-		 * @constructor
-		 */
-		constructor( root, path, stickiness ) {
-			super( root, path );
-
-			/**
-			 * Flag representing LivePosition stickiness. LivePosition might be sticking to previous node or next node.
-			 * Whenever some nodes are inserted at the same position as LivePosition, `stickiness` is checked to decide if
-			 * LivePosition should be moved. Similar applies when a range of nodes is moved and one of it's boundary
-			 * position is same as LivePosition.
-			 *
-			 * Examples:
-			 * Insert:
-			 * Position is at | and we insert at the same position, marked as ^:
-			 * | sticks to previous node: `<p>f|^oo</p>` => `<p>f|baroo</p>`
-			 * | sticks to next node: `<p>f^|oo</p>` => `<p>fbar|oo</p>`
-			 *
-			 * Move:
-			 * Position is at | and range [ ] is moved to position ^:
-			 * | sticks to previous node: `<p>f|[oo]</p><p>b^ar</p>` => `<p>f|</p><p>booar</p>`
-			 * | sticks to next node: `<p>f|[oo]</p><p>b^ar</p>` => `<p>f</p><p>b|ooar</p>`
-			 *
-			 * Accepted values are {@link #STICKS_TO_PREVIOUS} and {@link #STICKS_TO_NEXT}.
-			 *
-			 * @type {Number}
-			 */
-			this.stickiness = stickiness || STICKS_TO_NEXT;
-
-			bindWithDocument.call( this );
-		}
-
-		/**
-		 * Unbinds all events previously bound by LivePosition. Use it whenever you don't need LivePosition instance
-		 * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
-		 * referring to it).
-		 */
-		detach() {
-			this.stopListening();
-		}
-
-		/**
-		 * @static
-		 * @method createAfter
-		 * @see {@link treeModel.Position#createAfter}
-		 * @param {treeModel.Node} node
-		 * @returns {treeModel.LivePosition}
-		 */
-
-		/**
-		 * @static
-		 * @method createBefore
-		 * @see {@link treeModel.Position#createBefore}
-		 * @param {treeModel.Node} node
-		 * @returns {treeModel.LivePosition}
-		 */
-
-		/**
-		 * @static
-		 * @method createFromParentAndOffset
-		 * @see {@link treeModel.Position#createFromParentAndOffset}
-		 * @param {treeModel.Element} parent
-		 * @param {Number} offset
-		 * @returns {treeModel.LivePosition}
+		 * Examples:
+		 * Insert:
+		 * Position is at | and we insert at the same position, marked as ^:
+		 * | sticks to previous node: `<p>f|^oo</p>` => `<p>f|baroo</p>`
+		 * | sticks to next node: `<p>f^|oo</p>` => `<p>fbar|oo</p>`
+		 *
+		 * Move:
+		 * Position is at | and range [ ] is moved to position ^:
+		 * | sticks to previous node: `<p>f|[oo]</p><p>b^ar</p>` => `<p>f|</p><p>booar</p>`
+		 * | sticks to next node: `<p>f|[oo]</p><p>b^ar</p>` => `<p>f</p><p>b|ooar</p>`
+		 *
+		 * Accepted values are {@link #STICKS_TO_PREVIOUS} and {@link #STICKS_TO_NEXT}.
+		 *
+		 * @type {Number}
 		 */
+		this.stickiness = stickiness || STICKS_TO_NEXT;
 
-		/**
-		 * @static
-		 * @method createFromPosition
-		 * @see {@link treeModel.Position#createFromPosition}
-		 * @param {treeModel.Position} position
-		 * @returns {treeModel.LivePosition}
-		 */
+		bindWithDocument.call( this );
 	}
 
 	/**
-	 * Binds this LivePosition to the {@link treeModel.Document} that owns this position {@link treeModel.RootElement root}.
-	 *
-	 * @private
-	 * @method bindWithDocument
+	 * Unbinds all events previously bound by LivePosition. Use it whenever you don't need LivePosition instance
+	 * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
+	 * referring to it).
 	 */
-	function bindWithDocument() {
-		/*jshint validthis: true */
-
-		this.listenTo(
-			this.root.document,
-			'change',
-			( event, type, changes ) => {
-				transform.call( this, type, changes.range, changes.sourcePosition );
-			},
-			this
-		);
+	detach() {
+		this.stopListening();
 	}
 
 	/**
-	 * Updates this position accordingly to the updates applied to the Tree Model. Bases on change events.
-	 *
-	 * @private
-	 * @method transform
-	 * @param {String} type Type of changes applied to the Tree Model.
-	 * @param {treeModel.Range} range Range containing the result of applied change.
-	 * @param {treeModel.Position} [position] Additional position parameter provided by some change events.
+	 * @static
+	 * @method createAfter
+	 * @see {@link treeModel.Position#createAfter}
+	 * @param {treeModel.Node} node
+	 * @returns {treeModel.LivePosition}
 	 */
-	function transform( type, range, position ) {
-		/*jshint validthis: true */
-
-		let howMany = range.end.offset - range.start.offset;
-		let transformed;
 
-		switch ( type ) {
-			case 'insert':
-				let insertBefore = this.stickiness == STICKS_TO_NEXT;
-				transformed = this.getTransformedByInsertion( range.start, howMany, insertBefore );
-				break;
-
-			case 'move':
-			case 'remove':
-			case 'reinsert':
-				let originalRange = Range.createFromPositionAndShift( position, howMany );
-
-				let gotMoved = originalRange.containsPosition( this ) ||
-					( originalRange.start.isEqual( this ) && this.stickiness == STICKS_TO_NEXT ) ||
-					( originalRange.end.isEqual( this ) && this.stickiness == STICKS_TO_PREVIOUS );
-
-				// We can't use .getTransformedByMove() because we have a different if-condition.
-				if ( gotMoved ) {
-					transformed = this._getCombined( position, range.start );
-				} else {
-					let insertBefore = this.stickiness == STICKS_TO_NEXT;
-					transformed = this.getTransformedByMove( position, range.start, howMany, insertBefore );
-				}
-				break;
-		}
-
-		this.path = transformed.path;
-		this.root = transformed.root;
-	}
+	/**
+	 * @static
+	 * @method createBefore
+	 * @see {@link treeModel.Position#createBefore}
+	 * @param {treeModel.Node} node
+	 * @returns {treeModel.LivePosition}
+	 */
 
 	/**
-	 * Flag representing that the position is sticking to the node before it or to the beginning of it's parent node.
-	 *
-	 * @type {Number}
+	 * @static
+	 * @method createFromParentAndOffset
+	 * @see {@link treeModel.Position#createFromParentAndOffset}
+	 * @param {treeModel.Element} parent
+	 * @param {Number} offset
+	 * @returns {treeModel.LivePosition}
 	 */
-	LivePosition.STICKS_TO_PREVIOUS = STICKS_TO_PREVIOUS;
 
 	/**
-	 * Flag representing that the position is sticking to the node after it or to the end of it's parent node.
-	 *
-	 * @type {number}
+	 * @static
+	 * @method createFromPosition
+	 * @see {@link treeModel.Position#createFromPosition}
+	 * @param {treeModel.Position} position
+	 * @returns {treeModel.LivePosition}
 	 */
-	LivePosition.STICKS_TO_NEXT = STICKS_TO_NEXT;
+}
+
+/**
+ * Binds this LivePosition to the {@link treeModel.Document} that owns this position {@link treeModel.RootElement root}.
+ *
+ * @private
+ * @method bindWithDocument
+ */
+function bindWithDocument() {
+	/*jshint validthis: true */
+
+	this.listenTo(
+		this.root.document,
+		'change',
+		( event, type, changes ) => {
+			transform.call( this, type, changes.range, changes.sourcePosition );
+		},
+		this
+	);
+}
 
-	utils.extend( LivePosition.prototype, EmitterMixin );
+/**
+ * Updates this position accordingly to the updates applied to the Tree Model. Bases on change events.
+ *
+ * @private
+ * @method transform
+ * @param {String} type Type of changes applied to the Tree Model.
+ * @param {treeModel.Range} range Range containing the result of applied change.
+ * @param {treeModel.Position} [position] Additional position parameter provided by some change events.
+ */
+function transform( type, range, position ) {
+	/*jshint validthis: true */
+
+	let howMany = range.end.offset - range.start.offset;
+	let transformed;
+
+	switch ( type ) {
+		case 'insert':
+			let insertBefore = this.stickiness == STICKS_TO_NEXT;
+			transformed = this.getTransformedByInsertion( range.start, howMany, insertBefore );
+			break;
+
+		case 'move':
+		case 'remove':
+		case 'reinsert':
+			let originalRange = Range.createFromPositionAndShift( position, howMany );
+
+			let gotMoved = originalRange.containsPosition( this ) ||
+				( originalRange.start.isEqual( this ) && this.stickiness == STICKS_TO_NEXT ) ||
+				( originalRange.end.isEqual( this ) && this.stickiness == STICKS_TO_PREVIOUS );
+
+			// We can't use .getTransformedByMove() because we have a different if-condition.
+			if ( gotMoved ) {
+				transformed = this._getCombined( position, range.start );
+			} else {
+				let insertBefore = this.stickiness == STICKS_TO_NEXT;
+				transformed = this.getTransformedByMove( position, range.start, howMany, insertBefore );
+			}
+			break;
+	}
+
+	this.path = transformed.path;
+	this.root = transformed.root;
+}
+
+/**
+ * Flag representing that the position is sticking to the node before it or to the beginning of it's parent node.
+ *
+ * @type {Number}
+ */
+LivePosition.STICKS_TO_PREVIOUS = STICKS_TO_PREVIOUS;
+
+/**
+ * Flag representing that the position is sticking to the node after it or to the end of it's parent node.
+ *
+ * @type {number}
+ */
+LivePosition.STICKS_TO_NEXT = STICKS_TO_NEXT;
 
-	return LivePosition;
-} );
+objectUtils.extend( LivePosition.prototype, EmitterMixin );

+ 114 - 118
packages/ckeditor5-engine/src/treemodel/liverange.js

@@ -5,137 +5,133 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/range',
-	'treemodel/liveposition',
-	'emittermixin',
-	'utils'
-], ( Range, LivePosition, EmitterMixin, utils ) => {
+import Range from './range.js';
+import LivePosition from './liveposition.js';
+import EmitterMixin from '../emittermixin.js';
+import objectUtils from '../lib/lodash/object.js';
+
+/**
+ * LiveRange is a Range in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
+ * **Note:** Constructor creates it's own {@link treeModel.LivePosition} instances basing on passed values.
+ * **Note:** Be very careful when dealing with LiveRange. Each LiveRange instance bind events that might
+ * have to be unbound. Use {@link #detach} whenever you don't need LiveRange anymore.
+ *
+ * @class treeModel.LiveRange
+ */
+export default class LiveRange extends Range {
 	/**
-	 * LiveRange is a Range in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
-	 * **Note:** Constructor creates it's own {@link treeModel.LivePosition} instances basing on passed values.
-	 * **Note:** Be very careful when dealing with LiveRange. Each LiveRange instance bind events that might
-	 * have to be unbound. Use {@link #detach} whenever you don't need LiveRange anymore.
+	 * Creates a live range.
 	 *
-	 * @class treeModel.LiveRange
+	 * @see {treeModel.Range}
+	 * @constructor
 	 */
-	class LiveRange extends Range {
-		/**
-		 * Creates a live range.
-		 *
-		 * @see {treeModel.Range}
-		 * @constructor
-		 */
-		constructor( start, end ) {
-			super( start, end );
-
-			this.start = new LivePosition( this.start.root, this.start.path.slice(), LivePosition.STICKS_TO_NEXT );
-			this.end = new LivePosition( this.end.root, this.end.path.slice(), LivePosition.STICKS_TO_PREVIOUS );
-
-			bindWithDocument.call( this );
-		}
+	constructor( start, end ) {
+		super( start, end );
 
-		/**
-		 * Unbinds all events previously bound by LiveRange. Use it whenever you don't need LiveRange instance
-		 * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
-		 * referring to it).
-		 */
-		detach() {
-			this.start.detach();
-			this.end.detach();
-			this.stopListening();
-		}
+		this.start = new LivePosition( this.start.root, this.start.path.slice(), LivePosition.STICKS_TO_NEXT );
+		this.end = new LivePosition( this.end.root, this.end.path.slice(), LivePosition.STICKS_TO_PREVIOUS );
 
-		/**
-		 * @see {@link treeModel.Range#createFromElement}
-		 * @static
-		 * @method createFromElement
-		 * @param {treeModel.Element} element
-		 * @returns {treeModel.LiveRange}
-		 */
-
-		/**
-		 * @see {@link treeModel.Range#createFromPositionAndShift}
-		 * @static
-		 * @method createFromPositionAndShift
-		 * @param {treeModel.Position} position
-		 * @param {Number} shift
-		 * @returns {treeModel.LiveRange}
-		 */
-
-		/**
-		 * @see {@link treeModel.Range#createFromParentsAndOffsets}
-		 * @static
-		 * @method createFromParentsAndOffsets
-		 * @param {treeModel.Element} startElement
-		 * @param {Number} startOffset
-		 * @param {treeModel.Element} endElement
-		 * @param {Number} endOffset
-		 * @returns {treeModel.LiveRange}
-		 */
-
-		/**
-		 * @see {@link treeModel.Range#createFromRange}
-		 * @static
-		 * @method createFromRange
-		 * @param {treeModel.Range} range
-		 * @returns {treeModel.LiveRange}
-		 */
+		bindWithDocument.call( this );
 	}
 
 	/**
-	 * Binds this LiveRange to the {@link treeModel.Document} that owns this range.
-	 *
-	 * @private
-	 * @method bindWithDocument
+	 * Unbinds all events previously bound by LiveRange. Use it whenever you don't need LiveRange instance
+	 * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
+	 * referring to it).
 	 */
-	function bindWithDocument() {
-		/*jshint validthis: true */
-
-		this.listenTo(
-			this.root.document,
-			'change',
-			( event, type, changes ) => {
-				fixBoundaries.call( this, type, changes.range, changes.sourcePosition );
-			},
-			this
-		);
+	detach() {
+		this.start.detach();
+		this.end.detach();
+		this.stopListening();
 	}
 
 	/**
-	 * LiveRange boundaries are instances of {@link treeModel.LivePosition}, so it is updated thanks to them. This method
-	 * additionally fixes the results of updating live positions taking into account that those live positions
-	 * are boundaries of a range. An example case for fixing live positions is end boundary is moved before start boundary.
-	 *
-	 * @private
-	 * @method fixBoundaries
-	 * @param {String} type Type of changes applied to the Tree Model.
-	 * @param {treeModel.Range} range Range containing the result of applied change.
-	 * @param {treeModel.Position} [position] Additional position parameter provided by some change events.
+	 * @see {@link treeModel.Range#createFromElement}
+	 * @static
+	 * @method createFromElement
+	 * @param {treeModel.Element} element
+	 * @returns {treeModel.LiveRange}
+	 */
+
+	/**
+	 * @see {@link treeModel.Range#createFromPositionAndShift}
+	 * @static
+	 * @method createFromPositionAndShift
+	 * @param {treeModel.Position} position
+	 * @param {Number} shift
+	 * @returns {treeModel.LiveRange}
+	 */
+
+	/**
+	 * @see {@link treeModel.Range#createFromParentsAndOffsets}
+	 * @static
+	 * @method createFromParentsAndOffsets
+	 * @param {treeModel.Element} startElement
+	 * @param {Number} startOffset
+	 * @param {treeModel.Element} endElement
+	 * @param {Number} endOffset
+	 * @returns {treeModel.LiveRange}
 	 */
-	function fixBoundaries( type, range, position ) {
-		/*jshint validthis: true */
-
-		if ( type == 'move' || type == 'remove' || type == 'reinsert' ) {
-			let containsStart = range.containsPosition( this.start ) || range.start.isEqual( this.start );
-			let containsEnd = range.containsPosition( this.end ) || range.end.isEqual( this.end );
-			position = position.getTransformedByInsertion( range.start, range.end.offset - range.start.offset, true );
-
-			// If the range contains both start and end, don't do anything - LivePositions that are boundaries of
-			// this LiveRange are in correct places, they got correctly transformed.
-			if ( containsStart && !containsEnd && !range.end.isTouching( position ) ) {
-				this.start.path = position.path.slice();
-				this.start.root = position.root;
-			}
-
-			if ( containsEnd && !containsStart && !range.start.isTouching( position ) ) {
-				this.end.path = position.path.slice();
-				this.end.root = position.root;
-			}
+
+	/**
+	 * @see {@link treeModel.Range#createFromRange}
+	 * @static
+	 * @method createFromRange
+	 * @param {treeModel.Range} range
+	 * @returns {treeModel.LiveRange}
+	 */
+}
+
+/**
+ * Binds this LiveRange to the {@link treeModel.Document} that owns this range.
+ *
+ * @private
+ * @method bindWithDocument
+ */
+function bindWithDocument() {
+	/*jshint validthis: true */
+
+	this.listenTo(
+		this.root.document,
+		'change',
+		( event, type, changes ) => {
+			fixBoundaries.call( this, type, changes.range, changes.sourcePosition );
+		},
+		this
+	);
+}
+
+/**
+ * LiveRange boundaries are instances of {@link treeModel.LivePosition}, so it is updated thanks to them. This method
+ * additionally fixes the results of updating live positions taking into account that those live positions
+ * are boundaries of a range. An example case for fixing live positions is end boundary is moved before start boundary.
+ *
+ * @private
+ * @method fixBoundaries
+ * @param {String} type Type of changes applied to the Tree Model.
+ * @param {treeModel.Range} range Range containing the result of applied change.
+ * @param {treeModel.Position} [position] Additional position parameter provided by some change events.
+ */
+function fixBoundaries( type, range, position ) {
+	/* jshint validthis: true */
+
+	if ( type == 'move' || type == 'remove' || type == 'reinsert' ) {
+		let containsStart = range.containsPosition( this.start ) || range.start.isEqual( this.start );
+		let containsEnd = range.containsPosition( this.end ) || range.end.isEqual( this.end );
+		position = position.getTransformedByInsertion( range.start, range.end.offset - range.start.offset, true );
+
+		// If the range contains both start and end, don't do anything - LivePositions that are boundaries of
+		// this LiveRange are in correct places, they got correctly transformed.
+		if ( containsStart && !containsEnd && !range.end.isTouching( position ) ) {
+			this.start.path = position.path.slice();
+			this.start.root = position.root;
 		}
-	}
 
-	utils.extend( LiveRange.prototype, EmitterMixin );
+		if ( containsEnd && !containsStart && !range.start.isTouching( position ) ) {
+			this.end.path = position.path.slice();
+			this.end.root = position.root;
+		}
+	}
+}
 
-	return LiveRange;
-} );
+objectUtils.extend( LiveRange.prototype, EmitterMixin );

+ 159 - 163
packages/ckeditor5-engine/src/treemodel/node.js

@@ -5,205 +5,201 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/attributelist',
-	'utils',
-	'ckeditorerror'
-], ( AttributeList, utils, CKEditorError ) => {
+import AttributeList from './attributelist.js';
+import langUtils from '../lib/lodash/lang.js';
+import CKEditorError from '../ckeditorerror.js';
+
+/**
+ * Abstract document tree node class.
+ *
+ * @abstract
+ * @class treeModel.Node
+ */
+export default class Node {
 	/**
-	 * Abstract document tree node class.
+	 * Creates a tree node.
+	 *
+	 * This is an abstract class, so this constructor should not be used directly.
 	 *
-	 * @abstract
-	 * @class treeModel.Node
+	 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
+	 * @constructor
 	 */
-	class Node {
+	constructor( attrs ) {
 		/**
-		 * Creates a tree node.
+		 * Parent element. Null by default. Set by {@link treeModel.Element#insertChildren}.
 		 *
-		 * This is an abstract class, so this constructor should not be used directly.
-		 *
-		 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
-		 * @constructor
+		 * @readonly
+		 * @property {treeModel.Element|null} parent
 		 */
-		constructor( attrs ) {
-			/**
-			 * Parent element. Null by default. Set by {@link treeModel.Element#insertChildren}.
-			 *
-			 * @readonly
-			 * @property {treeModel.Element|null} parent
-			 */
-			this.parent = null;
-
-			/**
-			 * List of attributes set on this node.
-			 * Attributes of nodes attached to the document can be changed only be the {@link treeModel.operation.AttributeOperation}.
-			 *
-			 * @private
-			 * @property {treeModel.AttributeList} _attrs
-			 */
-			this._attrs = new AttributeList( attrs );
-		}
+		this.parent = null;
 
 		/**
-		 * Depth of the node, which equals to total number of its parents.
+		 * List of attributes set on this node.
+		 * Attributes of nodes attached to the document can be changed only be the {@link treeModel.operation.AttributeOperation}.
 		 *
-		 * @readonly
-		 * @property {Number} depth
+		 * @private
+		 * @property {treeModel.AttributeList} _attrs
 		 */
-		get depth() {
-			let depth = 0;
-			let parent = this.parent;
+		this._attrs = new AttributeList( attrs );
+	}
 
-			while ( parent ) {
-				depth++;
+	/**
+	 * Depth of the node, which equals to total number of its parents.
+	 *
+	 * @readonly
+	 * @property {Number} depth
+	 */
+	get depth() {
+		let depth = 0;
+		let parent = this.parent;
 
-				parent = parent.parent;
-			}
+		while ( parent ) {
+			depth++;
 
-			return depth;
+			parent = parent.parent;
 		}
 
-		/**
-		 * Nodes next sibling or `null` if it is the last child.
-		 *
-		 * @readonly
-		 * @property {treeModel.Node|null} nextSibling
-		 */
-		get nextSibling() {
-			const index = this.getIndex();
-
-			return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
-		}
+		return depth;
+	}
 
-		/**
-		 * Nodes previous sibling or null if it is the last child.
-		 *
-		 * @readonly
-		 * @property {treeModel.Node|null} previousSibling
-		 */
-		get previousSibling() {
-			const index = this.getIndex();
+	/**
+	 * Nodes next sibling or `null` if it is the last child.
+	 *
+	 * @readonly
+	 * @property {treeModel.Node|null} nextSibling
+	 */
+	get nextSibling() {
+		const index = this.getIndex();
 
-			return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
-		}
+		return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
+	}
 
-		/**
-		 * The top parent for the node. If node has no parent it is the root itself.
-		 *
-		 * @readonly
-		 * @property {Number} depth
-		 */
-		get root() {
-			let root = this;
+	/**
+	 * Nodes previous sibling or null if it is the last child.
+	 *
+	 * @readonly
+	 * @property {treeModel.Node|null} previousSibling
+	 */
+	get previousSibling() {
+		const index = this.getIndex();
 
-			while ( root.parent ) {
-				root = root.parent;
-			}
+		return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
+	}
 
-			return root;
-		}
+	/**
+	 * The top parent for the node. If node has no parent it is the root itself.
+	 *
+	 * @readonly
+	 * @property {Number} depth
+	 */
+	get root() {
+		let root = this;
 
-		/**
-		 * @see {@link treeModel.AttributeList#getAttr}
-		 */
-		getAttr( key ) {
-			return this._attrs.getAttr( key );
+		while ( root.parent ) {
+			root = root.parent;
 		}
 
-		/**
-		 * @see {@link treeModel.AttributeList#getAttrs}
-		 */
-		getAttrs() {
-			return this._attrs.getAttrs();
-		}
+		return root;
+	}
 
-		/**
-		 * Index of the node in the parent element or null if the node has no parent.
-		 *
-		 * Throws error if the parent element does not contain this node.
-		 *
-		 * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
-		 */
-		getIndex() {
-			let pos;
-
-			if ( !this.parent ) {
-				return null;
-			}
-
-			// No parent or child doesn't exist in parent's children.
-			if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
-				/**
-				 * The node's parent does not contain this node. It means that the document tree is corrupted.
-				 *
-				 * @error node-not-found-in-parent
-				 */
-				throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
-			}
-
-			return pos;
-		}
+	/**
+	 * @see {@link treeModel.AttributeList#getAttr}
+	 */
+	getAttr( key ) {
+		return this._attrs.getAttr( key );
+	}
 
-		/**
-		 * Gets path to the node. For example if the node is the second child of the first child of the root then the path
-		 * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link treeModel.Position}.
-		 *
-		 * @returns {Number[]} The path.
-		 */
-		getPath() {
-			const path = [];
-			let node = this;
+	/**
+	 * @see {@link treeModel.AttributeList#getAttrs}
+	 */
+	getAttrs() {
+		return this._attrs.getAttrs();
+	}
 
-			while ( node.parent ) {
-				path.unshift( node.getIndex() );
-				node = node.parent;
-			}
+	/**
+	 * Index of the node in the parent element or null if the node has no parent.
+	 *
+	 * Throws error if the parent element does not contain this node.
+	 *
+	 * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
+	 */
+	getIndex() {
+		let pos;
 
-			return path;
+		if ( !this.parent ) {
+			return null;
 		}
 
-		/**
-		 * @see {@link treeModel.AttributeList#hasAttr}
-		 */
-		hasAttr( key ) {
-			return this._attrs.hasAttr( key );
+		// No parent or child doesn't exist in parent's children.
+		if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
+			/**
+			 * The node's parent does not contain this node. It means that the document tree is corrupted.
+			 *
+			 * @error node-not-found-in-parent
+			 */
+			throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
 		}
 
-		/**
-		 * @see {@link treeModel.AttributeList#removeAttr}
-		 */
-		removeAttr( key ) {
-			this._attrs.removeAttr( key );
-		}
+		return pos;
+	}
 
-		/**
-		 * @see {@link treeModel.AttributeList#setAttr}
-		 */
-		setAttr( attr ) {
-			this._attrs.setAttr( attr );
-		}
+	/**
+	 * Gets path to the node. For example if the node is the second child of the first child of the root then the path
+	 * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link treeModel.Position}.
+	 *
+	 * @returns {Number[]} The path.
+	 */
+	getPath() {
+		const path = [];
+		let node = this;
 
-		/**
-		 * @see {@link treeModel.AttributeList#setAttrsTo}
-		 */
-		setAttrsTo( attrs ) {
-			this._attrs.setAttrsTo( attrs );
+		while ( node.parent ) {
+			path.unshift( node.getIndex() );
+			node = node.parent;
 		}
 
-		/**
-		 * Custom toJSON method to solve child-parent circular dependencies.
-		 *
-		 * @returns {Object} Clone of this object with the parent property replaced with its name.
-		 */
-		toJSON() {
-			const json = utils.clone( this );
+		return path;
+	}
 
-			// Due to circular references we need to remove parent reference.
-			json.parent = this.parent ? this.parent.name : null;
+	/**
+	 * @see {@link treeModel.AttributeList#hasAttr}
+	 */
+	hasAttr( key ) {
+		return this._attrs.hasAttr( key );
+	}
 
-			return json;
-		}
+	/**
+	 * @see {@link treeModel.AttributeList#removeAttr}
+	 */
+	removeAttr( key ) {
+		this._attrs.removeAttr( key );
 	}
 
-	return Node;
-} );
+	/**
+	 * @see {@link treeModel.AttributeList#setAttr}
+	 */
+	setAttr( attr ) {
+		this._attrs.setAttr( attr );
+	}
+
+	/**
+	 * @see {@link treeModel.AttributeList#setAttrsTo}
+	 */
+	setAttrsTo( attrs ) {
+		this._attrs.setAttrsTo( attrs );
+	}
+
+	/**
+	 * Custom toJSON method to solve child-parent circular dependencies.
+	 *
+	 * @returns {Object} Clone of this object with the parent property replaced with its name.
+	 */
+	toJSON() {
+		const json = langUtils.clone( this );
+
+		// Due to circular references we need to remove parent reference.
+		json.parent = this.parent ? this.parent.name : null;
+
+		return json;
+	}
+}

+ 121 - 125
packages/ckeditor5-engine/src/treemodel/nodelist.js

@@ -5,152 +5,148 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/character',
-	'treemodel/text',
-	'treemodel/node',
-	'utils'
-], ( Character, Text, Node, utils ) => {
+import Character from './character.js';
+import Text from './text.js';
+import Node from './node.js';
+import utils from '../utils.js';
+
+/**
+ * List of nodes. It is used to represent multiple nodes with a given order, for example children of
+ * {@link treeModel.Element} object or nodes inserted using {@link treeModel.operation.InsertOperation}.
+ *
+ * Thanks to the constructor, which accepts various arguments, this class lets you easily create desired list of nodes.
+ *
+ * It also may internally compress nodes.
+ *
+ * @class treeModel.NodeList
+ */
+export default class NodeList {
 	/**
-	 * List of nodes. It is used to represent multiple nodes with a given order, for example children of
-	 * {@link treeModel.Element} object or nodes inserted using {@link treeModel.operation.InsertOperation}.
+	 * Constructor let you create a list of nodes in many ways. See examples:
+	 *
+	 *		let nodeList = new NodeList( [ new Element( p1 ), new Element( p1 ) ] );
+	 *		nodeList.length; // 2
+	 *
+	 *		let nodeList = new NodeList( new Element( p ) );
+	 *		nodeList.length; // 1
 	 *
-	 * Thanks to the constructor, which accepts various arguments, this class lets you easily create desired list of nodes.
+	 *		let nodeList = new NodeList( [ 'foo', new Element( p ), 'bar' ] );
+	 *		nodeList.length; // 7
 	 *
-	 * It also may internally compress nodes.
+	 *		let nodeList = new NodeList( 'foo' );
+	 *		nodeList.length; // 3
 	 *
-	 * @class treeModel.NodeList
+	 *		let nodeList = new NodeList( new Text( 'foo', [ new Attribute( 'bar', 'bom' ) ] ) );
+	 *		nodeList.length; // 3
+	 *		nodeList.get( 0 ).getAttr( 'bar' ); // 'bom'
+	 *		nodeList.get( 1 ).getAttr( 'bar' ); // 'bom'
+	 *		nodeList.get( 2 ).getAttr( 'bar' ); // 'bom'
+	 *
+	 *		let nodeListA = new NodeList( 'foo' );
+	 *		let nodeListB = new NodeList( nodeListA );
+	 *		nodeListA === nodeListB // true
+	 *		nodeListB.length // 3
+	 *
+	 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes List of nodes.
+	 * @constructor
 	 */
-	class NodeList {
+	constructor( nodes ) {
+		if ( nodes instanceof NodeList ) {
+			// We do not clone anything.
+			return nodes;
+		}
+
 		/**
-		 * Constructor let you create a list of nodes in many ways. See examples:
+		 * Internal array to store nodes.
 		 *
-		 *		let nodeList = new NodeList( [ new Element( p1 ), new Element( p1 ) ] );
-		 *		nodeList.length; // 2
-		 *
-		 *		let nodeList = new NodeList( new Element( p ) );
-		 *		nodeList.length; // 1
-		 *
-		 *		let nodeList = new NodeList( [ 'foo', new Element( p ), 'bar' ] );
-		 *		nodeList.length; // 7
-		 *
-		 *		let nodeList = new NodeList( 'foo' );
-		 *		nodeList.length; // 3
-		 *
-		 *		let nodeList = new NodeList( new Text( 'foo', [ new Attribute( 'bar', 'bom' ) ] ) );
-		 *		nodeList.length; // 3
-		 *		nodeList.get( 0 ).getAttr( 'bar' ); // 'bom'
-		 *		nodeList.get( 1 ).getAttr( 'bar' ); // 'bom'
-		 *		nodeList.get( 2 ).getAttr( 'bar' ); // 'bom'
-		 *
-		 *		let nodeListA = new NodeList( 'foo' );
-		 *		let nodeListB = new NodeList( nodeListA );
-		 *		nodeListA === nodeListB // true
-		 *		nodeListB.length // 3
-		 *
-		 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes List of nodes.
-		 * @constructor
+		 * @private
+		 * @property {Array}
 		 */
-		constructor( nodes ) {
-			if ( nodes instanceof NodeList ) {
-				// We do not clone anything.
-				return nodes;
-			}
+		this._nodes = [];
 
-			/**
-			 * Internal array to store nodes.
-			 *
-			 * @private
-			 * @property {Array}
-			 */
-			this._nodes = [];
+		if ( nodes ) {
+			let node, character;
 
-			if ( nodes ) {
-				let node, character;
+			if ( !utils.isIterable( nodes ) ) {
+				nodes = [ nodes ];
+			}
 
-				if ( !utils.isIterable( nodes ) ) {
-					nodes = [ nodes ];
+			for ( node of nodes ) {
+				// Node.
+				if ( node instanceof Node ) {
+					this._nodes.push( node );
 				}
-
-				for ( node of nodes ) {
-					// Node.
-					if ( node instanceof Node ) {
-						this._nodes.push( node );
-					}
-					// Text.
-					else if ( node instanceof Text ) {
-						for ( character of node.text ) {
-							this._nodes.push( new Character( character, node.attrs ) );
-						}
+				// Text.
+				else if ( node instanceof Text ) {
+					for ( character of node.text ) {
+						this._nodes.push( new Character( character, node.attrs ) );
 					}
-					// String.
-					else {
-						for ( character of node ) {
-							this._nodes.push( new Character( character ) );
-						}
+				}
+				// String.
+				else {
+					for ( character of node ) {
+						this._nodes.push( new Character( character ) );
 					}
 				}
 			}
 		}
+	}
 
-		/**
-		 * Number of nodes in the node list.
-		 *
-		 * @readonly
-		 * @property {Number} length
-		 */
-		get length() {
-			return this._nodes.length;
-		}
-
-		/**
-		 * Node list iterator.
-		 */
-		[ Symbol.iterator ]() {
-			return this._nodes[ Symbol.iterator ]();
-		}
+	/**
+	 * Number of nodes in the node list.
+	 *
+	 * @readonly
+	 * @property {Number} length
+	 */
+	get length() {
+		return this._nodes.length;
+	}
 
-		/**
-		 * Returns node at the given index.
-		 *
-		 * @param {Number} index Node index.
-		 * @returns {treeModel.Node} Node at given index.
-		 */
-		get( index ) {
-			return this._nodes[ index ];
-		}
+	/**
+	 * Node list iterator.
+	 */
+	[ Symbol.iterator ]() {
+		return this._nodes[ Symbol.iterator ]();
+	}
 
-		/**
-		 * Search for the node in the node list.
-		 *
-		 * @param {treeModel.Node} node Node to find.
-		 * @returns {Number} Position of the node in the list.
-		 */
-		indexOf( node ) {
-			return this._nodes.indexOf( node );
-		}
+	/**
+	 * Returns node at the given index.
+	 *
+	 * @param {Number} index Node index.
+	 * @returns {treeModel.Node} Node at given index.
+	 */
+	get( index ) {
+		return this._nodes[ index ];
+	}
 
-		/**
-		 * Inserts nodes from the given node list into this node list at the given index.
-		 *
-		 * @param {Number} index Position where nodes should be inserted.
-		 * @param {treeModel.NodeList} nodeList List of nodes to insert.
-		 */
-		insert( index, nodeList ) {
-			this._nodes.splice.apply( this._nodes, [ index, 0 ].concat( nodeList._nodes ) );
-		}
+	/**
+	 * Search for the node in the node list.
+	 *
+	 * @param {treeModel.Node} node Node to find.
+	 * @returns {Number} Position of the node in the list.
+	 */
+	indexOf( node ) {
+		return this._nodes.indexOf( node );
+	}
 
-		/**
-		 * Removes number of nodes starting at the given index.
-		 *
-		 * @param {Number} index Position of the first node to remove.
-		 * @param {Number} number Number of nodes to remove.
-		 * @returns {treeModel.NodeList} List of removed nodes.
-		 */
-		remove( index, number ) {
-			return new NodeList( this._nodes.splice( index, number ) );
-		}
+	/**
+	 * Inserts nodes from the given node list into this node list at the given index.
+	 *
+	 * @param {Number} index Position where nodes should be inserted.
+	 * @param {treeModel.NodeList} nodeList List of nodes to insert.
+	 */
+	insert( index, nodeList ) {
+		this._nodes.splice.apply( this._nodes, [ index, 0 ].concat( nodeList._nodes ) );
 	}
 
-	return NodeList;
-} );
+	/**
+	 * Removes number of nodes starting at the given index.
+	 *
+	 * @param {Number} index Position of the first node to remove.
+	 * @param {Number} number Number of nodes to remove.
+	 * @returns {treeModel.NodeList} List of removed nodes.
+	 */
+	remove( index, number ) {
+		return new NodeList( this._nodes.splice( index, number ) );
+	}
+}

+ 108 - 112
packages/ckeditor5-engine/src/treemodel/operation/attributeoperation.js

@@ -5,139 +5,135 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/operation/operation',
-	'treemodel/range',
-	'ckeditorerror'
-], ( Operation, Range, CKEditorError ) => {
+import Operation from './operation.js';
+import Range from '../range.js';
+import CKEditorError from '../../ckeditorerror.js';
+
+/**
+ * Operation to change nodes' attribute. Using this class you can add, remove or change value of the attribute.
+ *
+ * @class treeModel.operation.AttributeOperation
+ */
+export default class AttributeOperation extends Operation {
 	/**
-	 * Operation to change nodes' attribute. Using this class you can add, remove or change value of the attribute.
+	 * Creates an operation that changes, removes or adds attributes.
+	 *
+	 * If only the new attribute is set, then it will be inserted. Note that in all nodes in ranges there must be
+	 * no attributes with the same key as the new attribute.
+	 *
+	 * If only the old attribute is set, then it will be removed. Note that this attribute must be present in all nodes in
+	 * ranges.
 	 *
-	 * @class treeModel.operation.AttributeOperation
+	 * If both new and old attributes are set, then the operation will change the attribute value. Note that both new and
+	 * old attributes have to have the same key and the old attribute must be present in all nodes in ranges.
+	 *
+	 * @param {treeModel.Range} range Range on which the operation should be applied.
+	 * @param {treeModel.Attribute|null} oldAttr Attribute to be removed. If `null`, then the operation just inserts a new attribute.
+	 * @param {treeModel.Attribute|null} newAttr Attribute to be added. If `null`, then the operation just removes the attribute.
+	 * @param {Number} baseVersion {@link treeModel.Document#version} on which the operation can be applied.
+	 * @constructor
 	 */
-	class AttributeOperation extends Operation {
+	constructor( range, oldAttr, newAttr, baseVersion ) {
+		super( baseVersion );
+
 		/**
-		 * Creates an operation that changes, removes or adds attributes.
-		 *
-		 * If only the new attribute is set, then it will be inserted. Note that in all nodes in ranges there must be
-		 * no attributes with the same key as the new attribute.
+		 * Range on which operation should be applied.
 		 *
-		 * If only the old attribute is set, then it will be removed. Note that this attribute must be present in all nodes in
-		 * ranges.
+		 * @readonly
+		 * @type {treeModel.Range}
+		 */
+		this.range = Range.createFromRange( range );
+
+		/**
+		 * Old attribute to change. Set to `null` if operation inserts a new attribute.
 		 *
-		 * If both new and old attributes are set, then the operation will change the attribute value. Note that both new and
-		 * old attributes have to have the same key and the old attribute must be present in all nodes in ranges.
+		 * @readonly
+		 * @type {treeModel.Attribute|null}
+		 */
+		this.oldAttr = oldAttr;
+
+		/**
+		 * New attribute. Set to `null` if operation removes the attribute.
 		 *
-		 * @param {treeModel.Range} range Range on which the operation should be applied.
-		 * @param {treeModel.Attribute|null} oldAttr Attribute to be removed. If `null`, then the operation just inserts a new attribute.
-		 * @param {treeModel.Attribute|null} newAttr Attribute to be added. If `null`, then the operation just removes the attribute.
-		 * @param {Number} baseVersion {@link treeModel.Document#version} on which the operation can be applied.
-		 * @constructor
+		 * @readonly
+		 * @type {treeModel.Attribute|null}
 		 */
-		constructor( range, oldAttr, newAttr, baseVersion ) {
-			super( baseVersion );
+		this.newAttr = newAttr;
+	}
 
-			/**
-			 * Range on which operation should be applied.
-			 *
-			 * @readonly
-			 * @type {treeModel.Range}
-			 */
-			this.range = Range.createFromRange( range );
+	get type() {
+		return 'attr';
+	}
 
-			/**
-			 * Old attribute to change. Set to `null` if operation inserts a new attribute.
-			 *
-			 * @readonly
-			 * @type {treeModel.Attribute|null}
-			 */
-			this.oldAttr = oldAttr;
+	clone() {
+		return new AttributeOperation( this.range, this.oldAttr, this.newAttr, this.baseVersion );
+	}
+
+	getReversed() {
+		return new AttributeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
+	}
 
+	_execute() {
+		const oldAttr = this.oldAttr;
+		const newAttr = this.newAttr;
+
+		if ( oldAttr !== null && newAttr !== null && oldAttr.key != newAttr.key ) {
 			/**
-			 * New attribute. Set to `null` if operation removes the attribute.
+			 * Old and new attributes should have the same keys.
 			 *
-			 * @readonly
-			 * @type {treeModel.Attribute|null}
+			 * @error operation-attribute-different-keys
+			 * @param {treeModel.Attribute} oldAttr
+			 * @param {treeModel.Attribute} newAttr
 			 */
-			this.newAttr = newAttr;
-		}
-
-		get type() {
-			return 'attr';
-		}
-
-		clone() {
-			return new AttributeOperation( this.range, this.oldAttr, this.newAttr, this.baseVersion );
+			throw new CKEditorError(
+				'operation-attribute-different-keys: Old and new attributes should have the same keys.',
+				{ oldAttr: oldAttr, newAttr: newAttr } );
 		}
 
-		getReversed() {
-			return new AttributeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
-		}
-
-		_execute() {
-			const oldAttr = this.oldAttr;
-			const newAttr = this.newAttr;
-
-			if ( oldAttr !== null && newAttr !== null && oldAttr.key != newAttr.key ) {
-				/**
-				 * Old and new attributes should have the same keys.
-				 *
-				 * @error operation-attribute-different-keys
-				 * @param {treeModel.Attribute} oldAttr
-				 * @param {treeModel.Attribute} newAttr
-				 */
-				throw new CKEditorError(
-					'operation-attribute-different-keys: Old and new attributes should have the same keys.',
-					{ oldAttr: oldAttr, newAttr: newAttr } );
-			}
+		// Remove or change.
+		if ( oldAttr !== null ) {
+			for ( let node of this.range.getAllNodes() ) {
+				if ( !node.hasAttr( oldAttr ) ) {
+					/**
+					 * The attribute which should be removed does not exists for the given node.
+					 *
+					 * @error operation-attribute-no-attr-to-remove
+					 * @param {treeModel.Node} node
+					 * @param {treeModel.Attribute} attr
+					 */
+					throw new CKEditorError(
+						'operation-attribute-no-attr-to-remove: The attribute which should be removed does not exists for given node.',
+						{ node: node, attr: oldAttr } );
+				}
 
-			// Remove or change.
-			if ( oldAttr !== null ) {
-				for ( let node of this.range.getAllNodes() ) {
-					if ( !node.hasAttr( oldAttr ) ) {
-						/**
-						 * The attribute which should be removed does not exists for the given node.
-						 *
-						 * @error operation-attribute-no-attr-to-remove
-						 * @param {treeModel.Node} node
-						 * @param {treeModel.Attribute} attr
-						 */
-						throw new CKEditorError(
-							'operation-attribute-no-attr-to-remove: The attribute which should be removed does not exists for given node.',
-							{ node: node, attr: oldAttr } );
-					}
-
-					// There is no use in removing attribute if we will overwrite it later.
-					// Still it is profitable to run through the loop to check if all nodes in the range has old attribute.
-					if ( newAttr === null ) {
-						node.removeAttr( oldAttr.key );
-					}
+				// There is no use in removing attribute if we will overwrite it later.
+				// Still it is profitable to run through the loop to check if all nodes in the range has old attribute.
+				if ( newAttr === null ) {
+					node.removeAttr( oldAttr.key );
 				}
 			}
+		}
 
-			// Insert or change.
-			if ( newAttr !== null ) {
-				for ( let node of this.range.getAllNodes() ) {
-					if ( oldAttr === null && node.hasAttr( newAttr.key ) ) {
-						/**
-						 * The attribute with given key already exists for the given node.
-						 *
-						 * @error operation-attribute-attr-exists
-						 * @param {treeModel.Node} node
-						 * @param {treeModel.Attribute} attr
-						 */
-						throw new CKEditorError(
-							'operation-attribute-attr-exists: The attribute with given key already exists.',
-							{ node: node, attr: newAttr } );
-					}
-
-					node.setAttr( newAttr );
+		// Insert or change.
+		if ( newAttr !== null ) {
+			for ( let node of this.range.getAllNodes() ) {
+				if ( oldAttr === null && node.hasAttr( newAttr.key ) ) {
+					/**
+					 * The attribute with given key already exists for the given node.
+					 *
+					 * @error operation-attribute-attr-exists
+					 * @param {treeModel.Node} node
+					 * @param {treeModel.Attribute} attr
+					 */
+					throw new CKEditorError(
+						'operation-attribute-attr-exists: The attribute with given key already exists.',
+						{ node: node, attr: newAttr } );
 				}
-			}
 
-			return { range: this.range, oldAttr: oldAttr, newAttr: newAttr };
+				node.setAttr( newAttr );
+			}
 		}
-	}
 
-	return AttributeOperation;
-} );
+		return { range: this.range, oldAttr: oldAttr, newAttr: newAttr };
+	}
+}

+ 48 - 55
packages/ckeditor5-engine/src/treemodel/operation/insertoperation.js

@@ -5,71 +5,64 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/operation/operation',
-	'treemodel/nodelist',
-	'treemodel/position',
-	'treemodel/range',
-	'treemodel/operation/removeoperation'
-], ( Operation, NodeList, Position, Range ) => {
+import Operation from './operation.js';
+import NodeList from '../nodelist.js';
+import Position from '../position.js';
+import Range from '../range.js';
+import RemoveOperation from './removeoperation.js';
+
+/**
+ * Operation to insert list of nodes on the given position in the tree data model.
+ *
+ * @class treeModel.operation.InsertOperation
+ */
+export default class InsertOperation extends Operation {
 	/**
-	 * Operation to insert list of nodes on the given position in the tree data model.
+	 * Creates an insert operation.
 	 *
-	 * @class treeModel.operation.InsertOperation
+	 * @param {treeModel.Position} position Position of insertion.
+	 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
+	 * List of nodes can be any type accepted by the {@link treeModel.NodeList} constructor.
+	 * @param {Number} baseVersion {@link treeModel.Document#version} on which operation can be applied.
+	 * @constructor
 	 */
-	class InsertOperation extends Operation {
+	constructor( position, nodes, baseVersion ) {
+		super( baseVersion );
+
 		/**
-		 * Creates an insert operation.
+		 * Position of insertion.
 		 *
-		 * @param {treeModel.Position} position Position of insertion.
-		 * @param {treeModel.Node|treeModel.Text|treeModel.NodeList|String|Iterable} nodes The list of nodes to be inserted.
-		 * List of nodes can be any type accepted by the {@link treeModel.NodeList} constructor.
-		 * @param {Number} baseVersion {@link treeModel.Document#version} on which operation can be applied.
-		 * @constructor
+		 * @readonly
+		 * @type {treeModel.Position}
 		 */
-		constructor( position, nodes, baseVersion ) {
-			super( baseVersion );
+		this.position = Position.createFromPosition( position );
 
-			/**
-			 * Position of insertion.
-			 *
-			 * @readonly
-			 * @type {treeModel.Position}
-			 */
-			this.position = Position.createFromPosition( position );
-
-			/**
-			 * List of nodes to insert.
-			 *
-			 * @readonly
-			 * @type {treeModel.NodeList}
-			 */
-			this.nodeList = new NodeList( nodes );
-		}
-
-		get type() {
-			return 'insert';
-		}
+		/**
+		 * List of nodes to insert.
+		 *
+		 * @readonly
+		 * @type {treeModel.NodeList}
+		 */
+		this.nodeList = new NodeList( nodes );
+	}
 
-		clone() {
-			return new InsertOperation( this.position, this.nodeList, this.baseVersion );
-		}
+	get type() {
+		return 'insert';
+	}
 
-		getReversed() {
-			// Because of circular dependencies we need to re-require remove operation here.
-			const RemoveOperation = CKEDITOR.require( 'treemodel/operation/removeoperation' );
+	clone() {
+		return new InsertOperation( this.position, this.nodeList, this.baseVersion );
+	}
 
-			return new RemoveOperation( this.position, this.nodeList.length, this.baseVersion + 1 );
-		}
+	getReversed() {
+		return new RemoveOperation( this.position, this.nodeList.length, this.baseVersion + 1 );
+	}
 
-		_execute() {
-			this.position.parent.insertChildren( this.position.offset, this.nodeList );
+	_execute() {
+		this.position.parent.insertChildren( this.position.offset, this.nodeList );
 
-			return {
-				range: Range.createFromPositionAndShift( this.position, this.nodeList.length )
-			};
-		}
+		return {
+			range: Range.createFromPositionAndShift( this.position, this.nodeList.length )
+		};
 	}
-
-	return InsertOperation;
-} );
+}

+ 105 - 109
packages/ckeditor5-engine/src/treemodel/operation/moveoperation.js

@@ -5,135 +5,131 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/operation/operation',
-	'treemodel/position',
-	'treemodel/range',
-	'ckeditorerror',
-	'utils'
-], ( Operation, Position, Range, CKEditorError, utils ) => {
+import Operation from './operation.js';
+import Position from '../position.js';
+import Range from '../range.js';
+import CKEditorError from '../../ckeditorerror.js';
+import utils from '../../utils.js';
+
+/**
+ * Operation to move list of subsequent nodes from one position in the document to another.
+ *
+ * @class treeModel.operation.MoveOperation
+ */
+export default class MoveOperation extends Operation {
 	/**
-	 * Operation to move list of subsequent nodes from one position in the document to another.
+	 * Creates a move operation.
 	 *
-	 * @class treeModel.operation.MoveOperation
+	 * @param {treeModel.Position} sourcePosition Position before the first node to move.
+	 * @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
+	 * @param {treeModel.Position} targetPosition Position where moved nodes will be inserted.
+	 * @param {Number} baseVersion {@link treeModel.Document#version} on which operation can be applied.
+	 * @constructor
 	 */
-	class MoveOperation extends Operation {
+	constructor( sourcePosition, howMany, targetPosition, baseVersion ) {
+		super( baseVersion );
+
+		/**
+		 * Source move position.
+		 *
+		 * @type {treeModel.Position}
+		 */
+		this.sourcePosition = Position.createFromPosition( sourcePosition );
+
+		/**
+		 * How many nodes to move.
+		 *
+		 * @type {Number}
+		 */
+		this.howMany = howMany;
+
 		/**
-		 * Creates a move operation.
+		 * Target move position.
 		 *
-		 * @param {treeModel.Position} sourcePosition Position before the first node to move.
-		 * @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
-		 * @param {treeModel.Position} targetPosition Position where moved nodes will be inserted.
-		 * @param {Number} baseVersion {@link treeModel.Document#version} on which operation can be applied.
-		 * @constructor
+		 * @type {treeModel.Position}
 		 */
-		constructor( sourcePosition, howMany, targetPosition, baseVersion ) {
-			super( baseVersion );
+		this.targetPosition = Position.createFromPosition( targetPosition );
+	}
 
+	get type() {
+		return 'move';
+	}
+
+	clone() {
+		return new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion );
+	}
+
+	getReversed() {
+		return new this.constructor( this.targetPosition, this.howMany, this.sourcePosition, this.baseVersion + 1 );
+	}
+
+	_execute() {
+		let sourceElement = this.sourcePosition.parent;
+		let targetElement = this.targetPosition.parent;
+		let sourceOffset = this.sourcePosition.offset;
+		let targetOffset = this.targetPosition.offset;
+
+		// Validate whether move operation has correct parameters.
+		// Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
+		// We expect that many errors might be connected with one of scenarios described below.
+		if ( !sourceElement || !targetElement ) {
 			/**
-			 * Source move position.
+			 * Source position or target position is invalid.
 			 *
-			 * @type {treeModel.Position}
+			 * @error operation-move-position-invalid
 			 */
-			this.sourcePosition = Position.createFromPosition( sourcePosition );
-
+			throw new CKEditorError(
+				'operation-move-position-invalid: Source position or target position is invalid.'
+			);
+		} else if ( sourceOffset + this.howMany > sourceElement.getChildCount() ) {
 			/**
-			 * How many nodes to move.
+			 * The nodes which should be moved do not exist.
 			 *
-			 * @type {Number}
+			 * @error operation-move-nodes-do-not-exist
 			 */
-			this.howMany = howMany;
-
+			throw new CKEditorError(
+				'operation-move-nodes-do-not-exist: The nodes which should be moved do not exist.'
+			);
+		} else if ( sourceElement === targetElement && sourceOffset <= targetOffset && targetOffset < sourceOffset + this.howMany ) {
 			/**
-			 * Target move position.
+			 * Trying to move a range of nodes into the middle of that range.
 			 *
-			 * @type {treeModel.Position}
+			 * @error operation-move-range-into-itself
 			 */
-			this.targetPosition = Position.createFromPosition( targetPosition );
-		}
-
-		get type() {
-			return 'move';
-		}
-
-		clone() {
-			return new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion );
-		}
-
-		getReversed() {
-			return new this.constructor( this.targetPosition, this.howMany, this.sourcePosition, this.baseVersion + 1 );
-		}
-
-		_execute() {
-			let sourceElement = this.sourcePosition.parent;
-			let targetElement = this.targetPosition.parent;
-			let sourceOffset = this.sourcePosition.offset;
-			let targetOffset = this.targetPosition.offset;
-
-			// Validate whether move operation has correct parameters.
-			// Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
-			// We expect that many errors might be connected with one of scenarios described below.
-			if ( !sourceElement || !targetElement ) {
-				/**
-				 * Source position or target position is invalid.
-				 *
-				 * @error operation-move-position-invalid
-				 */
-				throw new CKEditorError(
-					'operation-move-position-invalid: Source position or target position is invalid.'
-				);
-			} else if ( sourceOffset + this.howMany > sourceElement.getChildCount() ) {
-				/**
-				 * The nodes which should be moved do not exist.
-				 *
-				 * @error operation-move-nodes-do-not-exist
-				 */
-				throw new CKEditorError(
-					'operation-move-nodes-do-not-exist: The nodes which should be moved do not exist.'
-				);
-			} else if ( sourceElement === targetElement && sourceOffset <= targetOffset && targetOffset < sourceOffset + this.howMany ) {
-				/**
-				 * Trying to move a range of nodes into the middle of that range.
-				 *
-				 * @error operation-move-range-into-itself
-				 */
-				throw new CKEditorError(
-					'operation-move-range-into-itself: Trying to move a range of nodes to the inside of that range.'
-				);
-			} else {
-				if ( utils.compareArrays( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == utils.compareArrays.PREFIX ) {
-					let i = this.sourcePosition.path.length - 1;
-
-					if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
-						/**
-						 * Trying to move a range of nodes into one of nodes from that range.
-						 *
-						 * @error operation-move-node-into-itself
-						 */
-						throw new CKEditorError(
-							'operation-move-node-into-itself: Trying to move a range of nodes into one of nodes from that range.'
-						);
-					}
+			throw new CKEditorError(
+				'operation-move-range-into-itself: Trying to move a range of nodes to the inside of that range.'
+			);
+		} else {
+			if ( utils.compareArrays( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == utils.compareArrays.PREFIX ) {
+				let i = this.sourcePosition.path.length - 1;
+
+				if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
+					/**
+					 * Trying to move a range of nodes into one of nodes from that range.
+					 *
+					 * @error operation-move-node-into-itself
+					 */
+					throw new CKEditorError(
+						'operation-move-node-into-itself: Trying to move a range of nodes into one of nodes from that range.'
+					);
 				}
 			}
-			// End of validation.
+		}
+		// End of validation.
 
-			// If we move children in the same element and we remove elements on the position before the target we
-			// need to update a target offset.
-			if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
-				targetOffset -= this.howMany;
-			}
+		// If we move children in the same element and we remove elements on the position before the target we
+		// need to update a target offset.
+		if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
+			targetOffset -= this.howMany;
+		}
 
-			const removedNodes = sourceElement.removeChildren( sourceOffset, this.howMany );
+		const removedNodes = sourceElement.removeChildren( sourceOffset, this.howMany );
 
-			targetElement.insertChildren( targetOffset, removedNodes );
+		targetElement.insertChildren( targetOffset, removedNodes );
 
-			return {
-				sourcePosition: this.sourcePosition,
-				range: Range.createFromPositionAndShift( this.targetPosition, this.howMany )
-			};
-		}
+		return {
+			sourcePosition: this.sourcePosition,
+			range: Range.createFromPositionAndShift( this.targetPosition, this.howMany )
+		};
 	}
-
-	return MoveOperation;
-} );
+}

+ 22 - 26
packages/ckeditor5-engine/src/treemodel/operation/nooperation.js

@@ -5,33 +5,29 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/operation/operation'
-], ( Operation ) => {
-	/**
-	 * Operation which is doing nothing ("empty operation", "do-nothing operation", "noop").
-	 * This is an operation, which when executed does not change the tree model.
-	 * It still has some parameters defined for transformation purposes.
-	 *
-	 * In most cases this operation is a result of transforming operations. When transformation returns
-	 * {@link treeModel.operation.NoOperation} it means that changes done by the transformed operation
-	 * have already been applied.
-	 *
-	 * @class treeModel.operation.NoOperation
-	 */
-	class NoOperation extends Operation {
-		clone() {
-			return new NoOperation( this.baseVersion );
-		}
+import Operation from './operation.js';
 
-		getReversed() {
-			return new NoOperation( this.baseVersion + 1 );
-		}
+/**
+ * Operation which is doing nothing ("empty operation", "do-nothing operation", "noop").
+ * This is an operation, which when executed does not change the tree model.
+ * It still has some parameters defined for transformation purposes.
+ *
+ * In most cases this operation is a result of transforming operations. When transformation returns
+ * {@link treeModel.operation.NoOperation} it means that changes done by the transformed operation
+ * have already been applied.
+ *
+ * @class treeModel.operation.NoOperation
+ */
+export default class NoOperation extends Operation {
+	clone() {
+		return new NoOperation( this.baseVersion );
+	}
 
-		_execute() {
-			// Do nothing.
-		}
+	getReversed() {
+		return new NoOperation( this.baseVersion + 1 );
 	}
 
-	return NoOperation;
-} );
+	_execute() {
+		// Do nothing.
+	}
+}

+ 57 - 61
packages/ckeditor5-engine/src/treemodel/operation/operation.js

@@ -5,76 +5,72 @@
 
 'use strict';
 
-CKEDITOR.define( [], () => {
+/**
+ * Abstract base operation class.
+ *
+ * @abstract
+ * @class treeModel.operation.Operation
+ */
+export default class Operation {
 	/**
-	 * Abstract base operation class.
+	 * Base operation constructor.
 	 *
-	 * @abstract
-	 * @class treeModel.operation.Operation
+	 * @param {Number} baseVersion {@link treeModel.Document#version} on which the operation can be applied.
+	 * @constructor
 	 */
-	class Operation {
+	constructor( baseVersion ) {
 		/**
-		 * Base operation constructor.
+		 * {@link treeModel.Document#version} on which operation can be applied. If you try to
+		 * {@link treeModel.Document#applyOperation apply} operation with different base version than the
+		 * {@link treeModel.Document#version document version} the {@link document-applyOperation-wrong-version}
+		 * error is thrown.
 		 *
-		 * @param {Number} baseVersion {@link treeModel.Document#version} on which the operation can be applied.
-		 * @constructor
+		 * @property {Number}
 		 */
-		constructor( baseVersion ) {
-			/**
-			 * {@link treeModel.Document#version} on which operation can be applied. If you try to
-			 * {@link treeModel.Document#applyOperation apply} operation with different base version than the
-			 * {@link treeModel.Document#version document version} the {@link document-applyOperation-wrong-version}
-			 * error is thrown.
-			 *
-			 * @property {Number}
-			 */
-			this.baseVersion = baseVersion;
+		this.baseVersion = baseVersion;
 
-			/**
-			 * Operation type.
-			 *
-			 * @property {String} type
-			 */
+		/**
+		 * Operation type.
+		 *
+		 * @property {String} type
+		 */
 
-			/**
-			 * {@link treeModel.Delta Delta} which the operation is a part of. This property is set by the
-			 * {@link treeModel.Delta delta} when the operations is added to it by the
-			 * {@link treeModel.Delta#addOperation} method.
-			 *
-			 * @property {treeModel.Delta} delta
-			 */
+		/**
+		 * {@link treeModel.Delta Delta} which the operation is a part of. This property is set by the
+		 * {@link treeModel.Delta delta} when the operations is added to it by the
+		 * {@link treeModel.Delta#addOperation} method.
+		 *
+		 * @property {treeModel.Delta} delta
+		 */
 
-			/**
-			 * Creates and returns an operation that has the same parameters as this operation.
-			 *
-			 * @method clone
-			 * @returns {treeModel.operation.Operation} Clone of this operation.
-			 */
+		/**
+		 * Creates and returns an operation that has the same parameters as this operation.
+		 *
+		 * @method clone
+		 * @returns {treeModel.operation.Operation} Clone of this operation.
+		 */
 
-			/**
-			 * Creates and returns a reverse operation. Reverse operation when executed right after
-			 * the original operation will bring back tree model state to the point before the original
-			 * operation execution. In other words, it reverses changes done by the original operation.
-			 *
-			 * Keep in mind that tree model state may change since executing the original operation,
-			 * so reverse operation will be "outdated". In that case you will need to
-			 * {@link treeModel.operation.transform} it by all operations that were executed after the original operation.
-			 *
-			 * @method getReversed
-			 * @returns {treeModel.operation.Operation} Reversed operation.
-			 */
+		/**
+		 * Creates and returns a reverse operation. Reverse operation when executed right after
+		 * the original operation will bring back tree model state to the point before the original
+		 * operation execution. In other words, it reverses changes done by the original operation.
+		 *
+		 * Keep in mind that tree model state may change since executing the original operation,
+		 * so reverse operation will be "outdated". In that case you will need to
+		 * {@link treeModel.operation.transform} it by all operations that were executed after the original operation.
+		 *
+		 * @method getReversed
+		 * @returns {treeModel.operation.Operation} Reversed operation.
+		 */
 
-			/**
-			 * Executes the operation - modifications described by the operation attributes
-			 * will be applied to the tree model.
-			 *
-			 * @protected
-			 * @method _execute
-			 * @returns {Object} Object with additional information about the applied changes. Always has `range`
-			 * property containing changed nodes. May have additional properties depending on the operation type.
-			 */
-		}
+		/**
+		 * Executes the operation - modifications described by the operation attributes
+		 * will be applied to the tree model.
+		 *
+		 * @protected
+		 * @method _execute
+		 * @returns {Object} Object with additional information about the applied changes. Always has `range`
+		 * property containing changed nodes. May have additional properties depending on the operation type.
+		 */
 	}
-
-	return Operation;
-} );
+}

+ 20 - 27
packages/ckeditor5-engine/src/treemodel/operation/reinsertoperation.js

@@ -5,33 +5,26 @@
 
 'use strict';
 
-CKEDITOR.define( [
-	'treemodel/operation/moveoperation',
-	'treemodel/operation/removeoperation'
-], ( MoveOperation ) => {
-	/**
-	 * Operation to reinsert previously removed nodes back to the non-graveyard root.
-	 * This is basically {@link treeModel.operation.MoveOperation} but it returns
-	 * {@link treeModel.operation.RemoveOperation} when reversed.
-	 *
-	 * With this class, we achieve two goals: by having separate classes it's easier to distinguish whether move
-	 * operation is actually a remove/reinsert operation and fire proper events. Also it
-	 * will be easier to expand if we need to change operation's behavior if it is remove/reinsert.
-	 *
-	 * @class treeModel.operation.ReinsertOperation
-	 */
-	class ReinsertOperation extends MoveOperation {
-		getReversed() {
-			// Because of circular dependencies we need to re-require reinsert operation here.
-			const RemoveOperation = CKEDITOR.require( 'treemodel/operation/removeoperation' );
+import MoveOperation from './moveoperation.js';
+import RemoveOperation from './removeoperation.js';
 
-			return new RemoveOperation( this.targetPosition, this.howMany, this.baseVersion + 1 );
-		}
-
-		get type() {
-			return 'reinsert';
-		}
+/**
+ * Operation to reinsert previously removed nodes back to the non-graveyard root.
+ * This is basically {@link treeModel.operation.MoveOperation} but it returns
+ * {@link treeModel.operation.RemoveOperation} when reversed.
+ *
+ * With this class, we achieve two goals: by having separate classes it's easier to distinguish whether move
+ * operation is actually a remove/reinsert operation and fire proper events. Also it
+ * will be easier to expand if we need to change operation's behavior if it is remove/reinsert.
+ *
+ * @class treeModel.operation.ReinsertOperation
+ */
+export default class ReinsertOperation extends MoveOperation {
+	getReversed() {
+		return new RemoveOperation( this.targetPosition, this.howMany, this.baseVersion + 1 );
 	}
 
-	return ReinsertOperation;
-} );
+	get type() {
+		return 'reinsert';
+	}
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików