<commit_before>(function() {
	var ezswipe_container, ezswipe_surface, i, style, _droid_swipe, _ezSwipes;
	_droid_swipe = function(el) {
		var dispatchEvent, _child;
		dispatchEvent = function(name, delta) {
			var event, settings;
			settings = {
				detail : {
					delta : delta,
					bubbles : false,
					cancelable : true
				}
			};
			event = new CustomEvent(name, settings);
			el.parentNode.dispatchEvent(event);
		};
		_child = el.getElementsByClassName("_ezswipe_surface")[0];
		el.ezSwipe = {
			center : {
				x : (_child.clientWidth / 2) - (el.clientWidth / 2),
				y : (_child.clientHeight / 2) - (el.clientHeight / 2)
			},
			timer : null,
			scrolling : false,
			starttime : 0,
			last : {
				x : 0,
				y : 0,
				t : 0
			}
		};
		el.scrollLeft = el.ezSwipe.center.x;
		el.scrollTop = el.ezSwipe.center.y;
		el.onscroll = function(e) {
			var d, delta;
			if ((el.scrollLeft !== el.ezSwipe.center.x) || (el.scrollTop !== el.ezSwipe.center.y)) {
				if (!el.ezSwipe.scrolling) {
					d = new Date().getTime();
					delta = {
						x : 0,
						y : 0,
						t : d
					};
					el.ezSwipe.starttime = d;
					el.ezSwipelast = delta;
					dispatchEvent("swipeStart", delta);
					el.ezSwipe.scrolling = true;
				} else {
					d = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft - el.ezSwipelast.x,
						y : el.ezSwipe.center.y - el.scrollTop - el.ezSwipelast.y,
						t : d - el.ezSwipe.starttime - el.ezSwipelast.t
					};
					el.ezSwipelast = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : d - el.ezSwipe.starttime
					};
					dispatchEvent("swipeMove", delta);
				}
				if (el.ezSwipe.timer) {
					clearTimeout(el.ezSwipe.timer);
				}
				el.ezSwipe.timer = setTimeout(function() {
					el.ezSwipe.timer = null;
					d = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : d - el.ezSwipe.starttime
					};
					el.ezSwipelast = delta;
					dispatchEvent("swipeEnd", delta);
					if (Math.abs(delta.y) > Math.abs(delta.x)) {
						if (delta.y < 0) {
							dispatchEvent("swipeUp", delta);
						} else {
							dispatchEvent("swipeDown", delta);
						}
					} else {
						if (delta.x < 0) {
							dispatchEvent("swipeLeft", delta);
						} else {
							dispatchEvent("swipeRight", delta);
						}
					}
					el.ezSwipe.scrolling = false;
					_droid_swipe(el);
				}, 100);
			}
		};
	};
	_ezSwipes = document.getElementsByClassName("_ezswipe");
	i = 0;
	while (i < _ezSwipes.length) {
		ezswipe_container = document.createElement("div");
		ezswipe_container.classList.add("_ezswipe_container");
		_ezSwipes[i].style.overflow = "hidden";
		style = ezswipe_container.style;
			style.webkitUserSelect = "none";
			style.webkitOverflowScrolling = "touch";
			style.overflow = "auto";
			style.opacity = 0;
			style.top = 0;
			style.left = 0;
			style.position = "absolute";
		if (_ezSwipes[i].offsetWidth > document.width) {
			style.width = document.width + "px";
		} else {
			style.width = _ezSwipes[i].offsetWidth + "px";
		}
		if (_ezSwipes[i].offsetHeight > document.height) {
			style.height = document.height + "px";
		} else {
			style.height = _ezSwipes[i].offsetHeight + "px";
		}
		ezswipe_surface = document.createElement("div");
		ezswipe_surface.classList.add("_ezswipe_surface");
		style = ezswipe_surface.style;
			style.position = "relative";
			style.width = "300%";
			style.height = "300%";
		ezswipe_container.appendChild(ezswipe_surface);
		_droid_swipe(_ezSwipes[i].insertBefore(ezswipe_container, _ezSwipes[i].firstElementChild));
		i++;
	}
})();
<commit_msg>refactor<commit_after>(function() {
	var ezswipe_container, ezswipe_surface, i, style, _droid_swipe, _ezSwipes;
	_droid_swipe = function(el) {
		var dispatchEvent, _child;
		dispatchEvent = function(name, delta) {
			var event, settings;
			settings = {
				detail : {
					delta : delta,
					bubbles : false,
					cancelable : true
				}
			};
			event = new CustomEvent(name, settings);
			el.parentNode.dispatchEvent(event);
		};
		_child = el.getElementsByClassName("_ezswipe_surface")[0];
		el.ezSwipe = {
			center : {
				x : (_child.clientWidth / 2) - (el.clientWidth / 2),
				y : (_child.clientHeight / 2) - (el.clientHeight / 2)
			},
			timer : null,
			scrolling : false,
			starttime : 0,
			last : {
				x : 0,
				y : 0,
				t : 0
			}
		};
		el.scrollLeft = el.ezSwipe.center.x;
		el.scrollTop = el.ezSwipe.center.y;
		el.onscroll = function(e) {
			var d, delta;
			if ((el.scrollLeft !== el.ezSwipe.center.x) || (el.scrollTop !== el.ezSwipe.center.y)) {
				if (!el.ezSwipe.scrolling) {
					delta = {
						x : 0,
						y : 0,
						t : 0
					};
					el.ezSwipe.starttime = new Date().getTime();;
					el.ezSwipelast = delta;
					dispatchEvent("swipeStart", delta);
					el.ezSwipe.scrolling = true;
				} else {
					d = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft - el.ezSwipelast.x,
						y : el.ezSwipe.center.y - el.scrollTop - el.ezSwipelast.y,
						t : d - el.ezSwipe.starttime - el.ezSwipelast.t
					};
					el.ezSwipelast = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : d - el.ezSwipe.starttime
					};
					dispatchEvent("swipeMove", delta);
				}
				if (el.ezSwipe.timer) {
					clearTimeout(el.ezSwipe.timer);
				}
				el.ezSwipe.timer = setTimeout(function() {
					el.ezSwipe.timer = null;
					d = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : d - el.ezSwipe.starttime
					};
					el.ezSwipelast = delta;
					dispatchEvent("swipeEnd", delta);
					if (Math.abs(delta.y) > Math.abs(delta.x)) {
						if (delta.y < 0) {
							dispatchEvent("swipeUp", delta);
						} else {
							dispatchEvent("swipeDown", delta);
						}
					} else {
						if (delta.x < 0) {
							dispatchEvent("swipeLeft", delta);
						} else {
							dispatchEvent("swipeRight", delta);
						}
					}
					el.ezSwipe.scrolling = false;
					_droid_swipe(el);
				}, 100);
			}
		};
	};
	_ezSwipes = document.getElementsByClassName("_ezswipe");
	i = 0;
	while (i < _ezSwipes.length) {
		ezswipe_container = document.createElement("div");
		ezswipe_container.classList.add("_ezswipe_container");
		_ezSwipes[i].style.overflow = "hidden";
		style = ezswipe_container.style;
			style.webkitUserSelect = "none";
			style.webkitOverflowScrolling = "touch";
			style.overflow = "auto";
			style.opacity = 0;
			style.top = 0;
			style.left = 0;
			style.position = "absolute";
		if (_ezSwipes[i].offsetWidth > document.width) {
			style.width = document.width + "px";
		} else {
			style.width = _ezSwipes[i].offsetWidth + "px";
		}
		if (_ezSwipes[i].offsetHeight > document.height) {
			style.height = document.height + "px";
		} else {
			style.height = _ezSwipes[i].offsetHeight + "px";
		}
		ezswipe_surface = document.createElement("div");
		ezswipe_surface.classList.add("_ezswipe_surface");
		style = ezswipe_surface.style;
			style.position = "relative";
			style.width = "300%";
			style.height = "300%";
		ezswipe_container.appendChild(ezswipe_surface);
		_droid_swipe(_ezSwipes[i].insertBefore(ezswipe_container, _ezSwipes[i].firstElementChild));
		i++;
	}
})();
<|endoftext|>


