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

Merge branch 'master' into t/171

Piotrek Koszuliński 10 лет назад
Родитель
Сommit
aace76b7cf
52 измененных файлов с 687 добавлено и 1080 удалено
  1. 25 11
      packages/ckeditor5-engine/dev/tasks/lodash/tasks.js
  2. 1 0
      packages/ckeditor5-engine/package.json
  3. 4 3
      packages/ckeditor5-engine/src/config.js
  4. 2 2
      packages/ckeditor5-engine/src/editor.js
  5. 0 28
      packages/ckeditor5-engine/src/lib/lodash/array/last.js
  6. 0 58
      packages/ckeditor5-engine/src/lib/lodash/function/restParam.js
  7. 0 20
      packages/ckeditor5-engine/src/lib/lodash/internal/arrayCopy.js
  8. 0 32
      packages/ckeditor5-engine/src/lib/lodash/internal/assignWith.js
  9. 0 23
      packages/ckeditor5-engine/src/lib/lodash/internal/baseCopy.js
  10. 0 17
      packages/ckeditor5-engine/src/lib/lodash/internal/baseForIn.js
  11. 0 39
      packages/ckeditor5-engine/src/lib/lodash/internal/bindCallback.js
  12. 0 22
      packages/ckeditor5-engine/src/lib/lodash/internal/bufferClone.js
  13. 0 15
      packages/ckeditor5-engine/src/lib/lodash/internal/isArrayLike.js
  14. 0 20
      packages/ckeditor5-engine/src/lib/lodash/internal/isLength.js
  15. 0 12
      packages/ckeditor5-engine/src/lib/lodash/internal/isObjectLike.js
  16. 0 41
      packages/ckeditor5-engine/src/lib/lodash/internal/shimKeys.js
  17. 0 14
      packages/ckeditor5-engine/src/lib/lodash/internal/toObject.js
  18. 0 78
      packages/ckeditor5-engine/src/lib/lodash/lang/clone.js
  19. 0 2
      packages/ckeditor5-engine/src/lib/lodash/lang/eq.js
  20. 0 34
      packages/ckeditor5-engine/src/lib/lodash/lang/isArguments.js
  21. 0 48
      packages/ckeditor5-engine/src/lib/lodash/lang/isArray.js
  22. 0 62
      packages/ckeditor5-engine/src/lib/lodash/lang/isEqual.js
  23. 0 38
      packages/ckeditor5-engine/src/lib/lodash/lang/isFunction.js
  24. 0 48
      packages/ckeditor5-engine/src/lib/lodash/lang/isNative.js
  25. 0 37
      packages/ckeditor5-engine/src/lib/lodash/lang/isObject.js
  26. 0 79
      packages/ckeditor5-engine/src/lib/lodash/lang/isPlainObject.js
  27. 0 74
      packages/ckeditor5-engine/src/lib/lodash/lang/isTypedArray.js
  28. 0 51
      packages/ckeditor5-engine/src/lib/lodash/object/assign.js
  29. 0 2
      packages/ckeditor5-engine/src/lib/lodash/object/extend.js
  30. 0 45
      packages/ckeditor5-engine/src/lib/lodash/object/keys.js
  31. 0 64
      packages/ckeditor5-engine/src/lib/lodash/object/keysIn.js
  32. 0 5
      packages/ckeditor5-engine/src/lib/lodash/utility.js
  33. 0 20
      packages/ckeditor5-engine/src/lib/lodash/utility/identity.js
  34. 4 4
      packages/ckeditor5-engine/src/observablemixin.js
  35. 245 0
      packages/ckeditor5-engine/src/treecontroller/mapper.js
  36. 1 1
      packages/ckeditor5-engine/src/treemodel/delta/insertdelta.js
  37. 1 1
      packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js
  38. 15 2
      packages/ckeditor5-engine/src/treemodel/element.js
  39. 2 2
      packages/ckeditor5-engine/src/treemodel/node.js
  40. 5 5
      packages/ckeditor5-engine/src/treemodel/nodelist.js
  41. 1 1
      packages/ckeditor5-engine/src/treemodel/operation/insertoperation.js
  42. 2 2
      packages/ckeditor5-engine/src/treemodel/operation/transform.js
  43. 2 2
      packages/ckeditor5-engine/src/treemodel/position.js
  44. 2 2
      packages/ckeditor5-engine/src/treeview/element.js
  45. 3 3
      packages/ckeditor5-engine/src/ui/domemittermixin.js
  46. 2 2
      packages/ckeditor5-engine/src/ui/model.js
  47. 2 2
      packages/ckeditor5-engine/src/utils.js
  48. 2 2
      packages/ckeditor5-engine/tests/emittermixin.js
  49. 2 2
      packages/ckeditor5-engine/tests/lodash.js
  50. 346 0
      packages/ckeditor5-engine/tests/treecontroller/mapper.js
  51. 15 0
      packages/ckeditor5-engine/tests/treemodel/element.js
  52. 3 3
      packages/ckeditor5-engine/tests/ui/domemittermixin.js

+ 25 - 11
packages/ckeditor5-engine/dev/tasks/lodash/tasks.js

@@ -4,23 +4,37 @@
 
 const gulp = require( 'gulp' );
 const build = require( 'lodash-cli' );
