substance
Version:
Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing systems.
1,937 lines (1,740 loc) • 3.04 MB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';
/*
Implements Substance ChangeStore API. This is just a dumb store.
No integrity checks are made, as this is the task of DocumentEngine
*/
function forEach(iteratee, func) {
if (!iteratee) return
if (iteratee.constructor.prototype.forEach) {
iteratee.forEach(func);
} else {
Object.keys(iteratee).forEach(function(key) {
return func(iteratee[key], key)
});
}
}
function isObject(val) {
const type = typeof val;
return Boolean(val) && (type === 'object' || type === 'function')
}
/**
Event support.
*/
class EventEmitter {
/**
Emit an event.
@param {String} event
@param ...arguments
@return true if a listener was notified, false otherwise.
*/
emit(event) {
if (event in this.__events__) {
// console.log("Emitting event %s (%d listeners) on", event, this.__events__[event].length, this)
// Clone the list of bindings so that handlers can remove or add handlers during the call.
var bindings = this.__events__[event].slice();
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0, len = bindings.length; i < len; i++) {
var binding = bindings[i];
// console.log("- triggering %s on %s", event, binding.context.constructor.name)
binding.method.apply(binding.context, args);
}
return true
}
return false
}
/**
Subscribe a listener to an event.
Optionally, a `priority` can be provided to control the order
of all bindings. The default priority is 0. All listeners with the
same priority remain in order of registration.
A lower priority will make the listener be called later, a higher
priority earlier.
@param {String} event
@param {Function} method
@param {Object} context
@param {Object} options
*/
on(event, method, context, options) {
var priority = 0;
if (arguments.length === 4) {
priority = options.priority || priority;
}
_on.call(this, event, method, context, priority);
this.__events__[event].sort(byPriorityDescending);
}
/**
Unsubscrive a listener from an event.
@param {String} event
@param {Function} method
@param {Object} context
@param {Object} options
*/
off(event, method, context) { // eslint-disable-line no-unused-vars
if (arguments.length === 1 && isObject(arguments[0])) {
_disconnect.call(this, arguments[0]);
} else {
_off.apply(this, arguments);
}
}
_debugEvents() {
/* eslint-disable no-console */
console.log('### EventEmitter: ', this);
forEach(this.__events__, function(handlers, name) {
console.log("- %s listeners for %s: ", handlers.length, name, handlers);
});
/* eslint-enable no-console */
}
get __events__() {
if (!this.___events___) {
this.___events___ = {};
}
return this.___events___
}
}
// sort descending as a listener with higher priority should be
// called earlier
function byPriorityDescending(a, b) {
return b.priority - a.priority
}
/*
Internal implementation for registering a listener.
@param {String} event
@param {Function} method
@param {Object} context
*/
function _on(event, method, context, priority) {
/* eslint-disable no-invalid-this */
var bindings;
validateMethod( method, context );
if (this.__events__.hasOwnProperty(event)) {
bindings = this.__events__[event];
} else {
// Auto-initialize bindings list
bindings = this.__events__[event] = [];
}
// Add binding
bindings.push({
method: method,
context: context || null,
priority: priority
});
return this
/*eslint-enable no-invalid-this */
}
/*
Remove a listener.
@param {String} event
@param {Function} method
@param {Object} context
*/
function _off(event, method, context) {
/* eslint-disable no-invalid-this */
var i, bindings;
if ( arguments.length === 1 ) {
// Remove all bindings for event
delete this.__events__[event];
return this
}
validateMethod( method, context );
if ( !( event in this.__events__ ) || !this.__events__[event].length ) {
// No matching bindings
return this
}
// Default to null context
if ( arguments.length < 3 ) {
context = null;
}
// Remove matching handlers
bindings = this.__events__[event];
i = bindings.length;
while ( i-- ) {
if ( bindings[i].method === method && bindings[i].context === context ) {
bindings.splice( i, 1 );
}
}
// Cleanup if now empty
if ( bindings.length === 0 ) {
delete this.__events__[event];
}
return this
/* eslint-enable no-invalid-this */
}
/**
Internal implementation of disconnect.
*/
function _disconnect(context) {
/* eslint-disable no-invalid-this */
// Remove all connections to the context
forEach(this.__events__, function(bindings, event) {
for (var i = bindings.length-1; i>=0; i--) {
// bindings[i] may have been removed by the previous steps
// so check it still exists
if (bindings[i] && bindings[i].context === context) {
_off.call(this, event, bindings[i].method, context);
}
}
}.bind(this));
return this
/* eslint-enable no-invalid-this */
}
function validateMethod(method, context) {
// Validate method and context
if (typeof method === 'string') {
// Validate method
if (context === undefined || context === null) {
throw new Error( 'Method name "' + method + '" has no context.' )
}
if (!(method in context)) {
// Technically the method does not need to exist yet: it could be
// added before call time. But this probably signals a typo.
throw new Error( 'Method not found: "' + method + '"' )
}
if (typeof context[method] !== 'function') {
// Technically the property could be replaced by a function before
// call time. But this probably signals a typo.
throw new Error( 'Property "' + method + '" is not a function' )
}
} else if (typeof method !== 'function') {
throw new Error( 'Invalid callback. Function or method name expected.' )
}
}
/*
Custom error object for all Substance related errors
@example
```js
import Err from 'substance/util/SubstanceError'
throw new Err('Document.SelectionUpdateError', {message: 'Could not update selection.'})
```
For better inspection allows you to pass a cause (the error that caused the error).
That way we can attach context information on each level and we can also ensure
security, by not passing the cause-chain to the client.
Resources:
http://www.bennadel.com/blog/2828-creating-custom-error-objects-in-node-js-with-error-capturestacktrace.htm
https://gist.github.com/justmoon/15511f92e5216fa2624b
https://github.com/davepacheco/node-verror/blob/master/lib/verror.js
*/
function extend(...args) {
return Object.assign(...args)
}
function map(iteratee, func) {
if (!iteratee) return []
if (!func) func = function(item) { return item };
if (Array.isArray(iteratee)) {
return iteratee.map(func)
} else if (typeof iteratee.length !== 'undefined') {
let l = iteratee.length;
let result = [];
for (var i = 0; i < l; i++) {
result.push(func(iteratee[i], i));
}
return result
} else {
return Object.keys(iteratee).map(function(key) {
return func(iteratee[key], key)
})
}
}
function isPlainObject(o) {
return Boolean(o) && o.constructor === {}.constructor
}
function isArray(a) {
return Array.isArray(a)
}
function isEqual(a, b) {
if (a === b) return true
if (isArray(a) && isArray(b)) {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (!isEqual(a[i], b[i])) return false
}
return true
}
if (isPlainObject(a) && isPlainObject(b)) {
let akeys = Object.keys(a).sort();
let bkeys = Object.keys(b).sort();
if (!isEqual(akeys, bkeys)) return false
for (let i = 0; i < akeys.length; i++) {
let key = akeys[i];
if (!isEqual(a[key], b[key])) return false
}
return true
}
return false
}
function clone(val) {
if (isArray(val)) {
return val.slice(0)
}
if (isObject(val)) {
return Object.assign({}, val)
}
// we do not clone primitives
// TODO: is that ok?
return val
}
function cloneDeep(val) {
if (isArray(val)) {
return _cloneArrayDeep(val);
}
if (isObject(val)) {
return _cloneObjectDeep(val)
}
// primitives don't need to be cloned
// TODO: is that ok?
return val
}
function _cloneObjectDeep(obj) {
let res = {};
forEach(obj, (val, key) => {
res[key] = cloneDeep(val);
});
return res
}
function _cloneArrayDeep(arr) {
return arr.map(cloneDeep)
}
/*!
Math.uuid.js (v1.4)
http://www.broofa.com
mailto:robert@broofa.com
Copyright (c) 2010 Robert Kieffer
Dual licensed under the MIT and GPL licenses.
*/
/**
* Generates a unique id.
*
* @param {String} [prefix] if provided the UUID will be prefixed.
* @param {Number} [len] if provided a UUID with given length will be created.
* @return A generated uuid.
*/
function uuid(prefix, len) {
if (prefix && prefix[prefix.length-1] !== "-") {
prefix = prefix.concat("-");
}
var chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [];
var radix = 16;
var idx;
len = len || 32;
if (len) {
// Compact form
for (idx = 0; idx < len; idx++) uuid[idx] = chars[0 | Math.random()*radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (idx = 0; idx < 36; idx++) {
if (!uuid[idx]) {
r = 0 | Math.random()*16;
uuid[idx] = chars[(idx === 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return (prefix ? prefix : "") + uuid.join('');
}
function isNumber(n) {
return typeof n === 'number'
}
function isNil(o) {
return o === null || o === undefined
}
function isString(s) {
return typeof s === 'string'
}
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @type {Function}
* @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(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var isArray$1 = Array.isArray;
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @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(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject$1(value) {
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
/** `Object#toString` result references. */
var funcTag = '[object Function]';
var genTag = '[object GeneratorFunction]';
/** Used for built-in method references. */
var objectProto$1 = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString = objectProto$1.toString;
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @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 Safari 8 which returns 'object' for typed array and weak map constructors,
// and PhantomJS 1.9 which returns 'function' for `NodeList` instances.
var tag = isObject$1(value) ? objectToString.call(value) : '';
return tag == funcTag || tag == genTag;
}
/**
* Checks if `value` is a host object in IE < 9.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
*/
function isHostObject(value) {
// Many host objects are `Object` objects that can coerce to strings
// despite having improperly defined `toString` methods.
var result = false;
if (value != null && typeof value.toString != 'function') {
try {
result = !!(value + '');
} catch (e) {}
}
return result;
}
/**
* Checks if `value` is a global object.
*
* @private
* @param {*} value The value to check.
* @returns {null|Object} Returns `value` if it's a global object, else `null`.
*/
function checkGlobal(value) {
return (value && value.Object === Object) ? value : null;
}
/** Detect free variable `global` from Node.js. */
var freeGlobal = checkGlobal(typeof global == 'object' && global);
/** Detect free variable `self`. */
var freeSelf = checkGlobal(typeof self == 'object' && self);
/** Detect `this` as the global object. */
var thisGlobal = checkGlobal(typeof undefined == 'object' && undefined);
/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || thisGlobal || Function('return this')();
/** Used to detect overreaching core-js shims. */
var coreJsData = root['__core-js_shared__'];
/** Used to detect methods masquerading as native. */
var maskSrcKey = (function() {
var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
return uid ? ('Symbol(src)_1.' + uid) : '';
}());
/**
* Checks if `func` has its source masked.
*
* @private
* @param {Function} func The function to check.
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
*/
function isMasked(func) {
return !!maskSrcKey && (maskSrcKey in func);
}
/** Used to resolve the decompiled source of functions. */
var funcToString$1 = Function.prototype.toString;
/**
* Converts `func` to its source code.
*
* @private
* @param {Function} func The function to process.
* @returns {string} Returns the source code.
*/
function toSource(func) {
if (func != null) {
try {
return funcToString$1.call(func);
} catch (e) {}
try {
return (func + '');
} catch (e) {}
}
return '';
}
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns).
*/
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
/** Used to detect host constructors (Safari). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = 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('^' +
funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);
/**
* The base implementation of `_.isNative` without bad shim checks.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a native function,
* else `false`.
*/
function baseIsNative(value) {
if (!isObject$1(value) || isMasked(value)) {
return false;
}
var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
return pattern.test(toSource(value));
}
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/
function getValue(object, key) {
return object == null ? undefined : object[key];
}
/**
* 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 = getValue(object, key);
return baseIsNative(value) ? value : undefined;
}
/* Built-in method references that are verified to be native. */
var nativeCreate = getNative(Object, 'create');
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/
function hashClear() {
this.__data__ = nativeCreate ? nativeCreate(null) : {};
}
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function hashDelete(key) {
return this.has(key) && delete this.__data__[key];
}
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';
/** Used for built-in method references. */
var objectProto$2 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto$2.hasOwnProperty;
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function hashGet(key) {
var data = this.__data__;
if (nativeCreate) {
var result = data[key];
return result === HASH_UNDEFINED ? undefined : result;
}
return hasOwnProperty$1.call(data, key) ? data[key] : undefined;
}
/** Used for built-in method references. */
var objectProto$3 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$2 = objectProto$3.hasOwnProperty;
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function hashHas(key) {
var data = this.__data__;
return nativeCreate ? data[key] !== undefined : hasOwnProperty$2.call(data, key);
}
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED$1 = '__lodash_hash_undefined__';
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/
function hashSet(key, value) {
var data = this.__data__;
data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED$1 : value;
return this;
}
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Hash(entries) {
var index = -1,
length = entries ? entries.length : 0;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
// Add methods to `Hash`.
Hash.prototype.clear = hashClear;
Hash.prototype['delete'] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/
function listCacheClear() {
this.__data__ = [];
}
/**
* Performs a
* [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
* comparison between two values to determine if they are equivalent.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'user': 'fred' };
* var other = { 'user': 'fred' };
*
* _.eq(object, object);
* // => true
*
* _.eq(object, other);
* // => false
*
* _.eq('a', 'a');
* // => true
*
* _.eq('a', Object('a'));
* // => false
*
* _.eq(NaN, NaN);
* // => true
*/
function eq(value, other) {
return value === other || (value !== value && other !== other);
}
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to search.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function assocIndexOf(array, key) {
var length = array.length;
while (length--) {
if (eq(array[length][0], key)) {
return length;
}
}
return -1;
}
/** Used for built-in method references. */
var arrayProto = Array.prototype;
/** Built-in value references. */
var splice = arrayProto.splice;
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function listCacheDelete(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
return false;
}
var lastIndex = data.length - 1;
if (index == lastIndex) {
data.pop();
} else {
splice.call(data, index, 1);
}
return true;
}
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function listCacheGet(key) {
var data = this.__data__,
index = assocIndexOf(data, key);
return index < 0 ? undefined : data[index][1];
}
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function listCacheHas(key) {
return assocIndexOf(this.__data__, key) > -1;
}
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/
function listCacheSet(key, value) {
var data = this.__data__,
index = assocIndexOf(data, key);
if (index < 0) {
data.push([key, value]);
} else {
data[index][1] = value;
}
return this;
}
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function ListCache(entries) {
var index = -1,
length = entries ? entries.length : 0;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype['delete'] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;
/* Built-in method references that are verified to be native. */
var Map$1 = getNative(root, 'Map');
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/
function mapCacheClear() {
this.__data__ = {
'hash': new Hash,
'map': new (Map$1 || ListCache),
'string': new Hash
};
}
/**
* Checks if `value` is suitable for use as unique object key.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
*/
function isKeyable(value) {
var type = typeof value;
return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
? (value !== '__proto__')
: (value === null);
}
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/
function getMapData(map, key) {
var data = map.__data__;
return isKeyable(key)
? data[typeof key == 'string' ? 'string' : 'hash']
: data.map;
}
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function mapCacheDelete(key) {
return getMapData(this, key)['delete'](key);
}
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function mapCacheGet(key) {
return getMapData(this, key).get(key);
}
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function mapCacheHas(key) {
return getMapData(this, key).has(key);
}
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/
function mapCacheSet(key, value) {
getMapData(this, key).set(key, value);
return this;
}
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function MapCache(entries) {
var index = -1,
length = entries ? entries.length : 0;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;
MapCache.prototype['delete'] = mapCacheDelete;
MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapCacheSet;
/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided, it determines the cache key for storing the result based on the
* arguments provided to the memoized function. By default, the first argument
* provided to the memoized function is used as the map cache key. The `func`
* is invoked with the `this` binding of the memoized function.
*
* **Note:** The cache is exposed as the `cache` property on the memoized
* function. Its creation may be customized by replacing the `_.memoize.Cache`
* constructor with one whose instances implement the
* [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
* method interface of `delete`, `get`, `has`, and `set`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] The function to resolve the cache key.
* @returns {Function} Returns the new memoized function.
* @example
*
* var object = { 'a': 1, 'b': 2 };
* var other = { 'c': 3, 'd': 4 };
*
* var values = _.memoize(_.values);
* values(object);
* // => [1, 2]
*
* values(other);
* // => [3, 4]
*
* object.a = 2;
* values(object);
* // => [1, 2]
*
* // Modify the result cache.
* values.cache.set(object, ['a', 'b']);
* values(object);
* // => ['a', 'b']
*
* // Replace `_.memoize.Cache`.
* _.memoize.Cache = WeakMap;
*/
function memoize(func, resolver) {
if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
throw new TypeError(FUNC_ERROR_TEXT);
}
var memoized = function() {
var args = arguments,
key = resolver ? resolver.apply(this, args) : args[0],
cache = memoized.cache;
if (cache.has(key)) {
return cache.get(key);
}
var result = func.apply(this, args);
memoized.cache = cache.set(key, result);
return result;
};
memoized.cache = new (memoize.Cache || MapCache);
return memoized;
}
// Assign cache to `_.memoize`.
memoize.Cache = MapCache;
/** Built-in value references. */
var Symbol$1 = root.Symbol;
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return !!value && typeof value == 'object';
}
/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';
/** Used for built-in method references. */
var objectProto$4 = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString$1 = objectProto$4.toString;
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is correctly classified,
* else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value) {
return typeof value == 'symbol' ||
(isObjectLike(value) && objectToString$1.call(value) == symbolTag);
}
/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0;
/** Used to convert symbols to primitives and strings. */
var symbolProto = Symbol$1 ? Symbol$1.prototype : undefined;
var symbolToString = symbolProto ? symbolProto.toString : undefined;
/**
* The base implementation of `_.toString` which doesn't convert nullish
* values to empty strings.
*
* @private
* @param {*} value The value to process.
* @returns {string} Returns the string.
*/
function baseToString(value) {
// Exit early for strings to avoid a performance hit in some environments.
if (typeof value == 'string') {
return value;
}
if (isSymbol(value)) {
return symbolToString ? symbolToString.call(value) : '';
}
var result = (value + '');
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
}
/**
* Converts `value` to a string. An empty string is returned for `null`
* and `undefined` values. The sign of `-0` is preserved.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {string} Returns the string.
* @example
*
* _.toString(null);
* // => ''
*
* _.toString(-0);
* // => '-0'
*
* _.toString([1, 2, 3]);
* // => '1,2,3'
*/
function toString(value) {
return value == null ? '' : baseToString(value);
}
/** Used to match property names within property paths. */
var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g;
/** Used to match backslashes in property paths. */
var reEscapeChar = /\\(\\)?/g;
/**
* Converts `string` to a property path array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the property path array.
*/
var stringToPath = memoize(function(string) {
var result = [];
toString(string).replace(rePropName, function(match, number, quote, string) {
result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
});
return result;
});
/**
* Casts `value` to a path array if it's not one.
*
* @private
* @param {*} value The value to inspect.
* @returns {Array} Returns the cast property path array.
*/
function castPath(value) {
return isArray$1(value) ? value : stringToPath(value);
}
/** Used to match property names within property paths. */
var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/;
var reIsPlainProp = /^\w*$/;
/**
* Checks if `value` is a property name and not a property path.
*
* @private
* @param {*} value The value to check.
* @param {Object} [object] The object to query keys on.
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
*/
function isKey(value, object) {
if (isArray$1(value)) {
return false;
}
var type = typeof value;
if (type == 'number' || type == 'symbol' || type == 'boolean' ||
value == null || isSymbol(value)) {
return true;
}
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
(object != null && value in Object(object));
}
/** Used as references for various `Number` constants. */
var INFINITY$1 = 1 / 0;
/**
* Converts `value` to a string key if it's not a string or symbol.
*
* @private
* @param {*} value The value to inspect.
* @returns {string|symbol} Returns the key.
*/
function toKey(value) {
if (typeof value == 'string' || isSymbol(value)) {
return value;
}
var result = (value + '');
return (result == '0' && (1 / value) == -INFINITY$1) ? '-0' : result;
}
/**
* The base implementation of `_.get` without support for default values.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @returns {*} Returns the resolved value.
*/
function baseGet(object, path) {
path = isKey(path, object) ? [path] : castPath(path);
var index = 0,
length = path.length;
while (object != null && index < length) {
object = object[toKey(path[index++])];
}
return (index && index == length) ? object : undefined;
}
/**
* Gets the value at `path` of `object`. If the resolved value is
* `undefined`, the `defaultValue` is used in its place.
*
* @static
* @memberOf _
* @since 3.7.0
* @category Object
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @param {*} [defaultValue] The value returned for `undefined` resolved values.
* @returns {*} Returns the resolved value.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
*
* _.get(object, 'a[0].b.c');
* // => 3
*
* _.get(object, ['a', '0', 'b', 'c']);
* // => 3
*
* _.get(object, 'a.b.c', 'default');
* // => 'default'
*/
function get(object, path, defaultValue) {
var result = object == null ? undefined : baseGet(object, path);
return result === undefined ? defaultValue : result;
}
/** Used for built-in method references. */
var objectProto$5 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$3 = objectProto$5.hasOwnProperty;
/**
* Assigns `value` to `key` of `object` if the existing value is not equivalent
* using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
* for equality comparisons.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function assignValue(object, key, value) {
var objValue = object[key];
if (!(hasOwnProperty$3.call(object, key) && eq(objValue, value)) ||
(value === undefined && !(key in object))) {
object[key] = value;
}
}
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;
/**
* 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) {
length = length == null ? MAX_SAFE_INTEGER : length;
return !!length &&
(typeof value == 'number' || reIsUint.test(value)) &&
(value > -1 && value % 1 == 0 && value < length);
}
/**
* The base implementation of `_.set`.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @param {Function} [customizer] The function to customize path creation.
* @returns {Object} Returns `object`.
*/
function baseSet(object, path, value, customizer) {
path = isKey(path, object) ? [path] : castPath(path);
var index = -1,
length = path.length,
lastIndex = length - 1,
nested = object;
while (nested != null && ++index < length) {
var key = toKey(path[index]);
if (isObject$1(nested)) {
var newValue = value;
if (index != lastIndex) {
var objValue = nested[key];
newValue = customizer ? customizer(objValue, key, nested) : undefined;
if (newValue === undefined) {
newValue = objValue == null
? (isIndex(path[index + 1]) ? [] : {})
: objValue;
}
}
assignValue(nested, key, newValue);
}
nested = nested[key];
}
return object;
}
/**
* This method is like `_.set` except that it accepts `customizer` which is
* invoked to produce the objects of `path`. If `customizer` returns `undefined`
* path creation is handled by the method instead. The `customizer` is invoked
* with three arguments: (nsValue, key, nsObject).
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Object
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @param {Function} [customizer] The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* var object = {};
*
* _.setWith(object, '[0][1]', 'a', Object);
* // => { '0': { '1': 'a' } }
*/
function setWith(object, path, value, customizer) {
customizer = typeof customizer == 'function' ? customizer : undefined;
return object == null ? object : baseSet(object, path, value, customizer);
}
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeGetPrototype = Object.getPrototypeOf;
/**
* Gets the `[[Prototype]]` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {null|Object} Returns the `[[Prototype]]`.
*/
function getPrototype(value) {
return nativeGetPrototype(Object(value));
}
/** Used for built-in method references. */
var objectProto$6 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$4 = objectProto$6.hasOwnProperty;
/**
* The base implementation of `_.has` without support for deep paths.
*
* @private
* @param {Object} [object] The object to query.
* @param {Array|string} key The key to check.
* @returns {boolean} Returns `true` if `key` exists, else `false`.
*/
function baseHas(object, key) {
// Avoid a bug in IE 10-11 where objects with a [[Prototype]] of `null`,
// that are composed entirely of index properties, return `false` for
// `hasOwnProperty` checks of them.
return object != null &&
(hasOwnProperty$4.call(object, key) ||
(typeof object == 'object' && key in object && getPrototype(object) === null));
}
/**
* Gets the last element of `array`.
*
* @static
* @memberOf _
* @since 0.1.0
* @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;
}
/**
* The base implementation of `_.slice` without an iteratee call guard.
*
* @private
* @param {Array} array The array to slice.
* @param {number} [start=0] The start position.
* @param {number} [end=array.length] The end position.
* @returns {Array} Returns the slice of `array`.
*/
function baseSlice(array, start, end) {
var index = -1,
length = array.length;
if (start < 0) {
start = -start > length ? 0 : (length + start);
}
end = end > length ? length : end;
if (end < 0) {
end += length;
}
length = start > end ? 0 : ((end - start) >>> 0);
start >>>= 0;
var result = Array(length);
while (++index < length) {
result[index] = array[index + start];
}
return result;
}
/**
* Gets the parent value at `path` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Array} path The path to get the parent value of.
* @returns {*} Returns the parent value.
*/
function parent(object, path) {
return path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
}
/**
* The base implementation of `_.unset`.
*
* @private
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to unset.
* @returns {boolean} Returns `true` if the property is deleted, else `false`.
*/
function baseUnset(object, path) {
path = isKey(path, object) ? [path] : castPath(path);
object = parent(object, path);
var key = toKey(last(path));
return !(object != null && baseHas(object, key)) || delete object[key];
}
/**
* Removes the property at `path` of `object`.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Object
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to unset.
* @returns {boolean} Returns `true` if the property is deleted, else `false`.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 7 } }] };
* _.unset(object, 'a[0].b.c');
* // => true
*
* console.log(object);
* // => { 'a': [{ 'b': {} }] };
*
* _.unset(object, ['a', '0', 'b', 'c']);
* // => true
*
* console.log(object);
* // => { 'a': [{ 'b': {} }] };
*/
function unset(object, path) {
return object == null ? true : baseUnset(object, path);
}
/*
An object that can be access via path API.
@example
var obj = new PathObject({a: "aVal", b: {b1: 'b1Val', b2: 'b2Val'}})
*/
class PathObject {
/*
@param {object} [root] An object to operate on
*/
constructor(root) {
if (root) {
this.__root__ = root;
}
}
contains(id) {
return Boolean(this.getRoot()[id])
}
getRoot() {
if (this.__root__) {
return this.__root__
} else {
return this
}
}
/**
Get value at path
@return {object} The value stored for a given path
@example
obj.get(['b', 'b1'])
=> b1Val
*/
get(path) {
if (!path) {
return undefined
}
if (isString(path)) {
return this.getRoot()[path]
}
if (arguments.length > 1) {
path = Array.prototype.slice(arguments, 0);
}
if (!isArray(path)) {
throw new Error('Illegal argument for PathObject.get()')
}
return get(this.getRoot(), path)
}
set(path, value) {
if (!path) {
throw new Error('Illegal argument: PathObject.set(>path<, value) - path is mandatory.')
}
if (isString(path)) {
this.getRoot()[path] = value;
} else {
setWith(this.getRoot(), path, value);
}
}
delete(path) {
if (isString(path)) {
delete this.getRoot()[path];
} else if (path.length === 1) {
delete this.getRoot()[path[0]];
} else {
var success = unset(this.getRoot(), path);
if (!success) {
throw new Error('Could not delete property at path' + path)
}
}
}
clear() {
var root = this.getRoot();
for (var key in root) {
if (root.hasOwnProperty(key)) {
delete root[key];
}
}
}
}
PathObject.prototype._isPathObject = true;
class Conflict extends Error {
constructor(a, b) {
super("Conflict: " + JSON.stringify(a) +" vs " + JSON.stringify(b));
this.a = a;
this.b = b;
}
}
const INSERT = "insert";
const DELETE$1 = "delete";
class TextOperation {
constructor(data) {
if (!data || data.type === undefined || data.pos === undefined || data.str === undefined) {
throw new Error("Illegal argument: insufficient data.")
}
// 'insert' or 'delete'
this.type = data.type;
// the position where to apply the operation
this.pos = data.pos;
// the string to delete or insert
this.str = data.str;
// sanity checks
if(!this.isInsert() && !this.isDelete()) {
throw new Error("Illegal type.")
}
if (!isString(this.str)) {
throw new Error("Illegal argument: expecting string.")
}
if (!isNumber(this.pos) || this.pos < 0) {
throw new Error("Illegal argument: expecting positive number as pos.")
}
}
apply(str) {
if (this.isEmpty()) return str
if (this.type === INSERT) {
if (str.length < this.pos) {
throw new Error("Provided string is too short.")
}
if (str.splice) {
return str.splice(this.pos, 0, this.str)
} else {
return str.slice(0, this.pos).concat(this.str).concat(str.slice(this.pos))
}
}
else /* if (this.type === DELETE) */ {
if (str.length < this.pos + this.str.length) {
throw new Error("Provided string is too short.")
}
if (str.splice) {
return str.splice(this.pos, this.str.length)
} else {
return str.slice(0, this.pos).concat(str.slice(this.pos + this.str.length))
}
}
}
clone() {
return new TextOperation(this)
}
isNOP() {
return this.type === "NOP" || this.str.length === 0
}
isInsert() {
return this.type === INSERT
}
isDelete() {
return this.type === DELETE$1
}
getLength() {
return this.str.length
}
invert() {
var data = {
type: this.isInsert() ? DELETE$1 : INSERT,
pos: this.pos,
str: this.str
};
return new TextOperation(data)
}
hasConflict(other) {
return hasConflict$1(this, other)
}
isEmpty() {
return this.str.length === 0
}
toJSON() {
return {
type: this.type,
pos: this.pos,
str: this.str
}
}
toString() {
return ["(", (this.isInsert() ? INSERT : DELETE$1), ",", this.pos, ",'", this.str, "')"].join('')
}
}
TextOperation.prototype._isOperation = true;
TextOperation.prototype._isTextOperation = true;
function hasConflict$1(a, b) {
// Insert vs Insert:
//
// Insertions are conflicting iff their insert position is the same.
if (a.type === INSERT && b.type === INSERT) return (a.pos === b.pos)
// Delete vs Delete:
//
// Deletions are conflicting if their ranges overlap.
if (a.type === DELETE$1 && b.type === DELETE$1) {
// to have no conflict, either `a` should be after `b` or `b` after `a`, otherwise.
return !(a.pos >= b.pos + b.str.length || b.pos >= a.pos + a.str.length)
}
// Delete vs Insert:
//
// A deletion and an insertion are conflicting if the insert position is within the deleted range.
var del, ins;
if (a.type === DELETE$1) {
del = a; ins = b;
} else {
del = b; ins = a;
}
return (ins.pos >= del.pos && ins.pos < del.pos + del.str.length)
}
// Transforms two Insertions
// --------
function transform_insert_insert(a, b) {
if (a.pos === b.pos) {
b.pos += a.str.length;
}
else if (a.pos < b.pos) {
b.pos += a.str.length;
}
else {
a.pos += b.str.length;
}
}
// Transform two Deletions
// --------
//
function transform_delete_delete$1(a, b, first) {
// reduce to a normalized case
if (a.pos > b.pos) {
return transform_delete_delete$1(b, a, !first)
}
if (a.pos === b.pos && a.str.length > b.str.length) {
return transform_delete_delete$1(b, a, !first)
}
// take out overlapping parts
if (b.pos < a.pos + a.str.length) {
var s = b.pos - a.pos;
var s1 = a.str.length - s;
var s2 = s + b.str.length;
a.str = a.str.slice(0, s) + a.str.slice(s2);
b.str = b.str.slice(s1);
b.pos -= s;
} else {
b.pos -= a.str.length;
}
}
// Transform Insert and Deletion
// --------
//
function transform_insert_delete(a, b) {
if (a.type === DELETE$1) {
return transform_insert_delete(b, a)
}
// we can assume, that a is an insertion and b is a deletion
// a is before b
if (a.pos <= b.pos) {
b.pos += a.str.length;
}
// a is after b
else if (a.pos >= b.pos + b.str.length) {
a.pos -= b.str.length;
}
// Note: this is a conflict case the user should be noticed about
// If applied still, the deletion takes precedence
// a.pos > b.pos && <= b.pos + b.length
else {
var s = a.pos - b.pos;
b.str = b.str.slice(0, s) + a.str + b.str.slice(s);
a.str = "";
}
}
function transform$1(a, b, options) {
options = options || {};
if (options["no-conflict"] && hasConflict$1(a, b)) {
throw new Conflict(a, b)
}
if (!options.inplace) {
a = a.clone();
b = b.clone();
}
if (a.type === INSERT && b.type === INSERT) {
transform_insert_insert(a, b);
}
els