<commit_before>(function() {
	var ezswipe_container, ezswipe_surface, i, style, _droid_swipe, _ezSwipes;
	_droid_swipe = function(el) {
		var dispatchEvent, _child;
		dispatchEvent = function(name, delta) {
			var event, settings;
			settings = {
				detail : {
					delta : delta,
					bubbles : false,
					cancelable : true
				}
			};
			event = new CustomEvent(name, settings);
			el.parentNode.dispatchEvent(event);
		};
		_child = el.getElementsByClassName("_ezswipe_surface")[0];
		el.ezSwipe = {
			center : {
				x : (_child.clientWidth / 2) - (el.clientWidth / 2),
				y : (_child.clientHeight / 2) - (el.clientHeight / 2)
			},
			timer : null,
			scrolling : false,
			starttime : 0,
			last : {
				x : 0,
				y : 0,
				t : 0
			}
		};
		el.scrollLeft = el.ezSwipe.center.x;
		el.scrollTop = el.ezSwipe.center.y;
		el.onscroll = function(e) {
			var d, delta;
			if ((el.scrollLeft !== el.ezSwipe.center.x) || (el.scrollTop !== el.ezSwipe.center.y)) {
				if (!el.ezSwipe.scrolling) {
					delta = {
						x : 0,
						y : 0,
						t : new Date().getTime()
					};
					el.ezSwipestarttime = delta.t;
					el.ezSwipelast = delta;
					dispatchEvent("swipeStart", delta);
					el.ezSwipe.scrolling = true;
				} else {
					d = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft - el.ezSwipelast.x,
						y : el.ezSwipe.center.y - el.scrollTop - el.ezSwipelast.y,
						t : d - el.ezSwipe.starttime - el.ezSwipelast.t
					};
					el.ezSwipelast = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : d - el.ezSwipe.starttime
					};
					dispatchEvent("swipeMove", delta);
				}
				if (el.ezSwipe.timer) {
					clearTimeout(el.ezSwipe.timer);
				}
				el.ezSwipe.timer = setTimeout(function() {
					var t;
					el.ezSwipe.timer = null;
					t = parseInt(new Date().getTime());
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : t - el.ezSwipe.starttime
					};
					el.ezSwipelast = delta;
					dispatchEvent("swipeEnd", delta);
					if (Math.abs(delta.y) > Math.abs(delta.x)) {
						if (delta.y < 0) {
							dispatchEvent("swipeUp", delta);
						} else {
							dispatchEvent("swipeDown", delta);
						}
					} else {
						if (delta.x < 0) {
							dispatchEvent("swipeLeft", delta);
						} else {
							dispatchEvent("swipeRight", delta);
						}
					}
					el.ezSwipe.scrolling = false;
					_droid_swipe(el);
				}, 100);
			}
		};
	};
	_ezSwipes = document.getElementsByClassName("_ezswipe");
	i = 0;
	while (i < _ezSwipes.length) {
		ezswipe_container = document.createElement("div");
		ezswipe_container.classList.add("_ezswipe_container");
		_ezSwipes[i].style.overflow = "hidden";
		style = ezswipe_container.style;
			style.webkitUserSelect = "none";
			style.webkitOverflowScrolling = "touch";
			style.overflow = "auto";
			style.opacity = 0;
			style.top = 0;
			style.left = 0;
			style.position = "absolute";
		if (_ezSwipes[i].offsetWidth > document.width) {
			style.width = document.width + "px";
		} else {
			style.width = _ezSwipes[i].offsetWidth + "px";
		}
		if (_ezSwipes[i].offsetHeight > document.height) {
			style.height = document.height + "px";
		} else {
			style.height = _ezSwipes[i].offsetHeight + "px";
		}
		ezswipe_surface = document.createElement("div");
		ezswipe_surface.classList.add("_ezswipe_surface");
		style = ezswipe_surface.style;
			style.position = "relative";
			style.width = "300%";
			style.height = "300%";
		ezswipe_container.appendChild(ezswipe_surface);
		_droid_swipe(_ezSwipes[i].insertBefore(ezswipe_container, _ezSwipes[i].firstElementChild));
		i++;
	}
})();
<commit_msg>nno parseInt<commit_after>(function() {
	var ezswipe_container, ezswipe_surface, i, style, _droid_swipe, _ezSwipes;
	_droid_swipe = function(el) {
		var dispatchEvent, _child;
		dispatchEvent = function(name, delta) {
			var event, settings;
			settings = {
				detail : {
					delta : delta,
					bubbles : false,
					cancelable : true
				}
			};
			event = new CustomEvent(name, settings);
			el.parentNode.dispatchEvent(event);
		};
		_child = el.getElementsByClassName("_ezswipe_surface")[0];
		el.ezSwipe = {
			center : {
				x : (_child.clientWidth / 2) - (el.clientWidth / 2),
				y : (_child.clientHeight / 2) - (el.clientHeight / 2)
			},
			timer : null,
			scrolling : false,
			starttime : 0,
			last : {
				x : 0,
				y : 0,
				t : 0
			}
		};
		el.scrollLeft = el.ezSwipe.center.x;
		el.scrollTop = el.ezSwipe.center.y;
		el.onscroll = function(e) {
			var d, delta;
			if ((el.scrollLeft !== el.ezSwipe.center.x) || (el.scrollTop !== el.ezSwipe.center.y)) {
				if (!el.ezSwipe.scrolling) {
					delta = {
						x : 0,
						y : 0,
						t : new Date().getTime()
					};
					el.ezSwipestarttime = delta.t;
					el.ezSwipelast = delta;
					dispatchEvent("swipeStart", delta);
					el.ezSwipe.scrolling = true;
				} else {
					d = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft - el.ezSwipelast.x,
						y : el.ezSwipe.center.y - el.scrollTop - el.ezSwipelast.y,
						t : d - el.ezSwipe.starttime - el.ezSwipelast.t
					};
					el.ezSwipelast = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : d - el.ezSwipe.starttime
					};
					dispatchEvent("swipeMove", delta);
				}
				if (el.ezSwipe.timer) {
					clearTimeout(el.ezSwipe.timer);
				}
				el.ezSwipe.timer = setTimeout(function() {
					var t;
					el.ezSwipe.timer = null;
					t = new Date().getTime();
					delta = {
						x : el.ezSwipe.center.x - el.scrollLeft,
						y : el.ezSwipe.center.y - el.scrollTop,
						t : t - el.ezSwipe.starttime
					};
					el.ezSwipelast = delta;
					dispatchEvent("swipeEnd", delta);
					if (Math.abs(delta.y) > Math.abs(delta.x)) {
						if (delta.y < 0) {
							dispatchEvent("swipeUp", delta);
						} else {
							dispatchEvent("swipeDown", delta);
						}
					} else {
						if (delta.x < 0) {
							dispatchEvent("swipeLeft", delta);
						} else {
							dispatchEvent("swipeRight", delta);
						}
					}
					el.ezSwipe.scrolling = false;
					_droid_swipe(el);
				}, 100);
			}
		};
	};
	_ezSwipes = document.getElementsByClassName("_ezswipe");
	i = 0;
	while (i < _ezSwipes.length) {
		ezswipe_container = document.createElement("div");
		ezswipe_container.classList.add("_ezswipe_container");
		_ezSwipes[i].style.overflow = "hidden";
		style = ezswipe_container.style;
			style.webkitUserSelect = "none";
			style.webkitOverflowScrolling = "touch";
			style.overflow = "auto";
			style.opacity = 0;
			style.top = 0;
			style.left = 0;
			style.position = "absolute";
		if (_ezSwipes[i].offsetWidth > document.width) {
			style.width = document.width + "px";
		} else {
			style.width = _ezSwipes[i].offsetWidth + "px";
		}
		if (_ezSwipes[i].offsetHeight > document.height) {
			style.height = document.height + "px";
		} else {
			style.height = _ezSwipes[i].offsetHeight + "px";
		}
		ezswipe_surface = document.createElement("div");
		ezswipe_surface.classList.add("_ezswipe_surface");
		style = ezswipe_surface.style;
			style.position = "relative";
			style.width = "300%";
			style.height = "300%";
		ezswipe_container.appendChild(ezswipe_surface);
		_droid_swipe(_ezSwipes[i].insertBefore(ezswipe_container, _ezSwipes[i].firstElementChild));
		i++;
	}
})();
<|endoftext|>


<commit_before>// Abstract Persistence driver

'use strict';

var aFrom               = require('es5-ext/array/from')
  , compact             = require('es5-ext/array/#/compact')
  , clear               = require('es5-ext/array/#/clear')
  , flatten             = require('es5-ext/array/#/flatten')
  , isCopy              = require('es5-ext/array/#/is-copy')
  , ensureArray         = require('es5-ext/array/valid-array')
  , assign              = require('es5-ext/object/assign')
  , ensureCallable      = require('es5-ext/object/valid-callable')
  , ensureObject        = require('es5-ext/object/valid-object')
  , ensureString        = require('es5-ext/object/validate-stringifiable-value')
  , isSet               = require('es6-set/is-set')
  , deferred            = require('deferred')
  , emitError           = require('event-emitter/emit-error')
  , d                   = require('d')
  , autoBind            = require('d/auto-bind')
  , lazy                = require('d/lazy')
  , debug               = require('debug-ext')('db')
  , ee                  = require('event-emitter')
  , getStamp            = require('time-uuid/time')
  , ensureObservableSet = require('observable-set/valid-observable-set')
  , ensureDatabase      = require('dbjs/valid-dbjs')
  , Event               = require('dbjs/_setup/event')
  , unserialize         = require('dbjs/_setup/unserialize/value')
  , serialize           = require('dbjs/_setup/serialize/value')
  , resolveKeyPath      = require('dbjs/_setup/utils/resolve-property-path')
  , once                = require('timers-ext/once')
  , ensureDriver        = require('./ensure')

  , isArray = Array.isArray
  , isObjectId = RegExp.prototype.test.bind(/^[0-9a-z][0-9a-z]*$/)
  , isDbId = RegExp.prototype.test.bind(/^[0-9a-z][^\n]*$/)
  , isModelId = RegExp.prototype.test.bind(/^[A-Z]/)
  , tokenize = resolveKeyPath.tokenize, resolveObject = resolveKeyPath.resolveObject
  , create = Object.create;

var PersistenceDriver = module.exports = Object.defineProperties(function (dbjs/*, options*/) {
	var autoSaveFilter, options;
	if (!(this instanceof PersistenceDriver)) return new PersistenceDriver(dbjs, arguments[1]);
	options = Object(arguments[1]);
	this.db = ensureDatabase(dbjs);
	autoSaveFilter = (options.autoSaveFilter != null)
		? ensureCallable(options.autoSaveFilter) : this.constructor.defaultAutoSaveFilter;
	dbjs.objects.on('update', function (event) {
		if (event.sourceId === 'persistentLayer') return;
		if (!autoSaveFilter(event)) return;
		debug("persistent update %s %s", event.object.__valueId__, event.stamp);
		this._cueEvent(event);
	}.bind(this));
}, {
	defaultAutoSaveFilter: d(function (event) { return !isModelId(event.object.master.__id__); })
});

var notImplemented = function () { throw new Error("Not implemented"); };

ee(Object.defineProperties(PersistenceDriver.prototype, assign({
	// Any data
	_getRaw: d(notImplemented),
	_getRawObject: d(notImplemented),
	_storeRaw: d(notImplemented),

	// Database data
	_importValue: d(function (id, value, stamp) {
		var proto;
		if (this._loadedEventsMap[id + '.' + stamp]) return;
		this._loadedEventsMap[id + '.' + stamp] = true;
		value = unserialize(value, this.db.objects);
		if (value && value.__id__ && (value.constructor.prototype === value)) proto = value.constructor;
		return new Event(this.db.objects.unserialize(id, proto), value, stamp, 'persistentLayer');
	}),
	getValue: d(function (id) {
		id = ensureString(id);
		if (!isDbId(id)) throw new TypeError(id + " is not a database value id");
		this._ensureOpen();
		++this._runningOperations;
		return this._getRaw(id).finally(this._onOperationEnd);
	}),
	getObject: d(function (objId) {
		objId = ensureString(objId);
		if (!isObjectId(objId)) throw new TypeError(objId + " is not a database object id");
		this._ensureOpen();
		++this._runningOperations;
		return this._getRawObject(objId).finally(this._onOperationEnd);
	}),
	loadValue: d(function (id) {
		return this.getValue(id)(function (data) {
			if (!data) return null;
			return this._importValue(id, data.value, data.stamp);
		}.bind(this));
	}),
	loadObject: d(function (objId) {
		return this.getObject(objId)(function (data) {
			return compact.call(data.map(function (data) {
				return this._importValue(data.id, data.data.value, data.data.stamp);
			}, this));
		}.bind(this));
	}),
	loadAll: d(function () {
		this._ensureOpen();
		++this._runningOperations;
		return this._getAllObjectIds().map(this.loadObject, this).invoke(flatten)
			.finally(this._onOperationEnd);
	}),
	storeEvent: d(function (event) {
		event = ensureObject(event);
		this._ensureOpen();
		++this._runningOperations;
		return this._storeEvent(event).finally(this._onOperationEnd);
	}),
	storeEvents: d(function (events) {
		events = ensureArray(events);
		this._ensureOpen();
		++this._runningOperations;
		return this._storeEvents(events).finally(this._onOperationEnd);
	}),
	_cueEvent: d(function (event) {
		if (!this._eventsToStore.length) {
			++this._runningOperations;
			this._exportEvents();
		}
		this._eventsToStore.push(event);
	}),
	_getAllObjectIds: d(notImplemented),
	_storeEvent: d(notImplemented),
	_storeEvents: d(notImplemented),

	// Indexed database data
	indexKeyPath: d(function (keyPath/*, set*/) {
		var names, key, onAdd, onDelete, eventName, listener, set = arguments[1];
		if (set != null) ensureObservableSet(set);
		else set = this.db.Object.instances;
		names = tokenize(ensureString(keyPath));
		this._ensureOpen();
		key = names[names.length - 1];
		eventName = 'computed:' + keyPath;
		++this._runningOperations;
		return this._getIndexedMap(keyPath)(function (map) {
			listener = function (event) {
				var sValue, stamp, objId = event.target.object.master.__id__;
				if (event.target.object.constructor === event.target.object.database.Base) return;
				if (isSet(event.target)) {
					sValue = [];
					event.target.forEach(function (value) { sValue.push(serialize(value)); });
				} else {
					sValue = serialize(event.newValue);
				}
				stamp = event.dbjs ? event.dbjs.stamp : getStamp();
				map[objId].value = sValue;
				map[objId].stamp = stamp;
				this.emit(eventName, map[objId]);
				++this._runningOperations;
				this._storeIndexedValue(objId, keyPath, map[objId]).finally(this._onOperationEnd).done();
			}.bind(this);
			onAdd = function (obj) {
				var observable, value, stamp, objId, sValue, old;
				obj = resolveObject(obj, names);
				if (!obj) return null;
				objId = obj.__id__;
				if (obj.isKeyStatic(key)) {
					value = obj[key];
					stamp = 0;
				} else {
					value = obj._get_(key);
					observable = obj._getObservable_(key);
					stamp = observable.lastModified;
					if (isSet(value)) value.on('change', listener);
					else observable.on('change', listener);
				}
				if (isSet(value)) {
					sValue = [];
					value.forEach(function (value) { sValue.push(serialize(value)); });
				} else {
					sValue = serialize(value);
				}
				old = map[objId];
				if (old) {
					if (old.stamp === stamp) {
						if (isArray(sValue)) {
							if (isCopy.call(old.value, sValue)) return;
						} else {
							if (old.value === sValue) return;
						}
						++stamp; // most likely model update
					} else if (old.stamp > stamp) {
						stamp = old.stamp + 1;
					}
					old.value = sValue;
					old.stamp = stamp;
				} else {
					map[objId] = {
						value: sValue,
						stamp: stamp
					};
				}
				this.emit(eventName, map[objId]);
				return this._storeIndexedValue(objId, keyPath, map[objId]);
			}.bind(this);
			onDelete = function (obj) {
				obj = resolveObject(obj, names);
				if (!obj) return null;
				if (obj.isKeyStatic(key)) return;
				obj._getObservable_(key).off('change', listener);
			}.bind(this);
			set.on('change', function (event) {
				if (event.type === 'add') {
					++this._runningOperations;
					onAdd(event.value).finally(this._onOperationEnd).done();
					return;
				}
				if (event.type === 'delete') {
					onDelete(event.value);
					return;
				}
				if (event.type === 'batch') {
					if (event.added) {
						++this._runningOperations;
						deferred.map(event.added, onAdd).finally(this._onOperationEnd).done();
					}
					if (event.deleted) event.deleted.forEach(onDelete);
				}
			}.bind(this));
			return deferred.map(aFrom(set), onAdd)(map);
		}.bind(this)).finally(this._onOperationEnd);
	}),
	_getIndexedValue: d(notImplemented),
	_getIndexedMap: d(notImplemented),
	_storeIndexedValue: d(notImplemented),

	// Custom data
	getCustom: d(function (key) {
		key = ensureString(key);
		this._ensureOpen();
		++this._runningOperations;
		return this._getCustom(key).finally(this._onOperationEnd);
	}),
	storeCustom: d(function (key, value) {
		key = ensureString(key);
		this._ensureOpen();
		++this._runningOperations;
		return this._storeCustom(ensureString(key), value).finally(this._onOperationEnd);
	}),
	_getCustom: d(notImplemented),
	_storeCustom: d(notImplemented),

	// Storage export/import
	export: d(function (externalStore) {
		ensureDriver(externalStore);
		this._ensureOpen();
		++this._runningOperations;
		return this._exportAll(externalStore).finally(this._onOperationEnd);
	}),
	_exportAll: d(notImplemented),

	// Clonnection related
	isClosed: d(false),
	close: d(function () {
		this._ensureOpen();
		this.isClosed = true;
		if (this._runningOperations) {
			this._closeDeferred = deferred();
			return this._closeDeferred.promise;
		}
		return this._close();
	}),
	_ensureOpen: d(function () {
		if (this.isClosed) throw new Error("Database not accessible");
	}),
	_runningOperations: d(0),
	_close: d(notImplemented)
}, autoBind({
	emitError: d(emitError),
	_onOperationEnd: d(function () {
		if (--this._runningOperations) return;
		if (!this._closeDeferred) return;
		this._closeDeferred.resolve(this._close());
	})
}), lazy({
	_loadedEventsMap: d(function () { return create(null); }),
	_eventsToStore: d(function () { return []; }),
	_exportEvents: d(function () {
		return once(function () {
			var promise = this._storeEvents(this._eventsToStore);
			clear.call(this._eventsToStore);
			promise.finally(this._onOperationEnd).done();
		}.bind(this));
	})
}))));
<commit_msg>Support partial object records get<commit_after>// Abstract Persistence driver