+const del = require( 'del' );
+
+const DEST_PATH = 'src/lib/lodash';
 
 module.exports = function() {
 	const tasks = {
-		lodash( done ) {
-			build( [
-				'modularize',
-				// 'modern',
-				'exports=es',
-				'include=clone,extend,isPlainObject,isObject,isArray,last,isEqual',
-				'--development',
-				'--output', 'src/lib/lodash'
-			], ( e ) => {
-				done( e instanceof Error ? e : null );
-			} );
+		lodash() {
+			return del( DEST_PATH )
+				.then( buildLodash );
 		}
 	};
+
 	gulp.task( 'lodash', tasks.lodash );
 
 	return tasks;
 };
+
+function buildLodash() {
+	return new Promise( ( resolve, reject ) => {
+		build( [
+			'modularize',
+			'exports=es',
+			'include=clone,extend,isPlainObject,isObject,isArray,last,isEqual',
+			'--development',
+			'--output', DEST_PATH
+		], ( err ) => {
+			if ( err instanceof Error ) {
+				reject( err );
+			} else {
+				resolve( null );
+			}
+		} );
+	} );
+}

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

@@ -7,6 +7,7 @@
   ],
   "dependencies": {},
   "devDependencies": {
+    "del": "^2.2.0",
     "git-guppy": "^1.1.0",
     "gulp": "^3.9.0",
     "gulp-filter": "^3.0.1",

+ 4 - 3
packages/ckeditor5-engine/src/config.js

@@ -6,7 +6,8 @@
 'use strict';
 
 import ObservableMixin from './observablemixin.js';
-import utilsLang from './lib/lodash/lang.js';
+import isObject from './lib/lodash/isObject.js';
+import isPlainObject from './lib/lodash/isPlainObject.js';
 import utils from './utils.js';
 
 /**
@@ -69,7 +70,7 @@ export default class Config {
 	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 ) ) {
+		if ( isObject( name ) ) {
 			ObservableMixin.set.apply( this, arguments );
 
 			return;
@@ -95,7 +96,7 @@ export default class Config {
 		}
 
 		// Values set as pure objects will be treated as Config subsets.
-		if ( utilsLang.isPlainObject( value ) ) {
+		if ( 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.

+ 2 - 2
packages/ckeditor5-engine/src/editor.js

@@ -9,7 +9,7 @@ import ObservableMixin from './observablemixin.js';
 import EditorConfig from './editorconfig.js';
 import PluginCollection from './plugincollection.js';
 import CKEditorError from './ckeditorerror.js';
-import langUtils from './lib/lodash/lang.js';
+import isArray from './lib/lodash/isArray.js';
 import utils from './utils.js';
 
 /**
@@ -104,7 +104,7 @@ export default class Editor {
 			let plugins = config.features || [];
 
 			// Handle features passed as a string.
-			if ( !langUtils.isArray( plugins ) ) {
+			if ( !isArray( plugins ) ) {
 				plugins = plugins.split( ',' );
 			}
 

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

@@ -1,28 +0,0 @@
-/**
- * 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;

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

@@ -1,58 +0,0 @@
-/** 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;

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

@@ -1,20 +0,0 @@
-/**
- * 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;

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

@@ -1,32 +0,0 @@
-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;

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

@@ -1,23 +0,0 @@
-/**
- * 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;

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

@@ -1,17 +0,0 @@
-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;

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

@@ -1,39 +0,0 @@
-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;

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

@@ -1,22 +0,0 @@
-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;

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

@@ -1,15 +0,0 @@
-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;

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

@@ -1,20 +0,0 @@
-/**
- * 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;

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

@@ -1,12 +0,0 @@
-/**
- * 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;

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

@@ -1,41 +0,0 @@
-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;

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

@@ -1,14 +0,0 @@
-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;

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

@@ -1,78 +0,0 @@
-/**
- * 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;

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

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

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

@@ -1,34 +0,0 @@
-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;

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

@@ -1,48 +0,0 @@
-/**
- * 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;

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

@@ -1,62 +0,0 @@
-/**
- * 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;

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

@@ -1,38 +0,0 @@
-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;

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

@@ -1,48 +0,0 @@
-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;

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

@@ -1,37 +0,0 @@
-/**
- * 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;

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

@@ -1,79 +0,0 @@
-/**
- * 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;

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

@@ -1,74 +0,0 @@
-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 - 51
packages/ckeditor5-engine/src/lib/lodash/object/assign.js

@@ -1,51 +0,0 @@
-/**
- * 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;

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

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

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

@@ -1,45 +0,0 @@
-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;

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

@@ -1,64 +0,0 @@
-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;

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

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

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

@@ -1,20 +0,0 @@
-/**
- * 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;

+ 4 - 4
packages/ckeditor5-engine/src/observablemixin.js

@@ -7,8 +7,8 @@
 
 import EmitterMixin from './emittermixin.js';
 import CKEditorError from './ckeditorerror.js';
-import utilsObject from './lib/lodash/object.js';
-import utilsLang from './lib/lodash/lang.js';
+import extend from './lib/lodash/extend.js';
+import isObject from './lib/lodash/isObject.js';
 
 const attributesSymbol = Symbol( 'attributes' );
 const boundObservablesSymbol = Symbol( 'boundObservables' );
@@ -40,7 +40,7 @@ const ObservableMixin = {
 	 */
 	set( name, value ) {
 		// If the first parameter is an Object, iterate over its properties.
-		if ( utilsLang.isObject( name ) ) {
+		if ( isObject( name ) ) {
 			Object.keys( name ).forEach( ( attr ) => {
 				this.set( attr, name[ attr ] );
 			}, this );
@@ -629,7 +629,7 @@ function attachBindToListeners( observable, toBindings ) {
 	} );
 }
 
-utilsObject.extend( ObservableMixin, EmitterMixin );
+extend( ObservableMixin, EmitterMixin );
 
 /**
  * Fired when an attribute changed value.

+ 245 - 0
packages/ckeditor5-engine/src/treecontroller/mapper.js

@@ -0,0 +1,245 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import ModelPosition from '../treemodel/position.js';
+import ViewPosition from '../treeview/position.js';
+import ViewText from '../treeview/text.js';
+
+/**
+ * Maps elements and positions between {@link treeView TreeView} and {@link treeModel TreeModel}.
+ *
+ * Mapper use binded elements to find corresponding elements and positions, so, to get proper results,
+ * all Tree Model elements should be {@link treeController.Mapper#bindElements binded}.
+ *
+ * @class treeController.Mapper
+ */
+export default class Mapper {
+	/**
+	 * Creates an instance of the mapper.
+	 *
+	 * @constructor
+	 */
+	constructor() {
+		/**
+		 * Model element to View element mapping.
+		 *
+		 * @private
+		 * @type {WeakMap}
+		 */
+		this._modelToViewMapping = new WeakMap();
+
+		/**
+		 * View element to Model element mapping.
+		 *
+		 * @private
+		 * @type {WeakMap}
+		 */
+		this._viewToModelMapping = new WeakMap();
+	}
+
+	/**
+	 * Marks model and view elements as corresponding. Corresponding elements can be retrieved by using
+	 * the {@link #toModelElement} and {@link #toViewElement} methods.
+	 * The information that elements are bound is also used to translate positions.
+	 *
+	 * @param {treeModel.Element} modelElement Model element.
+	 * @param {treeView.Element} viewElement View element.
+	 */
+	bindElements( modelElement, viewElement ) {
+		this._modelToViewMapping.set( modelElement, viewElement );
+		this._viewToModelMapping.set( viewElement, modelElement );
+	}
+
+	/**
+	 * Gets the corresponding model element.
+	 *
+	 * @param {treeView.Element} viewElement View element.
+	 * @returns {treeModel.Element|null} Corresponding model element or `null` if not found.
+	 */
+	toModelElement( viewElement ) {
+		return this._viewToModelMapping.get( viewElement );
+	}
+
+	/**
+	 * Gets the corresponding view element.
+	 *
+	 * @param {treeModel.Element} modelElement Model element.
+	 * @returns {treeView.Element|null} Corresponding view element or `null` if not found.
+	 */
+	toViewElement( modelElement ) {
+		return this._modelToViewMapping.get( modelElement );
+	}
+
+	/**
+	 * Gets the corresponding model position.
+	 *
+	 * @param {treeView.Position} viewPosition View position.
+	 * @returns {treeModel.Position} Corresponding model position.
+	 */
+	toModelPosition( viewPosition ) {
+		let viewBlock = viewPosition.parent;
+		let modelParent =  this._viewToModelMapping.get( viewBlock );
+
+		while ( !modelParent ) {
+			viewBlock = viewBlock.parent;
+			modelParent = this._viewToModelMapping.get( viewBlock );
+		}
+
+		let modelOffset = this._toModelOffset( viewPosition.parent, viewPosition.offset, viewBlock );
+
+		return ModelPosition.createFromParentAndOffset( modelParent, modelOffset );
+	}
+
+	/**
+	 * Gets the corresponding view position.
+	 *
+	 * @param {treeModel.Position} modelPosition Model position.
+	 * @returns {treeView.Position} Corresponding view position.
+	 */
+	toViewPosition( modelPosition ) {
+		let viewContainer = this._modelToViewMapping.get( modelPosition.parent );
+
+		return this._findPositionIn( viewContainer, modelPosition.offset );
+	}
+
+	/**
+	 * Calculates model offset based on the view position and the block element.
+	 *
+	 * Example:
+	 *
+	 *		<p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, p ) -> 5
+	 *
+	 * Is a sum of:
+	 *
+	 *		<p>foo|<b>bar</b></p> // _toModelOffset( p, 3, p ) -> 3
+	 *		<p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, b ) -> 2
+	 *
+	 * @private
+	 * @param {treeView.Element} viewParent Position parent.
+	 * @param {Number} viewOffset Position offset.
+	 * @param {treeView.Element} viewBlock Block used as a base to calculate offset.
+	 * @returns {Number} Offset in the model.
+	 */
+	_toModelOffset( viewParent, viewOffset, viewBlock ) {
+		if ( viewBlock != viewParent ) {
+			// See example.
+			const offsetToParentBegging = this._toModelOffset( viewParent.parent, viewParent.getIndex(), viewBlock );
+			const offsetInParent = this._toModelOffset( viewParent, viewOffset, viewParent );
+
+			return offsetToParentBegging + offsetInParent;
+		}
+
+		// viewBlock == viewParent, so we need to calculate the offset in the parent element.
+
+		// If the position is a text it is simple ("ba|r" -> 2).
+		if ( viewParent instanceof ViewText ) {
+			return viewOffset;
+		}
+
+		// If the position is in an element we need to sum lengths of siblings ( <b> bar </b> foo | -> 3 + 3 = 6 ).
+		let modelOffset = 0;
+
+		for ( let i = 0; i < viewOffset; i++ ) {
+			modelOffset += this._getModelLength( viewParent.getChild( i ) );
+		}
+
+		return modelOffset;
+	}
+
+	/**
+	 * Gets the length of the view element in the model.
+	 *
+	 * Examples:
+	 *
+	 *		foo          -> 3 // Length of the text is the length of data.
+	 *		<b>foo</b>   -> 3 // Length the element which has no corresponding model element is a length of its children.
+	 *		<p>foo</p>   -> 1 // Length the element which has corresponding model element is always 1.
+	 *
+	 * @private
+	 * @param {treeView.Element} viewNode View node.
+	 * @returns {Number} Length of the node in the tree model.
+	 */
+	_getModelLength( viewNode ) {
+		// If we need mapping to be more flexible this method may fire event, so every feature may define the relation
+		// between length in the model to the length in the view, for example if one element in the model creates two
+		// elements in the view. Now I can not find any example where such feature would be useful.
+		if ( this._viewToModelMapping.has( viewNode ) ) {
+			return 1;
+		} else if ( viewNode instanceof ViewText ) {
+			return viewNode.data.length;
+		} else {
+			let len = 0;
+
+			for ( let child of viewNode.getChildren() ) {
+				len += this._getModelLength( child );
+			}
+
+			return len;
+		}
+	}
+
+	/**
+	 * Finds the position in the view node (or its children) with the expected model offset.
+	 *
+	 * Example:
+	 *
+	 *		<p>fo<b>bar</b>bom</p> -> expected offset: 4
+	 *
+	 *		_findPositionIn( p, 4 ):
+	 *		<p>|fo<b>bar</b>bom</p> -> expected offset: 4, actual offset: 0
+	 *		<p>fo|<b>bar</b>bom</p> -> expected offset: 4, actual offset: 2
+	 *		<p>fo<b>bar</b>|bom</p> -> expected offset: 4, actual offset: 5 -> we are too far
+	 *
+	 *		_findPositionIn( b, 4 - ( 5 - 3 ) ):
+	 *		<p>fo<b>|bar</b>bom</p> -> expected offset: 2, actual offset: 0
+	 *		<p>fo<b>bar|</b>bom</p> -> expected offset: 2, actual offset: 3 -> we are too far
+	 *
+	 *		_findPositionIn( bar, 2 - ( 3 - 3 ) ):
+	 *		We are in the text node so we can simple find the offset.
+	 *		<p>fo<b>ba|r</b>bom</p> -> expected offset: 2, actual offset: 2 -> position found
+	 *
+	 * @private
+	 * @param {treeView.Element} viewParent Tree view element in which we are looking for the position.
+	 * @param {Number} expectedOffset Expected offset.
+	 * @returns {treeVew.Position} Found position.
+	 */
+	_findPositionIn( viewParent, expectedOffset ) {
+		// Last scanned view node.
+		let viewNode;
+		// Length of the last scanned view node.
+		let lastLength = 0;
+
+		let modelOffset = 0;
+		let viewOffset = 0;
+
+		// In the text node it is simple: offset in the model equals offset in the text.
+		if ( viewParent instanceof ViewText ) {
+			return new ViewPosition( viewParent, expectedOffset );
+		}
+
+		// In other cases we add lengths of child nodes to find the proper offset.
+
+		// If it is smaller we add the length.
+		while ( modelOffset < expectedOffset ) {
+			viewNode = viewParent.getChild( viewOffset );
+			lastLength = this._getModelLength( viewNode );
+			modelOffset += lastLength;
+			viewOffset++;
+		}
+
+		// If it equals we found the position.
+		if ( modelOffset == expectedOffset ) {
+			return new ViewPosition( viewParent, viewOffset );
+		}
+		// If it is higher we need to enter last child.
+		else {
+			// ( modelOffset - lastLength ) is the offset to the child we enter,
+			// so we subtract it from the expected offset to fine the offset in the child.
+			return this._findPositionIn( viewNode, expectedOffset - ( modelOffset - lastLength ) );
+		}
+	}
+}

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

@@ -24,7 +24,7 @@ export default class InsertDelta extends Delta {}
  * @memberOf treeModel.Batch
  * @method insert
  * @param {treeModel.Position} position Position of insertion.
- * @param {treeModel.NodesSet} nodes The list of nodes to be inserted.
+ * @param {treeModel.NodeSet} 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 ) {

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

@@ -33,7 +33,7 @@ export default class WeakInsertDelta extends Delta {}
  * @memberOf treeModel.Batch
  * @method weakInsert
  * @param {treeModel.Position} position Position of insertion.
- * @param {treeModel.NodesSet} nodes The list of nodes to be inserted.
+ * @param {treeModel.NodeSet} nodes The list of nodes to be inserted.
  */
 register( 'weakInsert', function( position, nodes ) {
 	const delta = new WeakInsertDelta();

+ 15 - 2
packages/ckeditor5-engine/src/treemodel/element.js

@@ -20,7 +20,7 @@ export default class Element extends Node {
 	 *
 	 * @param {String} name Node name.
 	 * @param {Iterable} attrs Iterable collection of attributes.
-	 * @param {treeModel.NodesSet} children List of nodes to be inserted
+	 * @param {treeModel.NodeSet} 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
 	 */
@@ -77,6 +77,19 @@ export default class Element extends Node {
 		return this._children.indexOf( node );
 	}
 
+	/**
+	 * {@link treeModel.Element#insert Inserts} a child node or a list of child nodes at the end of this node 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 {treeModel.NodeSet} nodes The list of nodes to be inserted.
+	 */
+	appendChildren( nodes ) {
+		this.insertChildren( this.getChildCount(), nodes );
+	}
+
 	/**
 	 * Inserts a list of child nodes on the given index and sets the parent of these nodes to this element.
 	 *
@@ -84,7 +97,7 @@ export default class Element extends Node {
 	 * All attached nodes should be modified using the {@link treeModel.operation.InsertOperation}.
 	 *
 	 * @param {Number} index Position where nodes should be inserted.
-	 * @param {treeModel.NodesSet} nodes The list of nodes to be inserted.
+	 * @param {treeModel.NodeSet} 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 ) {

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/node.js

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import langUtils from '../lib/lodash/lang.js';
+import clone from '../lib/lodash/clone.js';
 import utils from '../utils.js';
 import CKEditorError from '../ckeditorerror.js';
 
@@ -154,7 +154,7 @@ export default class Node {
 	 * @returns {Object} Clone of this object with the parent property replaced with its name.
 	 */
 	toJSON() {
-		const json = langUtils.clone( this );
+		const json = clone( this );
 
 		// Due to circular references we need to remove parent reference.
 		json.parent = this.parent ? this.parent.name : null;

+ 5 - 5
packages/ckeditor5-engine/src/treemodel/nodelist.js

@@ -8,7 +8,7 @@
 import CharacterProxy from './characterproxy.js';
 import Text from './text.js';
 import utils from '../utils.js';
-import langUtils from '../lib/lodash/lang.js';
+import clone from '../lib/lodash/clone.js';
 import CKEditorError from '../ckeditorerror.js';
 
 /**
@@ -21,7 +21,7 @@ import CKEditorError from '../ckeditorerror.js';
  * point to the same nodes.
  * * Iterable collection of above items will be iterated over and all items will be added to the node list.
  *
- * @typedef {treeModel.Element|treeModel.CharacterProxy|treeModel.Text|String|treeModel.NodeList|Iterable} treeModel.NodesSet
+ * @typedef {treeModel.Element|treeModel.CharacterProxy|treeModel.Text|String|treeModel.NodeList|Iterable} treeModel.NodeSet
  */
 
 /**
@@ -60,7 +60,7 @@ class NodeListText extends Text {
 	 * @returns {Object} Clone of this object with the parent property replaced with its name.
 	 */
 	toJSON() {
-		const json = langUtils.clone( this );
+		const json = clone( this );
 
 		json.parent = json.parent ? this.parent.name : null;
 
@@ -106,9 +106,9 @@ export default class NodeList {
 	 *		nodeListA === nodeListB // true
 	 *		nodeListB.length // 3
 	 *
-	 * @see {@link treeModel.NodesSet} for more explanation.
+	 * @see {@link treeModel.NodeSet} for more explanation.
 	 *
-	 * @param {treeModel.NodesSet} nodes List of nodes.
+	 * @param {treeModel.NodeSet} nodes List of nodes.
 	 * @constructor
 	 */
 	constructor( nodes ) {

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

@@ -21,7 +21,7 @@ export default class InsertOperation extends Operation {
 	 * Creates an insert operation.
 	 *
 	 * @param {treeModel.Position} position Position of insertion.
-	 * @param {treeModel.NodesSet} nodes The list of nodes to be inserted.
+	 * @param {treeModel.NodeSet} 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

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/operation/transform.js

@@ -11,7 +11,7 @@ import MoveOperation from './moveoperation.js';
 import NoOperation from './nooperation.js';
 import Position from '../position.js';
 import Range from '../range.js';
-import langUtils from '../../lib/lodash/lang.js';
+import isEqual from '../../lib/lodash/isEqual.js';
 import utils from '../../utils.js';
 
 /**
@@ -108,7 +108,7 @@ const ot = {
 					} )
 				);
 
-				if ( isStrong && !langUtils.isEqual( a.newValue, b.newValue ) ) {
+				if ( isStrong && !isEqual( a.newValue, b.newValue ) ) {
 					// If this operation is more important, we want also want to apply change to the part of the
 					// original range that has already been changed by the other operation. Since that range
 					// got changed we have to update oldAttr.

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/position.js

@@ -7,7 +7,7 @@
 
 import RootElement from './rootelement.js';
 import CKEditorError from '../ckeditorerror.js';
-import arrayUtils from '../lib/lodash/array.js';
+import last from '../lib/lodash/last.js';
 import utils from '../utils.js';
 
 /**
@@ -106,7 +106,7 @@ export default class Position {
 	 * @property {Number} offset
 	 */
 	get offset() {
-		return arrayUtils.last( this.path );
+		return last( this.path );
 	}
 
 	/**

+ 2 - 2
packages/ckeditor5-engine/src/treeview/element.js

@@ -7,7 +7,7 @@
 
 import Node from './node.js';
 import utils from '../utils.js';
-import langUtils from '../lib/lodash/lang.js';
+import isPlainObject from '../lib/lodash/isPlainObject.js';
 
 /**
  * Tree view element.
@@ -46,7 +46,7 @@ export default class Element extends Node {
 		 * @protected
 		 * @property {Map} _attrs
 		 */
-		if ( langUtils.isPlainObject( attrs ) ) {
+		if ( isPlainObject( attrs ) ) {
 			this._attrs = utils.objectToMap( attrs );
 		} else {
 			this._attrs = new Map( attrs );

+ 3 - 3
packages/ckeditor5-engine/src/ui/domemittermixin.js

@@ -7,7 +7,7 @@
 
 import EmitterMixin from '../emittermixin.js';
 import utils from '../utils.js';
-import objectUtils from '../lib/lodash/object.js';
+import extend from '../lib/lodash/extend.js';
 import log from '../log.js';
 
 /**
@@ -33,7 +33,7 @@ class ProxyEmitter {
 	}
 }
 
-objectUtils.extend( ProxyEmitter.prototype, EmitterMixin, {
+extend( ProxyEmitter.prototype, EmitterMixin, {
 	/**
 	 * Collection of native DOM listeners.
 	 *
@@ -157,7 +157,7 @@ objectUtils.extend( ProxyEmitter.prototype, EmitterMixin, {
  * @implements DOMEmitter
  */
 
-const DOMEmitterMixin = objectUtils.extend( {}, EmitterMixin, {
+const DOMEmitterMixin = extend( {}, EmitterMixin, {
 	/**
 	 * Registers a callback function to be executed when an event is fired in a specific Emitter or DOM Node.
 	 * It is backwards compatible with {@link EmitterMixin#listenTo}.

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

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import utilsObject from '../lib/lodash/object.js';
+import extend from '../lib/lodash/extend.js';
 import utils from '../utils.js';
 import ObservableMixin from '../observablemixin.js';
 
@@ -27,7 +27,7 @@ export default class Model {
 	constructor( attributes, properties ) {
 		// Extend this instance with the additional (out of state) properties.
 		if ( properties ) {
-			utilsObject.extend( this, properties );
+			extend( this, properties );
 		}
 
 		// Initialize the attributes.

+ 2 - 2
packages/ckeditor5-engine/src/utils.js

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import langUtils from './lib/lodash/lang.js';
+import isPlainObject from './lib/lodash/isPlainObject.js';
 
 /**
  * An index at which arrays differ. If arrays are same at all indexes, it represents how arrays are related.
@@ -123,7 +123,7 @@ const utils = {
 	 * @returns {Map} Map created from data.
 	 */
 	toMap( data ) {
-		if ( langUtils.isPlainObject( data ) ) {
+		if ( isPlainObject( data ) ) {
 			return utils.objectToMap( data );
 		} else {
 			return new Map( data );

+ 2 - 2
packages/ckeditor5-engine/tests/emittermixin.js

@@ -7,7 +7,7 @@
 
 import EmitterMixin from '/ckeditor5/core/emittermixin.js';
 import EventInfo from '/ckeditor5/core/eventinfo.js';
-import utilsObject from '/ckeditor5/core/lib/lodash/object.js';
+import extend from '/ckeditor5/core/lib/lodash/extend.js';
 
 let emitter, listener;
 
@@ -429,5 +429,5 @@ function refreshListener() {
 }
 
 function getEmitterInstance() {
-	return utilsObject.extend( {}, EmitterMixin );
+	return extend( {}, EmitterMixin );
 }

+ 2 - 2
packages/ckeditor5-engine/tests/lodash.js

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import objectUtils from '/ckeditor5/core/lib/lodash/object.js';
+import extend from '/ckeditor5/core/lib/lodash/extend.js';
 
 describe( 'utils', () => {
 	describe( 'extend()', () => {
@@ -27,7 +27,7 @@ describe( 'utils', () => {
 				d: 2
 			};
 
-			objectUtils.extend( target, ext1, ext2 );
+			extend( target, ext1, ext2 );
 
 			expect( target ).to.have.property( 'a' ).to.equal( 0 );
 			expect( target ).to.have.property( 'b' ).to.equal( 1 );

+ 346 - 0
packages/ckeditor5-engine/tests/treecontroller/mapper.js

@@ -0,0 +1,346 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: treecontroller */
+
+'use strict';
+
+import Mapper from '/ckeditor5/core/treecontroller/mapper.js';
+
+import ModelElement from '/ckeditor5/core/treemodel/element.js';
+import ModelRootElement from '/ckeditor5/core/treemodel/rootelement.js';
+import ModelText from '/ckeditor5/core/treemodel/text.js';
+import ModelPosition from '/ckeditor5/core/treemodel/position.js';
+
+import ViewElement from '/ckeditor5/core/treeview/element.js';
+import ViewText from '/ckeditor5/core/treeview/text.js';
+import ViewPosition from '/ckeditor5/core/treeview/position.js';
+
+describe( 'Mapper', () => {
+	let modelDiv, modelP, modelImg;
+
+	let viewDiv, viewP, viewB, viewI, viewU, viewSup, viewImg;
+	let viewTextB, viewTextO, viewTextM, viewTextX, viewTextY, viewTextZZ, viewTextFOO, viewTextBAR;
+
+	let mapper;
+
+	before( () => {
+		// Tree Model:
+		//
+		// <div>             ---> modelDiv
+		//   ├─ x
+		//   ├─ <p>          ---> modelP
+		//   │   ├─ y
+		//   │   ├─ f {b,i}
+		//   │   ├─ o {b,i}
+		//   │   ├─ o {b,i}
+		//   │   ├─ b
+		//   │   ├─ a
+		//   │   ├─ r
+		//   │   ├─ <img>    ---> modelImg
+		//   │   ├─ b {u}
+		//   │   ├─ o {u,sup}
+		//   │   └─ m {u}
+		//   ├─ z
+		//   └─ z
+		//
+		// Tree View:
+		//
+		// <div>                 ---> viewDiv
+		//   ├─ x                ---> viewTextX
+		//   ├─ <p>              ---> viewP
+		//   │   ├─ y            ---> viewTextY
+		//   │   ├─ <b>          ---> viewB
+		//   │   │   └─ <i>      ---> viewI
+		//   │   │       └─ foo  ---> viewTextFOO
+		//   │   ├─ bar          ---> viewTextBAR
+		//   │   ├─ <img>        ---> viewImg
+		//   │   └─ <u>          ---> viewU
+		//   │       ├─ b        ---> viewTextB
+		//   │       ├─ <sup>    ---> viewSup
+		//   │       │    └─ o   ---> viewTextO
+		//   │       └─ m        ---> viewTextM
+		//   └─ zz               ---> viewTextZZ
+
+		modelImg = new ModelElement( 'img' );
+		modelP = new ModelElement( 'p', {}, [
+			'y',
+			new ModelText( 'foo', { b: true, i: true } ),
+			'bar',
+			modelImg,
+			new ModelText( 'b', { u: true } ),
+			new ModelText( 'o', { u: true, sup: true } ),
+			new ModelText( 'm', { u: true } )
+		] );
+
+		modelDiv = new ModelRootElement();
+		modelDiv.appendChildren( [
+			'x',
+			modelP,
+			'zz'
+		] );
+
+		viewTextB = new ViewText( 'b' );
+		viewTextO = new ViewText( 'o' );
+		viewTextM = new ViewText( 'm' );
+		viewTextX = new ViewText( 'x' );
+		viewTextY = new ViewText( 'y' );
+		viewTextZZ = new ViewText( 'zz' );
+		viewTextFOO = new ViewText( 'foo' );
+		viewTextBAR = new ViewText( 'bar' );
+		viewImg = new ViewElement( 'img' );
+		viewSup = new ViewElement( 'sup', {}, [ viewTextO ] );
+		viewU = new ViewElement( 'u', {}, [ viewTextB, viewSup, viewTextM ] );
+		viewI = new ViewElement( 'i', {}, [ viewTextFOO ] );
+		viewB = new ViewElement( 'b', {}, [ viewI ] );
+		viewP = new ViewElement( 'p', {}, [ viewTextY, viewB, viewTextBAR, viewImg, viewU ] );
+		viewDiv = new ViewElement( 'div', {}, [ viewTextX, viewP, viewTextZZ ] );
+
+		mapper = new Mapper();
+		mapper.bindElements( modelP, viewP );
+		mapper.bindElements( modelDiv, viewDiv );
+		mapper.bindElements( modelImg, viewImg );
+	} );
+
+	describe( 'toModelElement', () => {
+		it( 'should return corresponding model element', () => {
+			expect( mapper.toModelElement( viewP ) ).to.equal( modelP );
+			expect( mapper.toModelElement( viewDiv ) ).to.equal( modelDiv );
+			expect( mapper.toModelElement( viewImg ) ).to.equal( modelImg );
+		} );
+	} );
+
+	describe( 'toViewElement', () => {
+		it( 'should return corresponding view element', () => {
+			expect( mapper.toViewElement( modelP ) ).to.equal( viewP );
+			expect( mapper.toViewElement( modelDiv ) ).to.equal( viewDiv );
+			expect( mapper.toViewElement( modelImg ) ).to.equal( viewImg );
+		} );
+	} );
+
+	describe( 'toModelPosition', () => {
+		function createToModelTest( viewElement, viewOffset, modelElement, modelOffset ) {
+			const viewPosition = new ViewPosition( viewElement, viewOffset );
+			const modelPosition = mapper.toModelPosition( viewPosition );
+			expect( modelPosition.parent ).to.equal( modelElement );
+			expect( modelPosition.offset ).to.equal( modelOffset );
+		}
+
+		it( 'should transform viewDiv 0', () => createToModelTest( viewDiv, 0, modelDiv, 0 ) );
+		it( 'should transform viewDiv 1', () => createToModelTest( viewDiv, 1, modelDiv, 1 ) );
+		it( 'should transform viewDiv 2', () => createToModelTest( viewDiv, 2, modelDiv, 2 ) );
+		it( 'should transform viewDiv 3', () => createToModelTest( viewDiv, 3, modelDiv, 4 ) );
+
+		it( 'should transform viewTextX 0', () => createToModelTest( viewTextX, 0, modelDiv, 0 ) );
+		it( 'should transform viewTextX 1', () => createToModelTest( viewTextX, 1, modelDiv, 1 ) );
+
+		it( 'should transform viewP 0', () => createToModelTest( viewP, 0, modelP, 0 ) );
+		it( 'should transform viewP 1', () => createToModelTest( viewP, 1, modelP, 1 ) );
+		it( 'should transform viewP 2', () => createToModelTest( viewP, 2, modelP, 4 ) );
+		it( 'should transform viewP 3', () => createToModelTest( viewP, 3, modelP, 7 ) );
+		it( 'should transform viewP 4', () => createToModelTest( viewP, 4, modelP, 8 ) );
+		it( 'should transform viewP 5', () => createToModelTest( viewP, 5, modelP, 11 ) );
+
+		it( 'should transform viewTextY 0', () => createToModelTest( viewTextY, 0, modelP, 0 ) );
+		it( 'should transform viewTextY 1', () => createToModelTest( viewTextY, 1, modelP, 1 ) );
+
+		it( 'should transform viewB 0', () => createToModelTest( viewB, 0, modelP, 1 ) );
+		it( 'should transform viewB 1', () => createToModelTest( viewB, 1, modelP, 4 ) );
+
+		it( 'should transform viewI 0', () => createToModelTest( viewI, 0, modelP, 1 ) );
+		it( 'should transform viewI 1', () => createToModelTest( viewI, 1, modelP, 4 ) );
+
+		it( 'should transform viewTextFOO 0', () => createToModelTest( viewTextFOO, 0, modelP, 1 ) );
+		it( 'should transform viewTextFOO 1', () => createToModelTest( viewTextFOO, 1, modelP, 2 ) );
+		it( 'should transform viewTextFOO 2', () => createToModelTest( viewTextFOO, 2, modelP, 3 ) );
+		it( 'should transform viewTextFOO 3', () => createToModelTest( viewTextFOO, 3, modelP, 4 ) );
+
+		it( 'should transform viewTextBAR 0', () => createToModelTest( viewTextBAR, 0, modelP, 4 ) );
+		it( 'should transform viewTextBAR 1', () => createToModelTest( viewTextBAR, 1, modelP, 5 ) );
+		it( 'should transform viewTextBAR 2', () => createToModelTest( viewTextBAR, 2, modelP, 6 ) );
+		it( 'should transform viewTextBAR 3', () => createToModelTest( viewTextBAR, 3, modelP, 7 ) );
+
+		it( 'should transform viewU 0', () => createToModelTest( viewU, 0, modelP, 8 ) );
+		it( 'should transform viewU 1', () => createToModelTest( viewU, 1, modelP, 9 ) );
+		it( 'should transform viewU 2', () => createToModelTest( viewU, 2, modelP, 10 ) );
+		it( 'should transform viewU 3', () => createToModelTest( viewU, 3, modelP, 11 ) );
+
+		it( 'should transform viewTextB 0', () => createToModelTest( viewTextB, 0, modelP, 8 ) );
+		it( 'should transform viewTextB 1', () => createToModelTest( viewTextB, 1, modelP, 9 ) );
+
+		it( 'should transform viewSup 0', () => createToModelTest( viewSup, 0, modelP, 9 ) );
+		it( 'should transform viewSup 1', () => createToModelTest( viewSup, 1, modelP, 10 ) );
+
+		it( 'should transform viewTextO 0', () => createToModelTest( viewTextO, 0, modelP, 9 ) );
+		it( 'should transform viewTextO 1', () => createToModelTest( viewTextO, 1, modelP, 10 ) );
+
+		it( 'should transform viewTextM 0', () => createToModelTest( viewTextM, 0, modelP, 10 ) );
+		it( 'should transform viewTextM 1', () => createToModelTest( viewTextM, 1, modelP, 11 ) );
+
+		it( 'should transform viewTextZZ 0', () => createToModelTest( viewTextZZ, 0, modelDiv, 2 ) );
+		it( 'should transform viewTextZZ 1', () => createToModelTest( viewTextZZ, 1, modelDiv, 3 ) );
+		it( 'should transform viewTextZZ 2', () => createToModelTest( viewTextZZ, 2, modelDiv, 4 ) );
+	} );
+
+	describe( 'toViewPosition', () => {
+		function createToViewTest( modelElement, modelOffset, viewElement, viewOffset ) {
+			const modelPosition = ModelPosition.createFromParentAndOffset( modelElement, modelOffset );
+			const viewPosition = mapper.toViewPosition( modelPosition );
+			expect( viewPosition.parent ).to.equal( viewElement );
+			expect( viewPosition.offset ).to.equal( viewOffset );
+		}
+
+		it( 'should transform modelDiv 0', () => createToViewTest( modelDiv, 0, viewDiv, 0 ) );
+		it( 'should transform modelDiv 1', () => createToViewTest( modelDiv, 1, viewDiv, 1 ) );
+		it( 'should transform modelDiv 2', () => createToViewTest( modelDiv, 2, viewDiv, 2 ) );
+		it( 'should transform modelDiv 3', () => createToViewTest( modelDiv, 3, viewTextZZ, 1 ) );
+		it( 'should transform modelDiv 4', () => createToViewTest( modelDiv, 4, viewDiv, 3 ) );
+
+		it( 'should transform modelP 0', () => createToViewTest( modelP, 0, viewP, 0 ) );
+		it( 'should transform modelP 1', () => createToViewTest( modelP, 1, viewP, 1 ) );
+		it( 'should transform modelP 2', () => createToViewTest( modelP, 2, viewTextFOO, 1 ) );
+		it( 'should transform modelP 3', () => createToViewTest( modelP, 3, viewTextFOO, 2 ) );
+		it( 'should transform modelP 4', () => createToViewTest( modelP, 4, viewP, 2 ) );
+		it( 'should transform modelP 5', () => createToViewTest( modelP, 5, viewTextBAR, 1 ) );
+		it( 'should transform modelP 6', () => createToViewTest( modelP, 6, viewTextBAR, 2 ) );
+		it( 'should transform modelP 7', () => createToViewTest( modelP, 7, viewP, 3 ) );
+		it( 'should transform modelP 8', () => createToViewTest( modelP, 8, viewP, 4 ) );
+		it( 'should transform modelP 9', () => createToViewTest( modelP, 9, viewU, 1 ) );
+		it( 'should transform modelP 10', () => createToViewTest( modelP, 10, viewU, 2 ) );
+		it( 'should transform modelP 11', () => createToViewTest( modelP, 11, viewP, 5 ) );
+	} );
+} );
+
+describe( 'Mapper for widget', () => {
+	let modelDiv, modelWidget, modelImg, modelCaption;
+
+	let viewDiv, viewWidget, viewMask, viewWrapper, viewImg, viewCaption;
+	let viewTextX, viewTextFOO, viewTextZZ, viewTextLABEL;
+
+	let mapper;
+
+	before( () => {
+		// Tree Model:
+		//
+		// <div>                 ---> modelDiv
+		//   ├─ x
+		//   ├─ <widget>         ---> modelWidget
+		//   │   ├─ <img>        ---> modelImg
+		//   │   └─ <caption>    ---> modelCaption
+		//   │       ├─ f
+		//   │       ├─ o
+		//   │       └─ o
+		//   ├─ z
+		//   └─ z
+		//
+		// Tree View:
+		//
+		// <div>                     ---> viewDiv
+		//   ├─ x                    ---> viewTextX
+		//   ├─ <widget>             ---> viewWidget
+		//   │   ├─ <mask>           ---> viewMask
+		//   │   │   └─ label        ---> viewTextLABEL
+		//   │   └─ <wrapper>        ---> viewWrapper
+		//   │       ├─ <img>        ---> viewImg
+		//   │       └─ <caption>    ---> viewCaption
+		//   │           └─ foo      ---> viewTextFOO
+		//   └─ zz                   ---> viewTextZZ
+
+		modelImg = new ModelElement( 'img' );
+		modelCaption = new ModelElement( 'caption', {}, 'foo' );
+		modelWidget = new ModelElement( 'widget', {}, [ modelImg, modelCaption ] );
+		modelDiv = new ModelRootElement();
+		modelDiv.appendChildren( [ 'x', modelWidget, 'zz' ] );
+
+		viewTextX = new ViewText( 'y' );
+		viewTextZZ = new ViewText( 'zz' );
+		viewTextFOO = new ViewText( 'foo' );
+		viewTextLABEL = new ViewText( 'label' );
+
+		viewImg = new ViewElement( 'img' );
+		viewMask = new ViewElement( 'mask', {}, [ viewTextLABEL ] );
+		viewCaption = new ViewElement( 'caption', {}, [ viewTextFOO ] );
+		viewWrapper = new ViewElement( 'wrapper', {}, [ viewImg, viewCaption ] );
+		viewWidget = new ViewElement( 'widget', [ viewMask, viewWrapper ] );
+		viewDiv = new ViewElement( 'div', {}, [ viewTextX, viewWidget, viewTextZZ ] );
+
+		mapper = new Mapper();
+		mapper.bindElements( modelDiv, viewDiv );
+		mapper.bindElements( modelWidget, viewWidget );
+		mapper.bindElements( modelImg, viewImg );
+		mapper.bindElements( modelCaption, viewCaption );
+	} );
+
+	describe( 'toModelElement', () => {
+		it( 'should return corresponding model element', () => {
+			expect( mapper.toModelElement( viewDiv ) ).to.equal( modelDiv );
+			expect( mapper.toModelElement( viewWidget ) ).to.equal( modelWidget );
+			expect( mapper.toModelElement( viewImg ) ).to.equal( modelImg );
+			expect( mapper.toModelElement( viewCaption ) ).to.equal( modelCaption );
+		} );
+	} );
+
+	describe( 'toViewElement', () => {
+		it( 'should return corresponding view element', () => {
+			expect( mapper.toViewElement( modelDiv ) ).to.equal( viewDiv );
+			expect( mapper.toViewElement( modelWidget ) ).to.equal( viewWidget );
+			expect( mapper.toViewElement( modelImg ) ).to.equal( viewImg );
+			expect( mapper.toViewElement( modelCaption ) ).to.equal( viewCaption );
+		} );
+	} );
+
+	describe( 'toModelPosition', () => {
+		function createToModelTest( viewElement, viewOffset, modelElement, modelOffset ) {
+			const viewPosition = new ViewPosition( viewElement, viewOffset );
+			const modelPosition = mapper.toModelPosition( viewPosition );
+			expect( modelPosition.parent ).to.equal( modelElement );
+			expect( modelPosition.offset ).to.equal( modelOffset );
+		}
+
+		it( 'should transform viewDiv 0', () => createToModelTest( viewDiv, 0, modelDiv, 0 ) );
+		it( 'should transform viewDiv 1', () => createToModelTest( viewDiv, 1, modelDiv, 1 ) );
+		it( 'should transform viewDiv 2', () => createToModelTest( viewDiv, 2, modelDiv, 2 ) );
+		it( 'should transform viewDiv 3', () => createToModelTest( viewDiv, 3, modelDiv, 4 ) );
+
+		it( 'should transform viewTextX 0', () => createToModelTest( viewTextX, 0, modelDiv, 0 ) );
+		it( 'should transform viewTextX 1', () => createToModelTest( viewTextX, 1, modelDiv, 1 ) );
+
+		it( 'should transform viewTextZZ 0', () => createToModelTest( viewTextZZ, 0, modelDiv, 2 ) );
+		it( 'should transform viewTextZZ 1', () => createToModelTest( viewTextZZ, 1, modelDiv, 3 ) );
+		it( 'should transform viewTextZZ 2', () => createToModelTest( viewTextZZ, 2, modelDiv, 4 ) );
+
+		it( 'should transform viewImg 0', () => createToModelTest( viewImg, 0, modelImg, 0 ) );
+
+		it( 'should transform viewCaption 0', () => createToModelTest( viewCaption, 0, modelCaption, 0 ) );
+		it( 'should transform viewCaption 1', () => createToModelTest( viewCaption, 1, modelCaption, 3 ) );
+
+		it( 'should transform viewTextFOO 0', () => createToModelTest( viewTextFOO, 0, modelCaption, 0 ) );
+		it( 'should transform viewTextFOO 1', () => createToModelTest( viewTextFOO, 1, modelCaption, 1 ) );
+		it( 'should transform viewTextFOO 2', () => createToModelTest( viewTextFOO, 2, modelCaption, 2 ) );
+		it( 'should transform viewTextFOO 3', () => createToModelTest( viewTextFOO, 3, modelCaption, 3 ) );
+	} );
+
+	describe( 'toViewPosition', () => {
+		function createToViewTest( modelElement, modelOffset, viewElement, viewOffset ) {
+			const modelPosition = ModelPosition.createFromParentAndOffset( modelElement, modelOffset );
+			const viewPosition = mapper.toViewPosition( modelPosition );
+			expect( viewPosition.parent ).to.equal( viewElement );
+			expect( viewPosition.offset ).to.equal( viewOffset );
+		}
+
+		it( 'should transform modelDiv 0', () => createToViewTest( modelDiv, 0, viewDiv, 0 ) );
+		it( 'should transform modelDiv 1', () => createToViewTest( modelDiv, 1, viewDiv, 1 ) );
+		it( 'should transform modelDiv 2', () => createToViewTest( modelDiv, 2, viewDiv, 2 ) );
+		it( 'should transform modelDiv 3', () => createToViewTest( modelDiv, 3, viewTextZZ, 1 ) );
+		it( 'should transform modelDiv 4', () => createToViewTest( modelDiv, 4, viewDiv, 3 ) );
+
+		it( 'should transform modelImg 0', () => createToViewTest( modelImg, 0, viewImg, 0 ) );
+
+		it( 'should transform modelCaption 0', () => createToViewTest( modelCaption, 0, viewCaption, 0 ) );
+		it( 'should transform modelCaption 1', () => createToViewTest( modelCaption, 1, viewTextFOO, 1 ) );
+		it( 'should transform modelCaption 2', () => createToViewTest( modelCaption, 2, viewTextFOO, 2 ) );
+		it( 'should transform modelCaption 3', () => createToViewTest( modelCaption, 3, viewCaption, 1 ) );
+	} );
+} );

+ 15 - 0
packages/ckeditor5-engine/tests/treemodel/element.js

@@ -62,6 +62,21 @@ describe( 'Element', () => {
 		} );
 	} );
 
+	describe( 'appendChildren', () => {
+		it( 'should add children to the end of the element', () => {
+			let element = new Element( 'elem', [], [ 'xy' ] );
+			element.appendChildren( 'foo' );
+
+			expect( element ).to.have.property( 'name' ).that.equals( 'elem' );
+			expect( element.getChildCount() ).to.equal( 5 );
+			expect( element.getChild( 0 ) ).to.have.property( 'character' ).that.equals( 'x' );
+			expect( element.getChild( 1 ) ).to.have.property( 'character' ).that.equals( 'y' );
+			expect( element.getChild( 2 ) ).to.have.property( 'character' ).that.equals( 'f' );
+			expect( element.getChild( 3 ) ).to.have.property( 'character' ).that.equals( 'o' );
+			expect( element.getChild( 4 ) ).to.have.property( 'character' ).that.equals( 'o' );
+		} );
+	} );
+
 	describe( 'removeChildren', () => {
 		it( 'should remove children from the element and return them as a NodeList', () => {
 			let element = new Element( 'elem', [], [ 'foobar' ] );

+ 3 - 3
packages/ckeditor5-engine/tests/ui/domemittermixin.js

@@ -9,7 +9,7 @@
 'use strict';
 
 import testUtils from '/tests/_utils/utils.js';
-import objectUtils from '/ckeditor5/core/lib/lodash/object.js';
+import extend from '/ckeditor5/core/lib/lodash/extend.js';
 import DOMEmitterMixin from '/ckeditor5/core/ui/domemittermixin.js';
 import EmitterMixin from '/ckeditor5/core/emittermixin.js';
 
@@ -17,8 +17,8 @@ let emitter, domEmitter, node;
 
 testUtils.createSinonSandbox();
 
-let getEmitterInstance = () => objectUtils.extend( {}, EmitterMixin );
-let getDOMEmitterInstance = () => objectUtils.extend( {}, DOMEmitterMixin );
+let getEmitterInstance = () => extend( {}, EmitterMixin );
+let getDOMEmitterInstance = () => extend( {}, DOMEmitterMixin );
 let getDOMNodeInstance = () => document.createElement( 'div' );
 
 function updateEmitterInstance() {