'use strict';

var aFrom               = require('es5-ext/array/from')
  , compact             = require('es5-ext/array/#/compact')
  , clear               = require('es5-ext/array/#/clear')
  , flatten             = require('es5-ext/array/#/flatten')
  , isCopy              = require('es5-ext/array/#/is-copy')
  , ensureArray         = require('es5-ext/array/valid-array')
  , assign              = require('es5-ext/object/assign')
  , ensureCallable      = require('es5-ext/object/valid-callable')
  , ensureObject        = require('es5-ext/object/valid-object')
  , ensureString        = require('es5-ext/object/validate-stringifiable-value')
  , isSet               = require('es6-set/is-set')
  , ensureSet           = require('es6-set/valid-set')
  , deferred            = require('deferred')
  , emitError           = require('event-emitter/emit-error')
  , d                   = require('d')
  , autoBind            = require('d/auto-bind')
  , lazy                = require('d/lazy')
  , debug               = require('debug-ext')('db')
  , ee                  = require('event-emitter')
  , getStamp            = require('time-uuid/time')
  , ensureObservableSet = require('observable-set/valid-observable-set')
  , ensureDatabase      = require('dbjs/valid-dbjs')
  , Event               = require('dbjs/_setup/event')
  , unserialize         = require('dbjs/_setup/unserialize/value')
  , serialize           = require('dbjs/_setup/serialize/value')
  , resolveKeyPath      = require('dbjs/_setup/utils/resolve-property-path')
  , once                = require('timers-ext/once')
  , ensureDriver        = require('./ensure')

  , isArray = Array.isArray
  , isObjectId = RegExp.prototype.test.bind(/^[0-9a-z][0-9a-z]*$/)
  , isDbId = RegExp.prototype.test.bind(/^[0-9a-z][^\n]*$/)
  , isModelId = RegExp.prototype.test.bind(/^[A-Z]/)
  , tokenize = resolveKeyPath.tokenize, resolveObject = resolveKeyPath.resolveObject
  , create = Object.create;

var PersistenceDriver = module.exports = Object.defineProperties(function (dbjs/*, options*/) {
	var autoSaveFilter, options;
	if (!(this instanceof PersistenceDriver)) return new PersistenceDriver(dbjs, arguments[1]);
	options = Object(arguments[1]);
	this.db = ensureDatabase(dbjs);
	autoSaveFilter = (options.autoSaveFilter != null)
		? ensureCallable(options.autoSaveFilter) : this.constructor.defaultAutoSaveFilter;
	dbjs.objects.on('update', function (event) {
		if (event.sourceId === 'persistentLayer') return;
		if (!autoSaveFilter(event)) return;
		debug("persistent update %s %s", event.object.__valueId__, event.stamp);
		this._cueEvent(event);
	}.bind(this));
}, {
	defaultAutoSaveFilter: d(function (event) { return !isModelId(event.object.master.__id__); })
});

var notImplemented = function () { throw new Error("Not implemented"); };

ee(Object.defineProperties(PersistenceDriver.prototype, assign({
	// Any data
	_getRaw: d(notImplemented),
	_getRawObject: d(notImplemented),
	_storeRaw: d(notImplemented),

	// Database data
	_importValue: d(function (id, value, stamp) {
		var proto;
		if (this._loadedEventsMap[id + '.' + stamp]) return;
		this._loadedEventsMap[id + '.' + stamp] = true;
		value = unserialize(value, this.db.objects);
		if (value && value.__id__ && (value.constructor.prototype === value)) proto = value.constructor;
		return new Event(this.db.objects.unserialize(id, proto), value, stamp, 'persistentLayer');
	}),
	getValue: d(function (id) {
		id = ensureString(id);
		if (!isDbId(id)) throw new TypeError(id + " is not a database value id");
		this._ensureOpen();
		++this._runningOperations;
		return this._getRaw(id).finally(this._onOperationEnd);
	}),
	getObject: d(function (objId/*, options*/) {
		var keyPaths, options = arguments[1];
		objId = ensureString(objId);
		if (!isObjectId(objId)) throw new TypeError(objId + " is not a database object id");
		this._ensureOpen();
		++this._runningOperations;
		if (options && (options.keyPaths != null)) keyPaths = ensureSet(options.keyPaths);
		return this._getRawObject(objId, keyPaths).finally(this._onOperationEnd);
	}),
	loadValue: d(function (id) {
		return this.getValue(id)(function (data) {
			if (!data) return null;
			return this._importValue(id, data.value, data.stamp);
		}.bind(this));
	}),
	loadObject: d(function (objId) {
		return this.getObject(objId)(function (data) {
			return compact.call(data.map(function (data) {
				return this._importValue(data.id, data.data.value, data.data.stamp);
			}, this));
		}.bind(this));
	}),
	loadAll: d(function () {
		this._ensureOpen();
		++this._runningOperations;
		return this._getAllObjectIds().map(this.loadObject, this).invoke(flatten)
			.finally(this._onOperationEnd);
	}),
	storeEvent: d(function (event) {
		event = ensureObject(event);
		this._ensureOpen();
		++this._runningOperations;
		return this._storeEvent(event).finally(this._onOperationEnd);
	}),
	storeEvents: d(function (events) {
		events = ensureArray(events);
		this._ensureOpen();
		++this._runningOperations;
		return this._storeEvents(events).finally(this._onOperationEnd);
	}),
	_cueEvent: d(function (event) {
		if (!this._eventsToStore.length) {
			++this._runningOperations;
			this._exportEvents();
		}
		this._eventsToStore.push(event);
	}),
	_getAllObjectIds: d(notImplemented),
	_storeEvent: d(notImplemented),
	_storeEvents: d(notImplemented),

	// Indexed database data
	indexKeyPath: d(function (keyPath/*, set*/) {
		var names, key, onAdd, onDelete, eventName, listener, set = arguments[1];
		if (set != null) ensureObservableSet(set);
		else set = this.db.Object.instances;
		names = tokenize(ensureString(keyPath));
		this._ensureOpen();
		key = names[names.length - 1];
		eventName = 'computed:' + keyPath;
		++this._runningOperations;
		return this._getIndexedMap(keyPath)(function (map) {
			listener = function (event) {
				var sValue, stamp, objId = event.target.object.master.__id__;
				if (event.target.object.constructor === event.target.object.database.Base) return;
				if (isSet(event.target)) {
					sValue = [];
					event.target.forEach(function (value) { sValue.push(serialize(value)); });
				} else {
					sValue = serialize(event.newValue);
				}
				stamp = event.dbjs ? event.dbjs.stamp : getStamp();
				map[objId].value = sValue;
				map[objId].stamp = stamp;
				this.emit(eventName, map[objId]);
				++this._runningOperations;
				this._storeIndexedValue(objId, keyPath, map[objId]).finally(this._onOperationEnd).done();
			}.bind(this);
			onAdd = function (obj) {
				var observable, value, stamp, objId, sValue, old;
				obj = resolveObject(obj, names);
				if (!obj) return null;
				objId = obj.__id__;
				if (obj.isKeyStatic(key)) {
					value = obj[key];
					stamp = 0;
				} else {
					value = obj._get_(key);
					observable = obj._getObservable_(key);
					stamp = observable.lastModified;
					if (isSet(value)) value.on('change', listener);
					else observable.on('change', listener);
				}
				if (isSet(value)) {
					sValue = [];
					value.forEach(function (value) { sValue.push(serialize(value)); });
				} else {
					sValue = serialize(value);
				}
				old = map[objId];
				if (old) {
					if (old.stamp === stamp) {
						if (isArray(sValue)) {
							if (isCopy.call(old.value, sValue)) return;
						} else {
							if (old.value === sValue) return;
						}
						++stamp; // most likely model update
					} else if (old.stamp > stamp) {
						stamp = old.stamp + 1;
					}
					old.value = sValue;
					old.stamp = stamp;
				} else {
					map[objId] = {
						value: sValue,
						stamp: stamp
					};
				}
				this.emit(eventName, map[objId]);
				return this._storeIndexedValue(objId, keyPath, map[objId]);
			}.bind(this);
			onDelete = function (obj) {
				obj = resolveObject(obj, names);
				if (!obj) return null;
				if (obj.isKeyStatic(key)) return;
				obj._getObservable_(key).off('change', listener);
			}.bind(this);
			set.on('change', function (event) {
				if (event.type === 'add') {
					++this._runningOperations;
					onAdd(event.value).finally(this._onOperationEnd).done();
					return;
				}
				if (event.type === 'delete') {
					onDelete(event.value);
					return;
				}
				if (event.type === 'batch') {
					if (event.added) {
						++this._runningOperations;
						deferred.map(event.added, onAdd).finally(this._onOperationEnd).done();
					}
					if (event.deleted) event.deleted.forEach(onDelete);
				}
			}.bind(this));
			return deferred.map(aFrom(set), onAdd)(map);
		}.bind(this)).finally(this._onOperationEnd);
	}),
	_getIndexedValue: d(notImplemented),
	_getIndexedMap: d(notImplemented),
	_storeIndexedValue: d(notImplemented),

	// Custom data
	getCustom: d(function (key) {
		key = ensureString(key);
		this._ensureOpen();
		++this._runningOperations;
		return this._getCustom(key).finally(this._onOperationEnd);
	}),
	storeCustom: d(function (key, value) {
		key = ensureString(key);
		this._ensureOpen();
		++this._runningOperations;
		return this._storeCustom(ensureString(key), value).finally(this._onOperationEnd);
	}),
	_getCustom: d(notImplemented),
	_storeCustom: d(notImplemented),

	// Storage export/import
	export: d(function (externalStore) {
		ensureDriver(externalStore);
		this._ensureOpen();
		++this._runningOperations;
		return this._exportAll(externalStore).finally(this._onOperationEnd);
	}),
	_exportAll: d(notImplemented),

	// Clonnection related
	isClosed: d(false),
	close: d(function () {
		this._ensureOpen();
		this.isClosed = true;
		if (this._runningOperations) {
			this._closeDeferred = deferred();
			return this._closeDeferred.promise;
		}
		return this._close();
	}),
	_ensureOpen: d(function () {
		if (this.isClosed) throw new Error("Database not accessible");
	}),
	_runningOperations: d(0),
	_close: d(notImplemented)
}, autoBind({
	emitError: d(emitError),
	_onOperationEnd: d(function () {
		if (--this._runningOperations) return;
		if (!this._closeDeferred) return;
		this._closeDeferred.resolve(this._close());
	})
}), lazy({
	_loadedEventsMap: d(function () { return create(null); }),
	_eventsToStore: d(function () { return []; }),
	_exportEvents: d(function () {
		return once(function () {
			var promise = this._storeEvents(this._eventsToStore);
			clear.call(this._eventsToStore);
			promise.finally(this._onOperationEnd).done();
		}.bind(this));
	})
}))));
<|endoftext|>


<commit_before>'use strict';

(function () {
  var EVENT_NAMES = {
    ALL_IDS_COMPLETE: 'allIdsComplete',
    ID_ADDED: 'idAdded',
    ID_RETAINED: 'idRetained'
  };

  CKEDITOR.plugins.add('autoid', {
    init: function (editor) {
      var self = this;

      // This configures the Advanced Content Filter to allow all headings
      // to have id attributes
      editor.addFeature({
        allowedContent: 'h1 h2 h3 h4 h5 h6 *[id]'
      });

      if (editor && !editor.config.autoid) {
        editor.config.autoid = {};
      }
      self.settings = editor.config.autoid;
      if (!self.settings) {
        self.settings = {};
      }

      editor.addCommand('autoid', {
        exec: function (editor) {
          var autoId = editor.getCommand('autoid');
          autoId.toggleState();
          if (autoId.state === CKEDITOR.TRISTATE_ON)
            addAllIds();
        },
        editorFocus: true
      });

      editor.on("instanceReady", function () {
        if (self.settings.autostart !== false) {
          editor.getCommand('autoid').setState(CKEDITOR.TRISTATE_ON);
          addAllIds();
        }
      });

      CKEDITOR.on('dialogDefinition', modifyLinkDialog);

      editor.on('selectionChange', addIdIfNewHeading);

      editor.on('paste', checkPastedContentForHeadings);

      function addAllIds() {
        var headings = findAllHeadings(),
          i, heading;

        for (i = 0; i < headings.count(); i++) {
          heading = headings.getItem(i);
          if (heading.hasAttribute('id')) {
            continue;
          }
          addId(heading);
        }
        editor.fire(EVENT_NAMES.ALL_IDS_COMPLETE);
      }

      function findAllHeadings() {
        return editor.document.find('h1, h2, h3, h4, h5, h6');
      }

      function findHeadingIds(headings) {
        var ids = [],
          i, heading, id;

        for (i = 0; i < headings.count(); i++) {
          heading = headings.getItem(i);
          if (id = heading.getAttribute('id')) {
            ids.push(id)
          }
        }
        return ids;
      }

      function addId(heading) {
        var uuid = CKEDITOR.tools.getUniqueId();
        heading.setAttributes({ id: uuid });
        editor.fire(EVENT_NAMES.ID_ADDED);
      }

      function addIdIfNewHeading() {
        var element = editor.getSelection().getStartElement();
        if (isHeading(element) && !element.hasAttribute('id')) {
          addId(element);
        }
      }

      function isHeading(element) {
        return element.is('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
      }

      function checkPastedContentForHeadings(ev) {
        var pastedContent = ev.data.dataValue,
          pastedContentAsHtml = CKEDITOR.htmlParser.fragment.fromHtml(pastedContent),
          pastedElements = pastedContentAsHtml.children,
          writer = new CKEDITOR.htmlParser.basicWriter(),
          i, element, id, originalHeading;

        for (i = 0; i < pastedElements.length; i++) {
          element = pastedElements[i];
          if (element.type === CKEDITOR.NODE_ELEMENT) {
            id = element.attributes.id;
            originalHeading = checkForDuplicateId(id);
            if (originalHeading) {
              element = resolveDuplicateIds(element, originalHeading);
            }
          }
        }

        pastedContentAsHtml.writeHtml(writer);
        ev.data.dataValue = writer.getHtml();
      }

      function checkForDuplicateId(id) {
        var headingIds = findHeadingIds(findAllHeadings()),
          i, headingId, originalHeading;

        for (i = 0; i < headingIds.length; i++) {
          headingId = headingIds[i];
          if (id === headingId) {
            return originalHeading = editor.document.getById(headingId);
          }
        }
      }

      function resolveDuplicateIds(newHeading, originalHeading) {
        var newHeadingText = newHeading.children[0].value,
          originalHeadingText = originalHeading.getText();

        // if the original heading has no content (blank or just a line-feed
        // character is left when all the content is cut), the original heading
        // will be removed and the new heading will retain the id
        if ( originalHeadingText.length <= 1 && notWordCharacter(originalHeadingText.charAt(0))) {
          originalHeading.remove();
          editor.fire(EVENT_NAMES.ID_RETAINED);
          return newHeading;
        }

        // if the original heading text is not blank, the original should retain its
        // id and the new heading should get a new one.
        if (originalHeadingText) {
          newHeading.attributes.id = CKEDITOR.tools.getUniqueId();
          editor.fire(EVENT_NAMES.ID_ADDED);
          return newHeading;
        }

      }

      function notWordCharacter(character) {
        if (!character)
          return true;

        return !(/\w/.test(character));
      }

      // This modifies the dialog for the 'link' plugin to allow the option of
      // internal document linking to autoid headings. Original 'link' plugin
      // code can be found at:
      // https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/link

      function modifyLinkDialog(ev) {
        var dialog = ev.data;

        if (dialog.name == 'link') {
          var def = ev.data.definition,
            infoTab = def.contents[0],
            linkTypeTab = findLinkTypeTab(infoTab);

          if (!headingTypeAdded(linkTypeTab)) {
            // Add option for heading links to linkType dropdown
            linkTypeTab.items.push(['Link to heading in the text', 'heading']);

            // Add additional content to 'Link Info' tab for heading links

            // Modify linkType's 'onChange' function to accomodate new heading option
            linkTypeTab.onChange = modifiedLinkTypeChanged;

            // Modify main dialog 'onOk' function to handle heading data
            def.onOk = modifiedOnOk;
          }
        }
      }

      function headingTypeAdded(linkTypeTab) {
        for ( var i = 0; i < linkTypeTab.items.length; i++ ) {
          var item = linkTypeTab.items[i];
          if ( item[1] === 'heading' )
            return true;
        }
      }

      function findLinkTypeTab(infoTab) {
        for ( var i = 0; i < infoTab.elements.length; i++ ) {
          var element = infoTab.elements[i];
          if ( element.id === "linkType" )
            return element;
        }
      }

      function modifiedLinkTypeChanged() {
        var dialog = this.getDialog(),
          partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions', 'headingOptions' ],
          typeValue = this.getValue(),
          uploadTab = dialog.definition.getContents( 'upload' ),
          uploadInitiallyHidden = uploadTab && uploadTab.hidden;

        if ( typeValue == 'url' ) {
          if ( editor.config.linkShowTargetTab )
            dialog.showPage( 'target' );
          if ( !uploadInitiallyHidden )
            dialog.showPage( 'upload' );
          } else {
            dialog.hidePage( 'target' );
          if ( !uploadInitiallyHidden )
            dialog.hidePage( 'upload' );
        }

        for ( var i = 0; i < partIds.length; i++ ) {
          var element = dialog.getContentElement( 'info', partIds[ i ] );
          if ( !element )
            continue;

          element = element.getElement().getParent().getParent();
          if ( partIds[ i ] == typeValue + 'Options' )
            element.show();
          else
            element.hide();
        }

        dialog.layout();
      }

      function createHeadingLinkContent() {
        var headings;

        return {
          type: 'vbox',
          id: 'headingOptions',
          children: [ {
            type: 'select',
            id: 'heading',
            label: 'Select a Heading',
            style: 'width : 100%;',
            items: [ [ '' ] ],
            setup: function() {
              this.clear();
              this.add( [ '' ] );

              headings = findAllHeadings();

              if (headings && headings.count()) {
                this.getElement().show();

                for (var i = 0; i < headings.count(); i++) {
                  var heading = headings.getItem(i);
                  this.add( heading.getText(), heading.getId() );
                }
              }
              else {
                this.getElement().hide();
              }
            },
            commit: function(data) {
              if (!data.heading)
                data.heading = {};

              data.heading.id = this.getValue();
              // set the data type to 'anchor' since the procedure is the same
              // for headings.  This way, we don't have to modify the main
              // 'plugin.js' for 'link' to handle a new link type.
              if (data.heading.id)
                data.type = 'anchor';
            }
          },
          {
            type: 'html',
            id: 'noHeadings',
            style: 'text-align: center;',
            html: '
' + CKEDITOR.tools.htmlEncode( '(No headings available in the document)' ) + '
',
            // Focus the first element defined in above html.
            focus: true,
            setup: function() {
              this.getElement()[ headings && headings.count() ? 'hide' : 'show' ]();
            }
          } ]
        }
      }

      function modifiedOnOk() {
        var data = {},
          plugin = CKEDITOR.plugins.link;

        // Collect data from fields.
        this.commitContent( data );

        // if there is a heading id and not an anchor id, the user is creating
        // a link to a heading.  We set value of the heading id to the anchor.id
        // property so it will create the new link without having to
        // modify the plugin further
        if (data.heading.id && !data.anchor.id)
          data.anchor.id = data.heading.id;

        var selection = editor.getSelection(),
          attributes = plugin.getLinkAttributes( editor, data ),
          bm,
          nestedLinks;

        if ( !this._.selectedElement ) {
          var range = selection.getRanges()[ 0 ],
            text;

          // Use link URL as text with a collapsed cursor.
          if ( range.collapsed ) {
            // Short mailto link text view (#5736).
            text = new CKEDITOR.dom.text( data.linkText || ( data.type == 'email' ?
              data.email.address : attributes.set[ 'data-cke-saved-href' ] ), editor.document );
            range.insertNode( text );
            range.selectNodeContents( text );
          } else if ( initialLinkText !== data.linkText ) {
            text = new CKEDITOR.dom.text( data.linkText, editor.document );

            // Shrink range to preserve block element.
            range.shrink( CKEDITOR.SHRINK_TEXT );

            // Use extractHtmlFromRange to remove markup within the selection. Also this method is a little
            // smarter than range#deleteContents as it plays better e.g. with table cells.
            editor.editable().extractHtmlFromRange( range );

            range.insertNode( text );
          }

          // Editable links nested within current range should be removed, so that the link is applied to whole selection.
          nestedLinks = range._find( 'a' );

          for	( var i = 0; i < nestedLinks.length; i++ ) {
            nestedLinks[ i ].remove( true );
          }

          // Apply style.
          var style = new CKEDITOR.style( {
            element: 'a',
            attributes: attributes.set
          } );

          style.type = CKEDITOR.STYLE_INLINE; // need to override... dunno why.
          style.applyToRange( range, editor );
          range.select();
        } else {
          // We're only editing an existing link, so just overwrite the attributes.
          var element = this._.selectedElement,
            href = element.data( 'cke-saved-href' ),
            textView = element.getHtml(),
            newText;

          element.setAttributes( attributes.set );
          element.removeAttributes( attributes.removed );

          if ( data.linkText && initialLinkText != data.linkText ) {
            // Display text has been changed.
            newText = data.linkText;
          } else if ( href == textView || data.type == 'email' && textView.indexOf( '@' ) != -1 ) {
            // Update text view when user changes protocol (#4612).
            // Short mailto link text view (#5736).
            newText = data.type == 'email' ? data.email.address : attributes.set[ 'data-cke-saved-href' ];
          }

          if ( newText ) {
            element.setText( newText );
            // We changed the content, so need to select it again.
            selection.selectElement( element );
          }

          delete this._.selectedElement;
        }
      }
    }
  });
})();
<commit_msg>Pass the editor explicitly to the 'findAllHeadings' function<commit_after>'use strict';

(function () {
  var EVENT_NAMES = {
    ALL_IDS_COMPLETE: 'allIdsComplete',
    ID_ADDED: 'idAdded',
    ID_RETAINED: 'idRetained'
  };

  CKEDITOR.plugins.add('autoid', {
    init: function (editor) {
      var self = this;

      // This configures the Advanced Content Filter to allow all headings
      // to have id attributes
      editor.addFeature({
        allowedContent: 'h1 h2 h3 h4 h5 h6 *[id]'
      });

      if (editor && !editor.config.autoid) {
        editor.config.autoid = {};
      }
      self.settings = editor.config.autoid;
      if (!self.settings) {
        self.settings = {};
      }

      editor.addCommand('autoid', {
        exec: function (editor) {
          var autoId = editor.getCommand('autoid');
          autoId.toggleState();
          if (autoId.state === CKEDITOR.TRISTATE_ON)
            addAllIds();
        },
        editorFocus: true
      });

      editor.on("instanceReady", function () {
        if (self.settings.autostart !== false) {
          editor.getCommand('autoid').setState(CKEDITOR.TRISTATE_ON);
          addAllIds();
        }
      });

      CKEDITOR.on('dialogDefinition', modifyLinkDialog);

      editor.on('selectionChange', addIdIfNewHeading);

      editor.on('paste', checkPastedContentForHeadings);

      function addAllIds() {
        var headings = findAllHeadings(editor),
          i, heading;

        for (i = 0; i < headings.count(); i++) {
          heading = headings.getItem(i);
          if (heading.hasAttribute('id')) {
            continue;
          }
          addId(heading);
        }
        editor.fire(EVENT_NAMES.ALL_IDS_COMPLETE);
      }

      function findAllHeadings(editor) {
        return editor.document.find('h1, h2, h3, h4, h5, h6');
      }

      function findHeadingIds(headings) {
        var ids = [],
          i, heading, id;

        for (i = 0; i < headings.count(); i++) {
          heading = headings.getItem(i);
          if (id = heading.getAttribute('id')) {
            ids.push(id)
          }
        }
        return ids;
      }

      function addId(heading) {
        var uuid = CKEDITOR.tools.getUniqueId();
        heading.setAttributes({ id: uuid });
        editor.fire(EVENT_NAMES.ID_ADDED);
      }

      function addIdIfNewHeading() {
        var element = editor.getSelection().getStartElement();
        if (isHeading(element) && !element.hasAttribute('id')) {
          addId(element);
        }
      }

      function isHeading(element) {
        return element.is('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
      }

      function checkPastedContentForHeadings(ev) {
        var pastedContent = ev.data.dataValue,
          pastedContentAsHtml = CKEDITOR.htmlParser.fragment.fromHtml(pastedContent),
          pastedElements = pastedContentAsHtml.children,
          writer = new CKEDITOR.htmlParser.basicWriter(),
          i, element, id, originalHeading;

        for (i = 0; i < pastedElements.length; i++) {
          element = pastedElements[i];
          if (element.type === CKEDITOR.NODE_ELEMENT) {
            id = element.attributes.id;
            originalHeading = checkForDuplicateId(id);
            if (originalHeading) {
              element = resolveDuplicateIds(element, originalHeading);
            }
          }
        }

        pastedContentAsHtml.writeHtml(writer);
        ev.data.dataValue = writer.getHtml();
      }

      function checkForDuplicateId(id) {
        var headingIds = findHeadingIds(findAllHeadings(editor)),
          i, headingId, originalHeading;

        for (i = 0; i < headingIds.length; i++) {
          headingId = headingIds[i];
          if (id === headingId) {
            return originalHeading = editor.document.getById(headingId);
          }
        }
      }

      function resolveDuplicateIds(newHeading, originalHeading) {
        var newHeadingText = newHeading.children[0].value,
          originalHeadingText = originalHeading.getText();

        // if the original heading has no content (blank or just a line-feed
        // character is left when all the content is cut), the original heading
        // will be removed and the new heading will retain the id
        if ( originalHeadingText.length <= 1 && notWordCharacter(originalHeadingText.charAt(0))) {
          originalHeading.remove();
          editor.fire(EVENT_NAMES.ID_RETAINED);
          return newHeading;
        }

        // if the original heading text is not blank, the original should retain its
        // id and the new heading should get a new one.
        if (originalHeadingText) {
          newHeading.attributes.id = CKEDITOR.tools.getUniqueId();
          editor.fire(EVENT_NAMES.ID_ADDED);
          return newHeading;
        }

      }

      function notWordCharacter(character) {
        if (!character)
          return true;

        return !(/\w/.test(character));
      }

      // This modifies the dialog for the 'link' plugin to allow the option of
      // internal document linking to autoid headings. Original 'link' plugin
      // code can be found at:
      // https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/link

      function modifyLinkDialog(ev) {
        var dialog = ev.data;

        if (dialog.name == 'link') {
          var def = ev.data.definition,
            editor = def.dialog._.editor,
            infoTab = def.contents[0],
            linkTypeTab = findLinkTypeTab(infoTab);

          if (!headingTypeAdded(linkTypeTab)) {
            // Add option for heading links to linkType dropdown
            linkTypeTab.items.push(['Link to heading in the text', 'heading']);

            // Add additional content to 'Link Info' tab for heading links
            infoTab.elements.push(createHeadingLinkContent(editor));

            // Modify linkType's 'onChange' function to accomodate new heading option
            linkTypeTab.onChange = modifiedLinkTypeChanged;

            // Modify main dialog 'onOk' function to handle heading data
            def.onOk = modifiedOnOk;
          }
        }
      }

      function headingTypeAdded(linkTypeTab) {
        for ( var i = 0; i < linkTypeTab.items.length; i++ ) {
          var item = linkTypeTab.items[i];
          if ( item[1] === 'heading' )
            return true;
        }
      }

      function findLinkTypeTab(infoTab) {
        for ( var i = 0; i < infoTab.elements.length; i++ ) {
          var element = infoTab.elements[i];
          if ( element.id === "linkType" )
            return element;
        }
      }

      function modifiedLinkTypeChanged() {
        var dialog = this.getDialog(),
          partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions', 'headingOptions' ],
          typeValue = this.getValue(),
          uploadTab = dialog.definition.getContents( 'upload' ),
          uploadInitiallyHidden = uploadTab && uploadTab.hidden;

        if ( typeValue == 'url' ) {
          if ( editor.config.linkShowTargetTab )
            dialog.showPage( 'target' );
          if ( !uploadInitiallyHidden )
            dialog.showPage( 'upload' );
          } else {
            dialog.hidePage( 'target' );
          if ( !uploadInitiallyHidden )
            dialog.hidePage( 'upload' );
        }

        for ( var i = 0; i < partIds.length; i++ ) {
          var element = dialog.getContentElement( 'info', partIds[ i ] );
          if ( !element )
            continue;

          element = element.getElement().getParent().getParent();
          if ( partIds[ i ] == typeValue + 'Options' )
            element.show();
          else
            element.hide();
        }

        dialog.layout();
      }

      function createHeadingLinkContent(editor) {
        var headings;

        return {
          type: 'vbox',
          id: 'headingOptions',
          children: [ {
            type: 'select',
            id: 'heading',
            label: 'Select a Heading',
            style: 'width : 100%;',
            items: [ [ '' ] ],
            setup: function() {
              this.clear();
              this.add( [ '' ] );

              headings = findAllHeadings(editor);

              if (headings && headings.count()) {
                this.getElement().show();

                for (var i = 0; i < headings.count(); i++) {
                  var heading = headings.getItem(i);
                  this.add( heading.getText(), heading.getId() );
                }
              }
              else {
                this.getElement().hide();
              }
            },
            commit: function(data) {
              if (!data.heading)
                data.heading = {};

              data.heading.id = this.getValue();
              // set the data type to 'anchor' since the procedure is the same
              // for headings.  This way, we don't have to modify the main
              // 'plugin.js' for 'link' to handle a new link type.
              if (data.heading.id)
                data.type = 'anchor';
            }
          },
          {
            type: 'html',
            id: 'noHeadings',
            style: 'text-align: center;',
            html: '
' + CKEDITOR.tools.htmlEncode( '(No headings available in the document)' ) + '
',
            // Focus the first element defined in above html.
            focus: true,
            setup: function() {
              this.getElement()[ headings && headings.count() ? 'hide' : 'show' ]();
            }
          } ]
        }
      }

      function modifiedOnOk() {
        var data = {},
          plugin = CKEDITOR.plugins.link;

        // Collect data from fields.
        this.commitContent( data );

        // if there is a heading id and not an anchor id, the user is creating
        // a link to a heading.  We set value of the heading id to the anchor.id
        // property so it will create the new link without having to
        // modify the plugin further
        if (data.heading.id && !data.anchor.id)
          data.anchor.id = data.heading.id;

        var selection = editor.getSelection(),
          attributes = plugin.getLinkAttributes( editor, data ),
          bm,
          nestedLinks;

        if ( !this._.selectedElement ) {
          var range = selection.getRanges()[ 0 ],
            text;

          // Use link URL as text with a collapsed cursor.
          if ( range.collapsed ) {
            // Short mailto link text view (#5736).
            text = new CKEDITOR.dom.text( data.linkText || ( data.type == 'email' ?
              data.email.address : attributes.set[ 'data-cke-saved-href' ] ), editor.document );
            range.insertNode( text );
            range.selectNodeContents( text );
          } else if ( initialLinkText !== data.linkText ) {
            text = new CKEDITOR.dom.text( data.linkText, editor.document );

            // Shrink range to preserve block element.
            range.shrink( CKEDITOR.SHRINK_TEXT );

            // Use extractHtmlFromRange to remove markup within the selection. Also this method is a little
            // smarter than range#deleteContents as it plays better e.g. with table cells.
            editor.editable().extractHtmlFromRange( range );

            range.insertNode( text );
          }

          // Editable links nested within current range should be removed, so that the link is applied to whole selection.
          nestedLinks = range._find( 'a' );

          for	( var i = 0; i < nestedLinks.length; i++ ) {
            nestedLinks[ i ].remove( true );
          }

          // Apply style.
          var style = new CKEDITOR.style( {
            element: 'a',
            attributes: attributes.set
          } );

          style.type = CKEDITOR.STYLE_INLINE; // need to override... dunno why.
          style.applyToRange( range, editor );
          range.select();
        } else {
          // We're only editing an existing link, so just overwrite the attributes.
          var element = this._.selectedElement,
            href = element.data( 'cke-saved-href' ),
            textView = element.getHtml(),
            newText;

          element.setAttributes( attributes.set );
          element.removeAttributes( attributes.removed );

          if ( data.linkText && initialLinkText != data.linkText ) {
            // Display text has been changed.
            newText = data.linkText;
          } else if ( href == textView || data.type == 'email' && textView.indexOf( '@' ) != -1 ) {
            // Update text view when user changes protocol (#4612).
            // Short mailto link text view (#5736).
            newText = data.type == 'email' ? data.email.address : attributes.set[ 'data-cke-saved-href' ];
          }

          if ( newText ) {
            element.setText( newText );
            // We changed the content, so need to select it again.
            selection.selectElement( element );
          }

          delete this._.selectedElement;
        }
      }
    }
  });
})();
<|endoftext|>


<commit_before>'use strict';

var redis = require('redis');

module.exports = function(plugin, manager, app) {
  /**
   * Listens for post-start-component
   * - Creates a proxy record via redis for components with proxy definitions.
   */
  app.on('post-start-component', function(component) {
    if (component.proxy) {
      var c = app.docker.getContainer(component.cid);
      c.inspect(function(err, data) {
        for (var x in component.proxy) {
          var proxy = component.proxy[x];
          var client = redis.createClient(component.app.kconfig.redis.port, component.app.kconfig.redis.host);
          var hostname = proxy.default ? app.appdomain : component.hostname;
          var rkey = 'frontend:' + hostname;

          console.log(component.app.kconfig.redis);
          console.log(hostname);
          console.log(rkey);
          console.log(port);

          if (data && data.NetworkSettings.Ports[proxy.port]) {
            var port = data.NetworkSettings.Ports[proxy.port][0].HostPort;
            var dst = 'http://' +component.app.kconfig.dockerHost + ':' + port;

            client.multi()
              .del(rkey)
              .rpush(rkey, component.cname)
              .rpush(rkey, dst)
              .exec(function (err, replies) {
                if (err) throw err;
                client.quit();
              });
          }
        }
      });
    }
  });

  /**
   * Listens for post-start-component
   * - Removes proxy records via redis.
   */
  app.on('post-remove-component', function(component) {
    // Setup the hipache proxy via redis
    if (component.proxy) {
      var c = app.docker.getContainer(component.cid);
      c.inspect(function(err, data) {
        for (var x in component.proxy) {
          var proxy = component.proxy[x];
          var client = redis.createClient(component.app.kconfig.redis.port, component.app.kconfig.redis.host);
          var hostname = proxy.default ? app.appdomain : component.hostname;
          var rkey = 'frontend:' + hostname;

          client.del(rkey, function (err, replies) {
            if (err) throw err;
            client.quit();
          });
        }
      });
    }
  });
};
<commit_msg>removed debug code<commit_after>'use strict';

var redis = require('redis');

module.exports = function(plugin, manager, app) {
  /**
   * Listens for post-start-component
   * - Creates a proxy record via redis for components with proxy definitions.
   */
  app.on('post-start-component', function(component) {
    if (component.proxy) {
      var c = app.docker.getContainer(component.cid);
      c.inspect(function(err, data) {
        for (var x in component.proxy) {
          var proxy = component.proxy[x];
          var client = redis.createClient(component.app.kconfig.redis.port, component.app.kconfig.redis.host);
          var hostname = proxy.default ? app.appdomain : component.hostname;
          var rkey = 'frontend:' + hostname;

          if (data && data.NetworkSettings.Ports[proxy.port]) {
            var port = data.NetworkSettings.Ports[proxy.port][0].HostPort;
            var dst = 'http://' +component.app.kconfig.dockerHost + ':' + port;

            client.multi()
              .del(rkey)
              .rpush(rkey, component.cname)
              .rpush(rkey, dst)
              .exec(function (err, replies) {
                if (err) throw err;
                client.quit();
              });
          }
        }
      });
    }
  });

  /**
   * Listens for post-start-component
   * - Removes proxy records via redis.
   */
  app.on('post-remove-component', function(component) {
    // Setup the hipache proxy via redis
    if (component.proxy) {
      var c = app.docker.getContainer(component.cid);
      c.inspect(function(err, data) {
        for (var x in component.proxy) {
          var proxy = component.proxy[x];
          var client = redis.createClient(component.app.kconfig.redis.port, component.app.kconfig.redis.host);
          var hostname = proxy.default ? app.appdomain : component.hostname;
          var rkey = 'frontend:' + hostname;

          client.del(rkey, function (err, replies) {
            if (err) throw err;
            client.quit();
          });
        }
      });
    }
  });
};
<|endoftext|>


<commit_before>var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Instructables',
    version:'0.1',
    prepareImgLinks:function (callback) {
        var res = [];
        hoverZoom.urlReplace(res,
            'a[href*=".THUMB."], img[src*=".THUMB."], img[src*=".SQUARE."]',
            /(SQUARE|THUMB)/,
            'LARGE'
        );
        callback($(res));
    }
});
<commit_msg>Fix for plug-in: instructables.js<commit_after>var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Instructables',
    version:'0.2',
    prepareImgLinks:function (callback) {
        var res = [];

        //url sample:
        //https://content.instructables.com/FIU/8ERO/K8CH5ZJM/FIU8EROK8CH5ZJM.LARGE.jpg?auto=webp&frame=1&width=384&height=1024&fit=bounds -> https://content.instructables.com/FIU/8ERO/K8CH5ZJM/FIU8EROK8CH5ZJM.jpg
        //https://cdn.instructables.com/ORIG/FLZ/S0HR/JZJ0TDDE/FLZS0HRJZJ0TDDE.jpg?crop=1%3A1&width=48 -> https://cdn.instructables.com/ORIG/FLZ/S0HR/JZJ0TDDE/FLZS0HRJZJ0TDDE.jpg

        // deal with lazy-loading using data-src
        $('img[data-src]').each(function() {

            var link = $(this);
            link = $(link);

            var re = /(((?:.*?)(?:\/[A-Z\d]+){1,}\.).*?(bmp|gifv?|jpe?g|png|svg|webp)).*/
            var m = this.dataset.src.match(re);
            if (m) {
                var backupUrl = m[1]; // original url without resize, to be tried iff fullsizeUrl does not exist
                var fullsizeUrl = m[2] + m[3];
                if (fullsizeUrl != undefined) {

                    cLog('fullsizeUrl:' + fullsizeUrl);

                    if (link.data().hoverZoomSrc == undefined) { link.data().hoverZoomSrc = [] }
                    if (link.data().hoverZoomSrc.indexOf(fullsizeUrl) == -1) {
                        link.data().hoverZoomSrc.push(fullsizeUrl);
                        link.data().hoverZoomSrc.reverse(); // 1st place
                    }

                    if (link.data().hoverZoomSrc.indexOf(backupUrl) == -1) {
                        link.data().hoverZoomSrc.push(backupUrl); // 2nd place
                    }

                    link.addClass('hoverZoomLink');
                    res.push(link);
                }
            }
        });

        callback($(res));
    }
});<|endoftext|>


<commit_before>/**
 * Created by kdbanman on 2/29/16.
 */
<commit_msg>start to define how the Comms module will look<commit_after>/**
 * Created by kdbanman on 2/29/16.
 */

var socket = require('socket.io');
var Util = require('polyball/shared/Util');
var Client = require('polyball/shared/model/Client');

/**
 *
 * @param {{
 *   httpServer: Object,
 *   model: Model
 * }}config
 * @constructor
 */
var Comms = function (config) {
    var io = socket(config.httpServer);
    var model = config.model;

    io.on('connection', function (clientSocket) {

        clientSocket.on('disconnect', function () {

        });

        clientSocket.on('vote', function () {
            //broadcast to listeners (like the engine).
            //listeners.notify('vote', voteData);
        });

        // create a spectator with the socket in the model
        var client = new Client({
            name: Util.randomUsername(),
            socket: clientSocket
            });

        model.addSpectator(client);
    });

    this.broadcastSnapshot = function () {
        // get spectators (get their clients)
        // get players (same)

        // for each client, socket.emit('newSnapshot', snapshot);

    };

};

module.export = Comms;<|endoftext|>


<commit_before>"use strict";

var _ = require('lodash');
//var Physics = require('physicsjs');
var Util = require('polyball/shared/Util');
var Ball = require('polyball/shared/model/Ball');
var Spectator = require('polyball/shared/model/Spectator');
//var Paddle = require('polyball/model/Paddle');
//var Player = require('polyball/model/Player');

var IDGenerator = function () {
    var nextID = 0;

    this.nextID = function () {
        return nextID++;
    };
};

/**
 * Search an array for an element by its id.
 * @template T
 * @param {T[]} array The array to search
 * @param {Number} id The id of the desired element (in field `element.id`)
 * @returns {T} The identified array element (or undefined).
 */
var findByID = function (array, id) {
    return _.find(array, function (element) { return element.id === id; });
};

/**
 * Search for an element by id in an array, and assign all properties from newState if found.
 * @param {Array} array The array to search.
 * @param {Number} id The id of the desired element (in field `element.id`)
 * @param {Object} newState The new state for the update (object structure must match source structure).
 */
var updateByID = function (array, id, newState) {
    if (newState.id != null) {
        delete newState.id;
    }

    var element = findByID(array, id);
    if (element != null) {
        _.assign(element, newState);
    }
};

/**
 * Remove element from array by its id.
 * @param {Array} array The array to search
 * @param {Number} id The id of the desired element (in field `element.id`)
 */
var removeByID = function (array, id) {
    _.remove(array, function (element) { return element.id === id; });
};

var Model = function () {
    var ids = new IDGenerator();

    //var players = [];
    var spectators = [];

    //var world = Physics();

    var balls = [];
    //var powerups = [];
    //var election = undefined;

    //
    //             BALLS
    //             -----
    //

    /**
     * Add a ball at the centre of the arena with a random velocity.
     *
     * @return {Ball} The new Ball.
     */
    this.addBall  = function () {
        var ballConfig = {
            id: ids.nextID(),
            x: 0,
            y: 0,
            vx: 0.1,
            vy: 0.1,
            radius: 1
        };

        var ball = new Ball(ballConfig);
        balls.push(ball);

        return ball;
    };

    /**
     * @param {Number} id
     * @return {Ball} Ball identified by id.
     */
    this.getBall = function (id) {
        return findByID(balls, id);
    };

    /**
     * @param {Number} id
     * @returns {boolean} True iff the model has the ball identified by id.
     */
    this.hasBall = function (id) {
        return this.getBall(id) != null;
    };

    /**
     * @returns {Number} The number of balls in the model.
     */
    this.ballCount = function () {
        return balls.length;
    };

    /**
     * Change the state of the identified ball to match the supplied state.
     *
     * @param {Number} id
     * @param newState (See Ball constructor config). **id field is ignored!**
     */
    this.updateBall = function (id, newState) {
        if (newState.id != null) {
            delete newState.id;
        }

        var ball = this.getBall(id);
        if (ball != null) {
            _.assign(ball, newState);
        }
    };

    /**
     * Delete the identified ball from the model.
     *
     * @param {Number} id
     */
    this.deleteBall = function (id) {
        _.remove(balls, function (ball) { return ball.id === id; });
    };

    //
    //             SPECTATORS
    //             ----------
    //

    /**
     * Adds a spectator to the model.
     * @param socket The socket connecting the server to the spectator client.
     * @return {Spectator} The new Spectator.
     */
    this.addSpectator = function (socket) {
        var spectatorConfig = {
            name: Util.randomUsername(),
            id: ids.nextID(),
            socket: socket
        };

        var spectator = new Spectator(spectatorConfig);
        spectators.push(spectator);

        return spectator;
    };

    /**
     * @param {Number} id The id of the spectator.
     * @return {Spectator} The spectator from the model (undefined if not found).
     */
    this.getSpectator = function (id) {
        return findByID(spectators, id);
    };

    this.queueSpectatorToPlay = function (id) {
        var spectator = this.getSpectator(id);

        if (spectator != null) {
            spectator.queued = true;
        }
    };

    this.unqueueSpectator = function (id) {
        var spectator = this.getSpectator(id);

        if (spectator != null) {
            spectator.queued = false;
        }
    };

    /**
     * @param {Number} id
     * @returns {boolean} True iff the model has the spectator identified by id.
     */
    this.hasSpectator = function (id) {
        return this.getSpectator(id) != null;
    };

    /**
     * @returns {Number} The number of spectators in the model.
     */
    this.spectatorCount = function () {
        return spectators.length;
    };

    /**
     * Change the state of the identified spectator to match the supplied state.
     *
     * @param {Number} id
     * @param newState (See Spectator constructor config). **id field is ignored!**
     */
    this.updateSpectator = function (id, newState) {
        updateByID(spectators, id, newState);
    };

    /**
     * Delete the identified spectator from the model.
     *
     * @param {Number} id
     */
    this.deleteSpectator = function (id) {
        removeByID(spectators, id);
    };

    //
    //             PLAYERS
    //             -------
    //

    this.convertQueuedSpectatorsToPlayers = function () {

    };

    /**
     * @return
     */
    this.getSnapshot = function() {

    };
};

module.exports = Model;

<commit_msg>add and remove balls from physics world.<commit_after>"use strict";

var _ = require('lodash');
var Physics = require('physicsjs');
var Logger = require('polyball/shared/Loggers');
var Util = require('polyball/shared/Util');
var Ball = require('polyball/shared/model/Ball');
var Spectator = require('polyball/shared/model/Spectator');
//var Paddle = require('polyball/model/Paddle');
//var Player = require('polyball/model/Player');

var IDGenerator = function () {
    var nextID = 0;

    this.nextID = function () {
        return nextID++;
    };
};

/**
 * Search an array for an element by its id.
 * @template T
 * @param {T[]} array The array to search
 * @param {Number} id The id of the desired element (in field `element.id`)
 * @returns {T} The identified array element (or undefined).
 */
var findByID = function (array, id) {
    return _.find(array, function (element) { return element.id === id; });
};

/**
 * Search for an element by id in an array, and assign all properties from newState if found.
 * @param {Array} array The array to search.
 * @param {Number} id The id of the desired element (in field `element.id`)
 * @param {Object} newState The new state for the update (object structure must match source structure).
 */
var updateByID = function (array, id, newState) {
    if (newState.id != null) {
        delete newState.id;
    }

    var element = findByID(array, id);
    if (element != null) {
        _.assign(element, newState);
    }
};

/**
 * Remove element from array by its id.
 * @template T
 * @param {Array} array The array to search
 * @param {Number} id The id of the desired element (in field `element.id`)
 * @return {T} The removed object, null if not found.
 */
var removeByID = function (array, id) {
    var removed = _.remove(array, function (element) { return element.id === id; });
    if (removed.length > 1) {
        Logger.error("More than one object of id " + id + " found in array " + array);
    }

    return removed[0];
};

var Model = function () {
    var ids = new IDGenerator();

    //var players = [];
    var spectators = [];

    var world = Physics();

    var balls = [];
    //var powerups = [];
    //var election = undefined;

    //
    //             BALLS
    //             -----
    //

    /**
     * Add a ball at the centre of the arena with a random velocity.
     *
     * @return {Ball} The new Ball.
     */
    this.addBall  = function () {
        var ballConfig = {
            id: ids.nextID(),
            x: 0,
            y: 0,
            vx: 0.1,
            vy: 0.1,
            radius: 1
        };

        var ball = new Ball(ballConfig);
        balls.push(ball);

        world.addBody(ball);

        return ball;
    };

    /**
     * @param {Number} id
     * @return {Ball} Ball identified by id.
     */
    this.getBall = function (id) {
        return findByID(balls, id);
    };

    /**
     * @param {Number} id
     * @returns {boolean} True iff the model has the ball identified by id.
     */
    this.hasBall = function (id) {
        return this.getBall(id) != null;
    };

    /**
     * @returns {Number} The number of balls in the model.
     */
    this.ballCount = function () {
        return balls.length;
    };

    /**
     * Change the state of the identified ball to match the supplied state.
     *
     * @param {Number} id
     * @param newState (See Ball constructor config). **id field is ignored!**
     */
    this.updateBall = function (id, newState) {
        if (newState.id != null) {
            delete newState.id;
        }

        var ball = this.getBall(id);
        if (ball != null) {
            _.assign(ball, newState);
        }
    };

    /**
     * Delete the identified ball from the model.
     *
     * @param {Number} id
     */
    this.deleteBall = function (id) {
        var ball = _.remove(balls, function (ball) { return ball.id === id; });

        if (ball != null) {
            world.removeBody(ball);
        }
    };

    //
    //             SPECTATORS
    //             ----------
    //

    /**
     * Adds a spectator to the model.
     * @param socket The socket connecting the server to the spectator client.
     * @return {Spectator} The new Spectator.
     */
    this.addSpectator = function (socket) {
        var spectatorConfig = {
            name: Util.randomUsername(),
            id: ids.nextID(),
            socket: socket
        };

        var spectator = new Spectator(spectatorConfig);
        spectators.push(spectator);

        return spectator;
    };

    /**
     * @param {Number} id The id of the spectator.
     * @return {Spectator} The spectator from the model (undefined if not found).
     */
    this.getSpectator = function (id) {
        return findByID(spectators, id);
    };

    this.queueSpectatorToPlay = function (id) {
        var spectator = this.getSpectator(id);

        if (spectator != null) {
            spectator.queued = true;
        }
    };

    this.unqueueSpectator = function (id) {
        var spectator = this.getSpectator(id);

        if (spectator != null) {
            spectator.queued = false;
        }
    };

    /**
     * @param {Number} id
     * @returns {boolean} True iff the model has the spectator identified by id.
     */
    this.hasSpectator = function (id) {
        return this.getSpectator(id) != null;
    };

    /**
     * @returns {Number} The number of spectators in the model.
     */
    this.spectatorCount = function () {
        return spectators.length;
    };

    /**
     * Change the state of the identified spectator to match the supplied state.
     *
     * @param {Number} id
     * @param newState (See Spectator constructor config). **id field is ignored!**
     */
    this.updateSpectator = function (id, newState) {
        updateByID(spectators, id, newState);
    };

    /**
     * Delete the identified spectator from the model.
     *
     * @param {Number} id
     */
    this.deleteSpectator = function (id) {
        removeByID(spectators, id);
    };

    //
    //             PLAYERS
    //             -------
    //

    this.convertQueuedSpectatorsToPlayers = function () {

    };

    /**
     * @return
     */
    this.getSnapshot = function() {

    };
};

module.exports = Model;

<|endoftext|>


<commit_before>'use strict';

// Create canvas game board inside #canvas-wrap
const canvas = document.createElement('canvas');
document.getElementById('canvas-wrap').appendChild(canvas);
const context = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

// Get all the html elements needed
const startInstructionsBox = document.querySelector('.start');
const winnerBox = document.querySelector('.winner');
const restartButton = document.querySelector('.restart');
const playerOneScoreBoard = document.querySelector('.player-one');
const playerTwoScoreBoard = document.querySelector('.player-two');

// helper functions
function randomInt (min, max) {
  return Math.random() * (max - min) + min;
}
function randomDirection (left, right) {
  return Math.random() < 0.5 ? left : right;
}

// Create ball
let ball = {
  radius: 20,
  color: '#ffffff',
  x: canvas.width / 2,
  y: canvas.height / 2,
  velocity: {
    x: randomDirection(-4, 4),
    y: randomInt(-4, 4)
  }
};

// Create player rackets
const playerWidth = 30;
const playerHeight = 170;

class Player {
  constructor (color, x, y) {
    this.color = color;
    this.backgroundColor = '#000000';
    this.x = x;
    this.y = y;
    this.height = playerHeight;
    this.width = playerWidth;
    this.velocity = { up: -7, down: 7 };
  }
}
const playerOne = new Player('#7ED321', 0, canvas.height / 2 - playerHeight / 2);
const playerTwo = new Player('#F8E71C', canvas.width - playerWidth, canvas.height / 2 - playerHeight / 2);

let playerOneButtons = {
  'left': false,
  'right': false
};
let playerTwoButtons = {
  'left': false,
  'right': false
};

// If the players press the valid button, set button to true
window.addEventListener('keydown', (event) => {
  if (event.keyCode === 65) playerOneButtons.left = true;
  if (event.keyCode === 68) playerOneButtons.right = true;
  if (event.keyCode === 37) playerTwoButtons.left = true;
  if (event.keyCode === 39) playerTwoButtons.right = true;
});

// Listen to the players keyup, set button to false
window.addEventListener('keyup', (event) => {
  if (event.keyCode === 65) playerOneButtons.left = false;
  if (event.keyCode === 68) playerOneButtons.right = false;
  if (event.keyCode === 37) playerTwoButtons.left = false;
  if (event.keyCode === 39) playerTwoButtons.right = false;
});

// If press spacebar the game begin
let gameRunning = false;
let ballStart = document.querySelector('.ball-button');
ballStart.addEventListener('click', (event) => {
  startInstructionsBox.classList.remove('show');
  gameRunning = true;
});

// Score counter
let scorePlayerOne = 0;
let scorePlayerTwo = 0;
let hasPlayerScored = false;

function drawGame () {
  // draw board background
  context.fillStyle = "rgba(0, 0, 0, 0.5)";
  context.clearRect(0, 0, canvas.width, canvas.height);

  // draw center line
  context.fillStyle = '#000';
  context.fillRect(canvas.width / 2 - 1.5, 0, 3, canvas.height);

  // draw player one
  context.beginPath();
  context.fillStyle = playerOne.backgroundColor;
  context.fillRect(playerOne.x, playerOne.y, playerOne.width, playerOne.height);
  context.fillStyle = playerOne.color;
  context.fillRect(playerOne.x + 28, playerOne.y, playerOne.width / 8, playerOne.height);

  // draw player two
  context.beginPath();
  context.fillStyle = playerTwo.backgroundColor;
  context.fillRect(playerTwo.x, playerTwo.y, playerTwo.width, playerTwo.height);
  context.fillStyle = playerTwo.color;
  context.fillRect(playerTwo.x - 2, playerTwo.y, playerTwo.width / 8, playerTwo.height)

  // draw ball
  context.beginPath();
  context.fillStyle = ball.color;
  context.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI, false);
  context.fill();
}

function movePlayers () {
  // players movement - move up and down on buttons click
  if (playerOneButtons.left) {
    if (playerOne.y + playerOne.height < canvas.height) {
      playerOne.y += playerOne.velocity.down;
    }
  }
  if (playerOneButtons.right) {
    if (playerOne.y > 0) {
      playerOne.y += playerOne.velocity.up;
    }
  }
  if (playerTwoButtons.left) {
    if (playerTwo.y + playerTwo.height < canvas.height) {
      playerTwo.y += playerTwo.velocity.down;
    }
  }
  if (playerTwoButtons.right) {
    if (playerTwo.y > 0) {
      playerTwo.y += playerTwo.velocity.up;
    }
  }
}

function moveBall () {
  // Update the x and y of the ball cordinates based on the velocity
  // TODO: calculate with delta time
  ball.x += ball.velocity.x;
  ball.y += ball.velocity.y;
  // Check if the ball collides at the bottom
  if (ball.y + ball.radius > canvas.height) {
    ball.velocity.y *= -1;
  }
  // Check if the ball collides at the top
  if (ball.y <= ball.radius) {
    ball.velocity.y *= -1;
  }
  // Check if the ball collides with long side on player one
  if (ball.y + ball.radius >= playerOne.y && ball.y - ball.radius <= playerOne.y + playerOne.height && ball.x - playerWidth <= ball.radius) {
    ball.velocity.x = Math.abs(ball.velocity.x - 1);
    ball.color = playerOne.color;
  }
  // Check if ball collides with player short sides on player one
  // if (ball.y + ball.radius === playerOne.y && ball.x + ball.radius <= playerOne.x + playerOne.width) {
  //     ball.velocity.x = Math.abs(ball.velocity.x - 1);
  // }

  // Check if the ball collides with long side on player two
  if (ball.y + ball.radius >= playerTwo.y && ball.y - ball.radius <= playerTwo.y + playerTwo.height && ball.x + ball.radius + playerWidth >= canvas.width) {
    ball.velocity.x = -ball.velocity.x - 1;
    ball.color = playerTwo.color;
  }
}

function resetBall () {

  // put ball back in the center to get ready for a new round
  ball.x = canvas.width / 2;
  ball.y = canvas.height / 2;
  ball.velocity.x = randomDirection(-4, 4);
  ball.velocity.y = randomInt(-4, 4);
  ball.color = "#FFFFFF";
}

function countScores () {
  // Start new round if one of the players misses the ball
  if (ball.x + ball.radius < 0 || ball.x - ball.radius > canvas.width) {
    if (ball.x + ball.radius < 0) {
      // add score to player two on score board
      scorePlayerTwo++;
      playerTwoScoreBoard.innerText = scorePlayerTwo;
    } else {
      // add score to player one on score board
      scorePlayerOne++;
      playerOneScoreBoard.innerText = scorePlayerOne;
    }
    gameRunning = false;
    return true;
  } else {
    return false;
  }
}

function isGameFinished () {

  // Check if one of the players has 5 points, then the game is finished
  if (scorePlayerOne >= 1) {
    winnerBox.classList.add('show');
    winnerBox.classList.add('green');
    winnerBox.firstChild.innerText = 'Green player is the winner!';
    return true;
  } else if (scorePlayerTwo >= 1) {
    winnerBox.classList.add('show');
    winnerBox.classList.add('yellow');
    winnerBox.firstChild.innerText = 'Yellow  player is the winner!';
    return true;
  } else {
    return false;
  }
}

function mainLoop () {
  movePlayers();

  if (gameRunning) {
    moveBall();
    hasPlayerScored = countScores();
  }
  if (!isGameFinished()) {
    if (hasPlayerScored) {
      resetBall();
    }
    window.requestAnimationFrame(mainLoop);
  } else {
    gameRunning = false;
  }
  drawGame();
}
mainLoop();

function resetGame () {
  // Reset positions and scores when starting a new game
  gameRunning = false;
  scorePlayerOne = 0;
  scorePlayerTwo = 0;
  playerOneScoreBoard.innerText = scorePlayerOne;
  playerTwoScoreBoard.innerText = scorePlayerTwo;

  winnerBox.classList.remove('show');
  winnerBox.classList.remove('green');
  winnerBox.classList.remove('yellow');
  startInstructionsBox.classList.add('show');

  resetBall();
  // reset players
  playerOne.y = canvas.height / 2 - playerHeight / 2;
  playerTwo.y = canvas.height / 2 - playerHeight / 2;
}

restartButton.addEventListener('click', function (event) {
  resetGame();
  mainLoop();
});
<commit_msg>Changed winner count<commit_after>'use strict';

// Create canvas game board inside #canvas-wrap
const canvas = document.createElement('canvas');
document.getElementById('canvas-wrap').appendChild(canvas);
const context = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

// Get all the html elements needed
const startInstructionsBox = document.querySelector('.start');
const winnerBox = document.querySelector('.winner');
const restartButton = document.querySelector('.restart');
const playerOneScoreBoard = document.querySelector('.player-one');
const playerTwoScoreBoard = document.querySelector('.player-two');

// helper functions
function randomInt (min, max) {
  return Math.random() * (max - min) + min;
}
function randomDirection (left, right) {
  return Math.random() < 0.5 ? left : right;
}

// Create ball
let ball = {
  radius: 20,
  color: '#ffffff',
  x: canvas.width / 2,
  y: canvas.height / 2,
  velocity: {
    x: randomDirection(-4, 4),
    y: randomInt(-4, 4)
  }
};

// Create player rackets
const playerWidth = 30;
const playerHeight = 170;

class Player {
  constructor (color, x, y) {
    this.color = color;
    this.backgroundColor = '#000000';
    this.x = x;
    this.y = y;
    this.height = playerHeight;
    this.width = playerWidth;
    this.velocity = { up: -7, down: 7 };
  }
}
const playerOne = new Player('#7ED321', 0, canvas.height / 2 - playerHeight / 2);
const playerTwo = new Player('#F8E71C', canvas.width - playerWidth, canvas.height / 2 - playerHeight / 2);

let playerOneButtons = {
  'left': false,
  'right': false
};
let playerTwoButtons = {
  'left': false,
  'right': false
};

// If the players press the valid button, set button to true
window.addEventListener('keydown', (event) => {
  if (event.keyCode === 65) playerOneButtons.left = true;
  if (event.keyCode === 68) playerOneButtons.right = true;
  if (event.keyCode === 37) playerTwoButtons.left = true;
  if (event.keyCode === 39) playerTwoButtons.right = true;
});

// Listen to the players keyup, set button to false
window.addEventListener('keyup', (event) => {
  if (event.keyCode === 65) playerOneButtons.left = false;
  if (event.keyCode === 68) playerOneButtons.right = false;
  if (event.keyCode === 37) playerTwoButtons.left = false;
  if (event.keyCode === 39) playerTwoButtons.right = false;
});

// If press spacebar the game begin
let gameRunning = false;
let ballStart = document.querySelector('.ball-button');
ballStart.addEventListener('click', (event) => {
  startInstructionsBox.classList.remove('show');
  gameRunning = true;
});

// Score counter
let scorePlayerOne = 0;
let scorePlayerTwo = 0;
let hasPlayerScored = false;

function drawGame () {
  // draw board background
  context.fillStyle = "rgba(0, 0, 0, 0.5)";
  context.clearRect(0, 0, canvas.width, canvas.height);

  // draw center line
  context.fillStyle = '#000';
  context.fillRect(canvas.width / 2 - 1.5, 0, 3, canvas.height);

  // draw player one
  context.beginPath();
  context.fillStyle = playerOne.backgroundColor;
  context.fillRect(playerOne.x, playerOne.y, playerOne.width, playerOne.height);
  context.fillStyle = playerOne.color;
  context.fillRect(playerOne.x + 28, playerOne.y, playerOne.width / 8, playerOne.height);

  // draw player two
  context.beginPath();
  context.fillStyle = playerTwo.backgroundColor;
  context.fillRect(playerTwo.x, playerTwo.y, playerTwo.width, playerTwo.height);
  context.fillStyle = playerTwo.color;
  context.fillRect(playerTwo.x - 2, playerTwo.y, playerTwo.width / 8, playerTwo.height)

  // draw ball
  context.beginPath();
  context.fillStyle = ball.color;
  context.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI, false);
  context.fill();
}

function movePlayers () {
  // players movement - move up and down on buttons click
  if (playerOneButtons.left) {
    if (playerOne.y + playerOne.height < canvas.height) {
      playerOne.y += playerOne.velocity.down;
    }
  }
  if (playerOneButtons.right) {
    if (playerOne.y > 0) {
      playerOne.y += playerOne.velocity.up;
    }
  }
  if (playerTwoButtons.left) {
    if (playerTwo.y + playerTwo.height < canvas.height) {
      playerTwo.y += playerTwo.velocity.down;
    }
  }
  if (playerTwoButtons.right) {
    if (playerTwo.y > 0) {
      playerTwo.y += playerTwo.velocity.up;
    }
  }
}

function moveBall () {
  // Update the x and y of the ball cordinates based on the velocity
  // TODO: calculate with delta time
  ball.x += ball.velocity.x;
  ball.y += ball.velocity.y;
  // Check if the ball collides at the bottom
  if (ball.y + ball.radius > canvas.height) {
    ball.velocity.y *= -1;
  }
  // Check if the ball collides at the top
  if (ball.y <= ball.radius) {
    ball.velocity.y *= -1;
  }
  // Check if the ball collides with long side on player one
  if (ball.y + ball.radius >= playerOne.y && ball.y - ball.radius <= playerOne.y + playerOne.height && ball.x - playerWidth <= ball.radius) {
    ball.velocity.x = Math.abs(ball.velocity.x - 1);
    ball.color = playerOne.color;
  }
  // Check if ball collides with player short sides on player one
  // if (ball.y + ball.radius === playerOne.y && ball.x + ball.radius <= playerOne.x + playerOne.width) {
  //     ball.velocity.x = Math.abs(ball.velocity.x - 1);
  // }

  // Check if the ball collides with long side on player two
  if (ball.y + ball.radius >= playerTwo.y && ball.y - ball.radius <= playerTwo.y + playerTwo.height && ball.x + ball.radius + playerWidth >= canvas.width) {
    ball.velocity.x = -ball.velocity.x - 1;
    ball.color = playerTwo.color;
  }
}

function resetBall () {

  // put ball back in the center to get ready for a new round
  ball.x = canvas.width / 2;
  ball.y = canvas.height / 2;
  ball.velocity.x = randomDirection(-4, 4);
  ball.velocity.y = randomInt(-4, 4);
  ball.color = "#FFFFFF";
}

function countScores () {
  // Start new round if one of the players misses the ball
  if (ball.x + ball.radius < 0 || ball.x - ball.radius > canvas.width) {
    if (ball.x + ball.radius < 0) {
      // add score to player two on score board
      scorePlayerTwo++;
      playerTwoScoreBoard.innerText = scorePlayerTwo;
    } else {
      // add score to player one on score board
      scorePlayerOne++;
      playerOneScoreBoard.innerText = scorePlayerOne;
    }
    gameRunning = false;
    return true;
  } else {
    return false;
  }
}

function isGameFinished () {
  // Check if one of the players has 5 points, then the game is finished
  if (scorePlayerOne >= 3) {
    winnerBox.classList.add('show');
    winnerBox.classList.add('green');
    winnerBox.firstChild.innerText = 'Green player is the winner!';
    return true;
  } else if (scorePlayerTwo >= 3) {
    winnerBox.classList.add('show');
    winnerBox.classList.add('yellow');
    winnerBox.firstChild.innerText = 'Yellow  player is the winner!';
    return true;
  } else {
    return false;
  }
}

function mainLoop () {
  movePlayers();

  if (gameRunning) {
    moveBall();
    hasPlayerScored = countScores();
  }
  if (!isGameFinished()) {
    if (hasPlayerScored) {
      resetBall();
    }
    window.requestAnimationFrame(mainLoop);
  } else {
    gameRunning = false;
  }
  drawGame();
}
mainLoop();

function resetGame () {
  // Reset positions and scores when starting a new game
  gameRunning = false;
  scorePlayerOne = 0;
  scorePlayerTwo = 0;
  playerOneScoreBoard.innerText = scorePlayerOne;
  playerTwoScoreBoard.innerText = scorePlayerTwo;

  winnerBox.classList.remove('show');
  winnerBox.classList.remove('green');
  winnerBox.classList.remove('yellow');
  startInstructionsBox.classList.add('show');

  resetBall();
  // reset players
  playerOne.y = canvas.height / 2 - playerHeight / 2;
  playerTwo.y = canvas.height / 2 - playerHeight / 2;
}

restartButton.addEventListener('click', function (event) {
  resetGame();
  mainLoop();
});
<|endoftext|>