<commit_before>
/* global: $ */
/**
 * Plugin for managing menu in full screen
 * Enabling this plug-in removes the "menu tab" triangle
 * and uses center touch/click to show/hide the menu below
 * Behavior is predicated on custom event: `brFullScreenToggled`
 * This is fired when user clicks on the fullscreen button on the menu
 */

(function addMenuToggler() {
    jQuery.extend(BookReader.defaultOptions, {
      enableMenuToggle: true
    });

    function hideArrow(br) {
      if (!br.refs || !br.refs.$BRnav) {
        return;
      }
      var $menuTab = br.refs.$BRnav.children('.js-tooltip');
      $menuTab.css('display', 'none');
    }

    function setupNavForToggle(br) {
      hideArrow(br);
      registerEventHandlers(br);
    }

    function alwaysShowNav(br) {
      hideArrow(br);
      removeToggleFromNav();
      br.showNavigation();
    }

    var removeToggleFromNav = function removeToggleFromNav(br) {
      if (!br.refs || !br.refs.$BRnav) {
        return;
      }
      var $menuTab = br.refs.$BRnav.children('.js-tooltip');
      $menuTab.css('display', 'block');
      removeEventHandlers(br);
    }

    var removeEventHandlers = function removeEventHandlers(br) {
      if (br.refs.$brPageViewEl) {
        br.refs.$brPageViewEl[0].removeEventListener('click', toggleMenuIfCenterClick, true);
      }
      if (br.refs.$brTwoPageView) {
        br.refs.$brTwoPageView[0].removeEventListener('click', toggleMenuIfCenterClick, true);
      }
    }

    var toggleNav = function toggleNav(br) {
      var menuIsShowing = br.navigationIsVisible();
      if (menuIsShowing) {
        br.hideNavigation();
      } else {
        br.showNavigation();
      }
    }

    var toggleMenuIfCenterClick = function toggleMenuIfCenterClick(br, e) {
      var book = e.currentTarget;
      if (!book) {
        return;
      }
      var bookWidth = book.offsetWidth;
      var leftOffset = book.offsetLeft
      var bookEndPageFlipArea = Math.round(bookWidth / 3);
      var leftThreshold = Math.round(bookEndPageFlipArea + leftOffset); // without it, the click area is small
      var rightThreshold = Math.round(bookWidth - bookEndPageFlipArea + leftOffset);
      var isCenterClick = (e.clientX > leftThreshold) && (e.clientX < rightThreshold);

      if (isCenterClick) {
        toggleNav(br);
        event.stopPropagation();
      }
    }

    function registerEventHandlers(br) {

      var brContainer = document.querySelector('.BRcontainer') || {};
      var mainBRWrapper = document.querySelector('.BookReader') || {};
      var firstChild = brContainer.firstChild;

      if (firstChild) {
        firstChild.addEventListener('click', function(e) {
          toggleMenuIfCenterClick(br, e);
        }, true);
      }

      var toggleAtBackgroundClick = function toggleAtBackgroundClick(e) {
        var isBackground = $(event.target).hasClass('BookReader')
          || $(event.target).hasClass('BRcontainer') /* main black theatre */
          || $(event.target).hasClass('BRemptypage') /* empty page placeholder */
          || $(event.target).hasClass('BRpageview'); /* empty page placeholder */
        if (isBackground) {
          toggleNav(br);
        }
      };
      mainBRWrapper.addEventListener('click', toggleAtBackgroundClick, true);
    }

    BookReader.prototype.initMenuToggle = function brInitMenuToggle(e) {
      var br = this;
      var hasNav = false;

      try {
        hasNav = br.navigationIsVisible();
      } catch(error) {
        hasNav = false;
      }

      if (!hasNav) {
        return;
      }

      var menuToggleEventRegister = function menuToggleEventRegister(e) {
        registerEventHandlers(br);
      };

      var setupDOMandHandlers = function setupDOMandHandlers(e) {
        setupNavForToggle(br);
      };

      var persistNav = function persistNav(e) {
        alwaysShowNav(br);
      };

      var whenToToggleNav = [
        'BookReader:2PageViewSelected',
        'BookReader:zoomIn',
        'BookReader:zoomOut',
        'BookReader:resize'
      ];

      var whenTolwaysShowNavWhen = [
        'BookReader:1PageViewSelected',
        'BookReader:3PageViewSelected'
      ];

      $(document).on(whenTolwaysShowNavWhen.join(' '), persistNav);
      $(document).on(whenToToggleNav.join(' '), menuToggleEventRegister);
      $(window).on('orientationchange', menuToggleEventRegister);
      $(document).on('BookReader:fullscreenToggled', setupDOMandHandlers);
      $(window).on('DOMContentLoaded', setupDOMandHandlers);
      setupDOMandHandlers();
    };

    BookReader.prototype.setup = (function(super_) {
      return function(options) {
        super_.call(this, options);
      };
    })(BookReader.prototype.setup);

    BookReader.prototype.init = (function(super_) {
      return function() {
        super_.call(this);
        if (this.options.enableMenuToggle) {
          this.initMenuToggle();
        }
      };
    })(BookReader.prototype.init);
  })();
  <commit_msg>Adding notes to what BookReader native properties are being used<commit_after>
/* global: $ */
/**
 * Plugin for managing menu in full screen
 * Enabling this plug-in removes the "menu tab" triangle
 * and uses center touch/click to show/hide the menu below
 * Behavior is predicated on custom event: `brFullScreenToggled`
 * This is fired when user clicks on the fullscreen button on the menu
 *
 * This uses BookReader native functions and parameters to check its UI state.
 * This includes:
 * - br.refs = (at best) ui references that are present at any given time
 * - br.navigationIsVisible() - checks using refs to confirm the navbar's presence
 *
 */

(function addMenuToggler() {
    jQuery.extend(BookReader.defaultOptions, {
      enableMenuToggle: true
    });

    function hideArrow(br) {
      if (!br.refs || !br.refs.$BRnav) {
        return;
      }
      var $menuTab = br.refs.$BRnav.children('.js-tooltip');
      $menuTab.css('display', 'none');
    }

    function setupNavForToggle(br) {
      hideArrow(br);
      registerEventHandlers(br);
    }

    function alwaysShowNav(br) {
      hideArrow(br);
      removeToggleFromNav();
      br.showNavigation();
    }

    var removeToggleFromNav = function removeToggleFromNav(br) {
      if (!br.refs || !br.refs.$BRnav) {
        return;
      }
      var $menuTab = br.refs.$BRnav.children('.js-tooltip');
      $menuTab.css('display', 'block');
      removeEventHandlers(br);
    }

    var removeEventHandlers = function removeEventHandlers(br) {
      if (br.refs.$brPageViewEl) {
        br.refs.$brPageViewEl[0].removeEventListener('click', toggleMenuIfCenterClick, true);
      }
      if (br.refs.$brTwoPageView) {
        br.refs.$brTwoPageView[0].removeEventListener('click', toggleMenuIfCenterClick, true);
      }
    }

    var toggleNav = function toggleNav(br) {
      var menuIsShowing = br.navigationIsVisible();
      if (menuIsShowing) {
        br.hideNavigation();
      } else {
        br.showNavigation();
      }
    }

    var toggleMenuIfCenterClick = function toggleMenuIfCenterClick(br, e) {
      var book = e.currentTarget;
      if (!book) {
        return;
      }
      var bookWidth = book.offsetWidth;
      var leftOffset = book.offsetLeft
      var bookEndPageFlipArea = Math.round(bookWidth / 3);
      var leftThreshold = Math.round(bookEndPageFlipArea + leftOffset); // without it, the click area is small
      var rightThreshold = Math.round(bookWidth - bookEndPageFlipArea + leftOffset);
      var isCenterClick = (e.clientX > leftThreshold) && (e.clientX < rightThreshold);

      if (isCenterClick) {
        toggleNav(br);
        event.stopPropagation();
      }
    }

    function registerEventHandlers(br) {

      var brContainer = document.querySelector('.BRcontainer') || {};
      var mainBRWrapper = document.querySelector('.BookReader') || {};
      var firstChild = brContainer.firstChild;

      if (firstChild) {
        firstChild.addEventListener('click', function(e) {
          toggleMenuIfCenterClick(br, e);
        }, true);
      }

      var toggleAtBackgroundClick = function toggleAtBackgroundClick(e) {
        var isBackground = $(event.target).hasClass('BookReader')
          || $(event.target).hasClass('BRcontainer') /* main black theatre */
          || $(event.target).hasClass('BRemptypage') /* empty page placeholder */
          || $(event.target).hasClass('BRpageview'); /* empty page placeholder */
        if (isBackground) {
          toggleNav(br);
        }
      };
      mainBRWrapper.addEventListener('click', toggleAtBackgroundClick, true);
    }

    BookReader.prototype.initMenuToggle = function brInitMenuToggle(e) {
      var br = this;
      var hasNav = false;

      try {
        hasNav = br.navigationIsVisible();
      } catch(error) {
        hasNav = false;
      }

      if (!hasNav) {
        return;
      }

      var menuToggleEventRegister = function menuToggleEventRegister(e) {
        registerEventHandlers(br);
      };

      var setupDOMandHandlers = function setupDOMandHandlers(e) {
        setupNavForToggle(br);
      };

      var persistNav = function persistNav(e) {
        alwaysShowNav(br);
      };

      var whenToToggleNav = [
        'BookReader:2PageViewSelected',
        'BookReader:zoomIn',
        'BookReader:zoomOut',
        'BookReader:resize'
      ];

      var whenTolwaysShowNavWhen = [
        'BookReader:1PageViewSelected',
        'BookReader:3PageViewSelected'
      ];

      $(document).on(whenTolwaysShowNavWhen.join(' '), persistNav);
      $(document).on(whenToToggleNav.join(' '), menuToggleEventRegister);
      $(window).on('orientationchange', menuToggleEventRegister);
      $(document).on('BookReader:fullscreenToggled', setupDOMandHandlers);
      $(window).on('DOMContentLoaded', setupDOMandHandlers);
      setupDOMandHandlers();
    };

    BookReader.prototype.setup = (function(super_) {
      return function(options) {
        super_.call(this, options);
      };
    })(BookReader.prototype.setup);

    BookReader.prototype.init = (function(super_) {
      return function() {
        super_.call(this);
        if (this.options.enableMenuToggle) {
          this.initMenuToggle();
        }
      };
    })(BookReader.prototype.init);
  })();
  <|endoftext|>


<commit_before>/*
 * raja browser client
 */
(function() {

function Raja() {
	this.delays = [];
	this.ready();
}

Raja.prototype.delay = function(url, query, listener) {
	this.delays.push([url, query, listener]);
	return this;
};

Raja.prototype.ready = function() {
	this.root = document.getElementById('raja');
	if (!this.root) return;
	var self = this;
	if (!this.pool) {
		this.pool = (this.root.getAttribute('client') || '').split(' ');
		loadScript(randomEl(this.pool) + '/socket.io/socket.io.js', function(err) {
			if (err) throw err;
			var proto = io.Manager.prototype;
			self._on = proto.on;
			self._emit = proto.emit;
			self.off = proto.off;
			self.listeners = proto.listeners;
			self.ready();
		});
		this.namespace = this.root.getAttribute('namespace') || '';
		this.room = this.root.getAttribute('room');
		if (!this.room) throw new Error("Raja cannot connect without a room url");
	}
	if (!window.io) {
		return;
	}

	var lastMod = this.root.getAttribute('last-modified');
	var now = new Date();
	if (!lastMod) {
		// work around webkit bug https://bugs.webkit.org/show_bug.cgi?id=4363
		lastMod = Date.parse(document.lastModified);
		var diff = (new Date(now.toLocaleString())).getTimezoneOffset() - now.getTimezoneOffset();
		if (!diff) diff = now.getTimezoneOffset() * 60000;
		else diff = 0;
		lastMod = lastMod - diff;
	}

	this.mtime = tryDate(lastMod) || now;
	this.root.setAttribute('last-modified', this.mtime.getTime());
	this.resources = JSON.parse(this.root.getAttribute('resources')) || {};
	for (var url in this.resources) {
		this.resources[url] = {mtime: tryDate(this.resources[url])};
	}
	var list = this.delays;
	delete this.delays;
	for (var i=0; i < list.length; i++) {
		this.on.apply(this, list[i]);
	}
};

Raja.prototype.update = function() {
	var resources = {};
	for (var url in this.resources) resources[url] = this.resources[url].mtime;
	this.root.setAttribute('resources', JSON.stringify(resources));
};

Raja.prototype.emit = function(what) {
	var args = Array.prototype.slice.call(arguments, 0);
	try {
		this._emit.apply(this, args);
	} catch(e) {
		console.error(e);
	}
	if (what == "error" && this.listeners("error").length == 0) {
		args.shift();
		console.error.apply(console, args);
	}
};

Raja.prototype.on = function(url, query, listener) {
	if (!listener && typeof query == "function") {
		listener = query;
		query = null;
	}
	if (!this.resources) {
		return this.delay(url, query, listener);
	}
	if (!this.io) this.setio();
	var self = this;
	if (url == "error") return this._on(url, listener);
	var plistener = function() {
		try {
			listener.apply(null, Array.prototype.slice.call(arguments));
		} catch(e) {
			self.emit('error', e);
		}
	};
	url = absolute(this.room, url);
	url = urlQuery(url, query);
	this._on(url, plistener);
	var resources = this.resources;
	var resource = resources[url];
	if (!resource) resource = resources[url] = {};
	if (resource.error) return;

	(function(next) {
		var reqs = resource.requests;
		if (resource.data !== undefined) {
			listener(resource.data, {
				method: "get",
				url: url,
				mtime: resource.mtime
			});
			return next();
		} else if (resource.mtime !== undefined) {
			// resource merged, just join it
			return next();
		} else if (resource.queue) {
			// resource is currently loading
			resource.queue.push(listener);
			return;
		}
		// resource has not been loaded, is not loading. Proceed
		resource.queue = [listener];
		var xhr = Raja.prototype.GET(url, function(err, obj) {
			if (err) {
				resource.error = err;
				return next(err);
			}
			var mtime = tryDate(xhr.getResponseHeader("Last-Modified"));
			resource.mtime = mtime;
			self.update();
			resource.data = obj;
			for (var i=0; i < resource.queue.length; i++) {
				try {
					resource.queue[i](obj, {
						method: "get",
						url: url,
						query: query,
						mtime: mtime
					});
				} catch(e) {
					console.error(e);
				}
			}
			delete resource.queue;
			next();
		});
	})(function(err) {
		if (err) self.emit('error', err);
	});
	return this;
};

Raja.prototype.setio = function() {
	var iohost = randomEl(this.pool);
	if (iohost.substring(0, 2) == '//') iohost = document.location.protocol + iohost;
	this.io = io(iohost + '/' + this.namespace);
	var self = this;
	this.io.once('reconnect_failed', function(err) {
		if (err) self.emit('error', err);
		self.io.removeAllListeners('message');
		setTimeout(function() {
			self.setio();
		}, 1000);
	});
	this.io.emit('join', {
		room: this.room,
		mtime: this.mtime.getTime()
	});

	this.io.on('message', function(msg) {
		if (!msg.url) return;
		var data = msg.data;
		if (data) delete msg.data;
		var stamp = msg.mtime;
		var mtime = tryDate(stamp);
		if (mtime) msg.mtime = mtime;
		var fresh = msg.mtime > self.mtime;
		if (fresh) {
			self.mtime = msg.mtime;
			self.root.setAttribute('last-modified', stamp);
		}
		var parents = msg.parents;
		parents.unshift(msg.url);
		for (var i=0; i < parents.length; i++) {
			var url = keyToUrl(parents[i]);
			var resource = self.resources[url];
			if (resource) {
				if (!resource.mtime || msg.mtime > resource.mtime) resource.mtime = msg.mtime;
				self.emit(url, data, msg);
			} else if (fresh && url != self.url) {
				// some dependency that isn't a resource - a static file ? - has changed
			}
		}
	});
};

function randomEl(arr) {
	var index = parseInt(Math.random() * arr.length);
	return arr[index];
}

function absolute(loc, url) {
	if (/^https?/i.test(url)) return url;
	if (typeof loc == "string") {
		if (!this.a) this.a = document.createElement('a');
		this.a.href = loc;
		loc = {
			href: this.a.href,
			pathname: this.a.pathname,
			protocol: this.a.protocol,
			host: this.a.host
		};
	}
	var path = loc.pathname;
	if (url.indexOf('..') == 0) {
		path = path.split('/');
		path.pop();
		url = path.join('/') + url.substring(2);
	} else if (url == '.') {
		 return loc.href;
	} else if (url.indexOf('.') == 0) {
		url = path + url.substring(1);
	} else if (url.indexOf('/') != 0) {
		var base = path.split('/');
		base.pop();
		base = base.join('/');
		url = base + '/' + url;
	}
	url = loc.protocol + '//' + loc.host + url;
	return url;
}
Raja.prototype.absolute = absolute;

function urlParams(url, params) {
	if (!params) return url;
	return url.replace(/\/:(\w+)/g, function(str, name) {
		var val = params[name];
		if (val != null) {
			delete params[name];
			return '/' + val;
		} else {
			return '/:' + name;
		}
	});
}
Raja.prototype.urlParams = urlParams;

function urlQuery(url, query) {
	if (!query) return url;
	var comps = [];
	for (var k in query) comps.push({key: k, val: query[k]});
	comps.sort(function(a, b) {
		if (a.key < b.key) return 1;
		else if (a.key > b.key) return -1;
		else return 0;
	});
	var str, list = [];
	for (var i=0; i < comps.length; i++) {
		str = encodeURIComponent(comps[i].key);
		if (comps[i].val != null) str += '=' + encodeURIComponent(comps[i].val);
		list.push(str);
	}
	if (list.length) {
		if (url.indexOf('?') > 0) url += '&';
		else url += '?';
		url += list.join('&');
	}
	return url;
}
Raja.prototype.urlQuery = urlQuery;

for (var method in {GET:1, PUT:1, POST:1, DELETE:1}) {
	Raja.prototype[method] = (function(method) { return function(url, query, body, cb) {
		if (!cb) {
			if (typeof body == "function") {
				cb = body;
				body = null;
			} else if (typeof query == "function") {
				cb = query;
				query = null;
			} else {
				cb = function(err) {
					if (err) console.error(err);
				};
			}
		}
		// consume parameters from query object
		url = urlParams(url, query);
		url = absolute(this.room, url);
		if (/^(HEAD|GET|COPY)$/i.test(method)) {
			query = query || body || {};
		} else {
			// give priority to body
			if (!body && query) {
				body = query;
				query = null;
			}
			if (body) body = JSON.stringify(body);
		}
		url = urlQuery(url, query);
		var xhr = new XMLHttpRequest();
		xhr.open(method, url, true);
		xhr.setRequestHeader('Accept', 'application/json');
		if (body) xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
		xhr.onreadystatechange = function (e) {
			if (xhr.readyState == 4) {
				var code = xhr.status;
				var response = tryJSON(xhr.responseText);
				if (code >= 200 && code < 400) {
					cb(null, response);
				} else {
					var err = new Error(response);
					err.code = code;
					cb(err);
				}
			}
		};
		xhr.send(body);
		return xhr;
	};})(method);
}

function loadScript(url, cb) {
	var script = document.createElement("script");
	script.async = true;
	script.type = 'text/javascript';
	script.charset = 'utf-8';
	script.src = url;
	function done() {
		script.onload = null;
		script.onerror = null;
		if (script.parentNode) {
			script.parentNode.removeChild(script);
		}
		script = null;
	}
	script.onload = function() {
		done();
		cb();
	};
	script.onerror = function(e) {
		done();
		cb(e);
	};
	// Circumvent IE6 bugs with base elements
	document.head.insertBefore(script, document.head.firstChild);
}
Raja.prototype.loadScript = loadScript;

function tryJSON(txt) {
	var obj;
	try {
		obj = JSON.parse(txt);
	} catch(e) {
		return txt;
	}
	return obj;
}

function tryDate(txt) {
	if (!txt) return;
	if (typeof txt == "string") {
		var num = parseInt(txt);
		if (!isNaN(num) && num.toString().length == txt.length) txt = num;
	}
	var date = new Date(txt);
	var time = date.getTime();
	if (isNaN(time)) return;
	else return date;
}

function $(str) {
	return document.querySelector(str);
}

function keyToUrl(key) {
	var find = /^(.*\s)?(https?:\/\/.+)$/.exec(key);
	if (find != null && find.length == 3) {
		if (find[2]) return find[2];
	}
	return key;
}

window.raja = new Raja();

})();
<commit_msg>Allow emit(relative) to current room<commit_after>/*
 * raja browser client
 */
(function() {

function Raja() {
	this.delays = [];
	this.ready();
}

Raja.prototype.delay = function(url, query, listener) {
	this.delays.push([url, query, listener]);
	return this;
};

Raja.prototype.ready = function() {
	this.root = document.getElementById('raja');
	if (!this.root) return;
	var self = this;
	if (!this.pool) {
		this.pool = (this.root.getAttribute('client') || '').split(' ');
		loadScript(randomEl(this.pool) + '/socket.io/socket.io.js', function(err) {
			if (err) throw err;
			var proto = io.Manager.prototype;
			self._on = proto.on;
			self._emit = proto.emit;
			self.off = proto.off;
			self.listeners = proto.listeners;
			self.ready();
		});
		this.namespace = this.root.getAttribute('namespace') || '';
		this.room = this.root.getAttribute('room');
		if (!this.room) throw new Error("Raja cannot connect without a room url");
	}
	if (!window.io) {
		return;
	}

	var lastMod = this.root.getAttribute('last-modified');
	var now = new Date();
	if (!lastMod) {
		// work around webkit bug https://bugs.webkit.org/show_bug.cgi?id=4363
		lastMod = Date.parse(document.lastModified);
		var diff = (new Date(now.toLocaleString())).getTimezoneOffset() - now.getTimezoneOffset();
		if (!diff) diff = now.getTimezoneOffset() * 60000;
		else diff = 0;
		lastMod = lastMod - diff;
	}

	this.mtime = tryDate(lastMod) || now;
	this.root.setAttribute('last-modified', this.mtime.getTime());
	this.resources = JSON.parse(this.root.getAttribute('resources')) || {};
	for (var url in this.resources) {
		this.resources[url] = {mtime: tryDate(this.resources[url])};
	}
	var list = this.delays;
	delete this.delays;
	for (var i=0; i < list.length; i++) {
		this.on.apply(this, list[i]);
	}
};

Raja.prototype.update = function() {
	var resources = {};
	for (var url in this.resources) resources[url] = this.resources[url].mtime;
	this.root.setAttribute('resources', JSON.stringify(resources));
};

Raja.prototype.emit = function(what) {
	var args = Array.prototype.slice.call(arguments, 0);
	var url = this.absolute(this.room, what);
	if (what != 'error') {
		args[0] = url;
	}
	try {
		this._emit.apply(this, args);
	} catch(e) {
		console.error(e);
	}
	if (what == "error" && this.listeners("error").length == 0) {
		args.shift();
		console.error.apply(console, args);
	}
};

Raja.prototype.on = function(url, query, listener) {
	if (!listener && typeof query == "function") {
		listener = query;
		query = null;
	}
	if (!this.resources) {
		return this.delay(url, query, listener);
	}
	if (!this.io) this.setio();
	var self = this;
	if (url == "error") return this._on(url, listener);
	var plistener = function() {
		try {
			listener.apply(null, Array.prototype.slice.call(arguments));
		} catch(e) {
			self.emit('error', e);
		}
	};
	url = absolute(this.room, url);
	url = urlQuery(url, query);
	this._on(url, plistener);
	var resources = this.resources;
	var resource = resources[url];
	if (!resource) resource = resources[url] = {};
	if (resource.error) return;

	(function(next) {
		var reqs = resource.requests;
		if (resource.data !== undefined) {
			listener(resource.data, {
				method: "get",
				url: url,
				mtime: resource.mtime
			});
			return next();
		} else if (resource.mtime !== undefined) {
			// resource merged, just join it
			return next();
		} else if (resource.queue) {
			// resource is currently loading
			resource.queue.push(listener);
			return;
		}
		// resource has not been loaded, is not loading. Proceed
		resource.queue = [listener];
		var xhr = Raja.prototype.GET(url, function(err, obj) {
			if (err) {
				resource.error = err;
				return next(err);
			}
			var mtime = tryDate(xhr.getResponseHeader("Last-Modified"));
			resource.mtime = mtime;
			self.update();
			resource.data = obj;
			for (var i=0; i < resource.queue.length; i++) {
				try {
					resource.queue[i](obj, {
						method: "get",
						url: url,
						query: query,
						mtime: mtime
					});
				} catch(e) {
					console.error(e);
				}
			}
			delete resource.queue;
			next();
		});
	})(function(err) {
		if (err) self.emit('error', err);
	});
	return this;
};

Raja.prototype.setio = function() {
	var iohost = randomEl(this.pool);
	if (iohost.substring(0, 2) == '//') iohost = document.location.protocol + iohost;
	this.io = io(iohost + '/' + this.namespace);
	var self = this;
	this.io.once('reconnect_failed', function(err) {
		if (err) self.emit('error', err);
		self.io.removeAllListeners('message');
		setTimeout(function() {
			self.setio();
		}, 1000);
	});
	this.io.emit('join', {
		room: this.room,
		mtime: this.mtime.getTime()
	});

	this.io.on('message', function(msg) {
		if (!msg.url) return;
		var data = msg.data;
		if (data) delete msg.data;
		var stamp = msg.mtime;
		var mtime = tryDate(stamp);
		if (mtime) msg.mtime = mtime;
		var fresh = msg.mtime > self.mtime;
		if (fresh) {
			self.mtime = msg.mtime;
			self.root.setAttribute('last-modified', stamp);
		}
		var parents = msg.parents;
		parents.unshift(msg.url);
		for (var i=0; i < parents.length; i++) {
			var url = keyToUrl(parents[i]);
			var resource = self.resources[url];
			if (resource) {
				if (!resource.mtime || msg.mtime > resource.mtime) resource.mtime = msg.mtime;
				self.emit(url, data, msg);
			} else if (fresh && url != self.url) {
				// some dependency that isn't a resource - a static file ? - has changed
			}
		}
	});
};

function randomEl(arr) {
	var index = parseInt(Math.random() * arr.length);
	return arr[index];
}

function absolute(loc, url) {
	if (/^https?/i.test(url)) return url;
	if (typeof loc == "string") {
		if (!this.a) this.a = document.createElement('a');
		this.a.href = loc;
		loc = {
			href: this.a.href,
			pathname: this.a.pathname,
			protocol: this.a.protocol,
			host: this.a.host
		};
	}
	var path = loc.pathname;
	if (url.indexOf('..') == 0) {
		path = path.split('/');
		path.pop();
		url = path.join('/') + url.substring(2);
	} else if (url == '.') {
		 return loc.href;
	} else if (url.indexOf('.') == 0) {
		url = path + url.substring(1);
	} else if (url.indexOf('/') != 0) {
		var base = path.split('/');
		base.pop();
		base = base.join('/');
		url = base + '/' + url;
	}
	url = loc.protocol + '//' + loc.host + url;
	return url;
}
Raja.prototype.absolute = absolute;

function urlParams(url, params) {
	if (!params) return url;
	return url.replace(/\/:(\w+)/g, function(str, name) {
		var val = params[name];
		if (val != null) {
			delete params[name];
			return '/' + val;
		} else {
			return '/:' + name;
		}
	});
}
Raja.prototype.urlParams = urlParams;

function urlQuery(url, query) {
	if (!query) return url;
	var comps = [];
	for (var k in query) comps.push({key: k, val: query[k]});
	comps.sort(function(a, b) {
		if (a.key < b.key) return 1;
		else if (a.key > b.key) return -1;
		else return 0;
	});
	var str, list = [];
	for (var i=0; i < comps.length; i++) {
		str = encodeURIComponent(comps[i].key);
		if (comps[i].val != null) str += '=' + encodeURIComponent(comps[i].val);
		list.push(str);
	}
	if (list.length) {
		if (url.indexOf('?') > 0) url += '&';
		else url += '?';
		url += list.join('&');
	}
	return url;
}
Raja.prototype.urlQuery = urlQuery;

for (var method in {GET:1, PUT:1, POST:1, DELETE:1}) {
	Raja.prototype[method] = (function(method) { return function(url, query, body, cb) {
		if (!cb) {
			if (typeof body == "function") {
				cb = body;
				body = null;
			} else if (typeof query == "function") {
				cb = query;
				query = null;
			} else {
				cb = function(err) {
					if (err) console.error(err);
				};
			}
		}
		// consume parameters from query object
		url = urlParams(url, query);
		url = absolute(this.room, url);
		if (/^(HEAD|GET|COPY)$/i.test(method)) {
			query = query || body || {};
		} else {
			// give priority to body
			if (!body && query) {
				body = query;
				query = null;
			}
			if (body) body = JSON.stringify(body);
		}
		url = urlQuery(url, query);
		var xhr = new XMLHttpRequest();
		xhr.open(method, url, true);
		xhr.setRequestHeader('Accept', 'application/json');
		if (body) xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
		xhr.onreadystatechange = function (e) {
			if (xhr.readyState == 4) {
				var code = xhr.status;
				var response = tryJSON(xhr.responseText);
				if (code >= 200 && code < 400) {
					cb(null, response);
				} else {
					var err = new Error(response);
					err.code = code;
					cb(err);
				}
			}
		};
		xhr.send(body);
		return xhr;
	};})(method);
}

function loadScript(url, cb) {
	var script = document.createElement("script");
	script.async = true;
	script.type = 'text/javascript';
	script.charset = 'utf-8';
	script.src = url;
	function done() {
		script.onload = null;
		script.onerror = null;
		if (script.parentNode) {
			script.parentNode.removeChild(script);
		}
		script = null;
	}
	script.onload = function() {
		done();
		cb();
	};
	script.onerror = function(e) {
		done();
		cb(e);
	};
	// Circumvent IE6 bugs with base elements
	document.head.insertBefore(script, document.head.firstChild);
}
Raja.prototype.loadScript = loadScript;

function tryJSON(txt) {
	var obj;
	try {
		obj = JSON.parse(txt);
	} catch(e) {
		return txt;
	}
	return obj;
}

function tryDate(txt) {
	if (!txt) return;
	if (typeof txt == "string") {
		var num = parseInt(txt);
		if (!isNaN(num) && num.toString().length == txt.length) txt = num;
	}
	var date = new Date(txt);
	var time = date.getTime();
	if (isNaN(time)) return;
	else return date;
}

function $(str) {
	return document.querySelector(str);
}

function keyToUrl(key) {
	var find = /^(.*\s)?(https?:\/\/.+)$/.exec(key);
	if (find != null && find.length == 3) {
		if (find[2]) return find[2];
	}
	return key;
}

window.raja = new Raja();

})();
<|endoftext|>


<commit_before>/*
 * FILE
 *  config.js
 *
 * PURPOSE
 *  You put things here you don't want the world to know. Then, you never commit
 *  it or submit to anywhere public. That is how to keep things like your Google
 *  password safe while sharing everything you do with the world. Don't panic.
 *  If you ever share this file with someone, the smartest "Step 2" is
 *  immediately changing all your passwords.
 *
 * LICENSE
 *  Copyright (C) 2014 Rob Colbert <rob.isConnected@gmail.com>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to
 *  deal in the Software without restriction, including without limitation the
 *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 *  sell copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 *  IN THE SOFTWARE.
 */

'use strict';

module.exports = exports = {
  'userPasswordSalt' : 'CREATE A UNIQUE PASSWORD SALT FOR YOUR USERS',
  'emailUser'        : 'ENTER YOUR GMAIL USERNAME',
  'emailPassword'    : 'ENTER YOUR GMAIL PASSWORD'
};

// AND DON'T EVER SHARE THIS FILE! IF YOU DO, CHANGE EVERYTHING AND MIGRATE
// THE USER PASSWORD SALT (TOTAL PAIN IN THE ASS). PLEASE: BE CAREFUL!!
<commit_msg>Update config.local.js<commit_after>/*
 * FILE
 *  config.local.js
 *
 * PURPOSE
 *  You put things here you don't want the world to know. Then, you never commit
 *  it or submit to anywhere public. That is how to keep things like your Google
 *  password safe while sharing everything you do with the world. Don't panic.
 *  If you ever share this file with someone, the smartest "Step 2" is
 *  immediately changing all your passwords.
 *
 * LICENSE
 *  Copyright (C) 2014 Rob Colbert <rob.isConnected@gmail.com>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to
 *  deal in the Software without restriction, including without limitation the
 *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 *  sell copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 *  IN THE SOFTWARE.
 */

'use strict';

module.exports = exports = {
  'userPasswordSalt' : 'CREATE A UNIQUE PASSWORD SALT FOR YOUR USERS',
  'emailUser'        : 'ENTER YOUR GMAIL USERNAME',
  'emailPassword'    : 'ENTER YOUR GMAIL PASSWORD'
};

// AND DON'T EVER SHARE THIS FILE! IF YOU DO, CHANGE EVERYTHING AND MIGRATE
// THE USER PASSWORD SALT (TOTAL PAIN IN THE ASS). PLEASE: BE CAREFUL!!
<|endoftext|>


<commit_before>var config = require('../../config.json');

var http = require('follow-redirects').http,
    url = require('url');

exports.redirect = function(req, res)
{
    return res.redirect(301, req.query.url);
}

exports.proxy = function(req, res)
{
    // if (!req.headers['referer'])
    //     return res.status(400).send();
    //
    // var ref_parts = url.parse(req.headers['referer'], true);
    // if (ref_parts.host != config.hostname)
    //     return res.status(400).send();

    var url_parts = url.parse(req.query.url, true);
    var query = url_parts.query;

    http.request(
        {
            host: url_parts.host,
            path: url_parts.path
        },
        (response) => {
            if (response.statusCode === 200)
            {
                res.writeHead(200, {
                    'Content-Type': response.headers['content-type']
                });
                response.pipe(res);
            }
            else
            {
                res.writeHead(response.statusCode);
                res.end();
            }
        }
    ).end();
}
<commit_msg>Restore HTTP referer check in the /redirect/proxy endpoint and do the same check in /redirect.<commit_after>var config = require('../../config.json');

var http = require('follow-redirects').http,
    url = require('url');

exports.redirect = function(req, res)
{
    if (!req.headers['referer'])
        return res.status(400).send();

    var ref_parts = url.parse(req.headers['referer'], true);
    if (ref_parts.host != config.hostname)
        return res.status(400).send();

    return res.redirect(301, req.query.url);
}

exports.proxy = function(req, res)
{
    if (!req.headers['referer'])
        return res.status(400).send();

    var ref_parts = url.parse(req.headers['referer'], true);
    if (ref_parts.host != config.hostname)
        return res.status(400).send();

    var url_parts = url.parse(req.query.url, true);
    var query = url_parts.query;

    http.request(
        {
            host: url_parts.host,
            path: url_parts.path
        },
        (response) => {
            if (response.statusCode === 200)
            {
                res.writeHead(200, {
                    'Content-Type': response.headers['content-type']
                });
                response.pipe(res);
            }
            else
            {
                res.writeHead(response.statusCode);
                res.end();
            }
        }
    ).end();
}
<|endoftext|>


<commit_before>"use strict";

angular.module("mySite").controller("portfolioController", function ($scope) {

    $scope.portfolioData = [{
        "imgSrc": "../res/images/scattertale.png",
        "linkWebsite": "http://scattertale.azurewebsites.net",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/scattertale",
        "title": "Scattertale",
        "content": "
Scattertale is a website for writing and sharing articles.  It has a unique way of creating revisions to parts of articles.  Development started in December 2015 and will be ongoing for sometime

Have a look around the site, create an account if you want.  What is there currently should be working, but it is far from finished

It's made with Node.js and Backbone with a T-SQL Database
"
    }, {
        "imgSrc": "../res/images/witw.png",
        "linkWebsite": "https://play.google.com/store/apps/details?id=com.dreamfire.whereintheworld&hl=en",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Where-In-The-World",
        "title": "Where in the World",
        "content": "
Where in the World is an Android smartphone Google Maps/Streetview based guessing game.  It is similar to Geoguessr, but has a unique  interactive hint system developed for touch screens

It was made with Android Java.  It uses an SQLite database, token based payment system for in app purchases and progressive gameplay mechanics. It also has unlockable categories and levels with randomly selected locations
"
    }, {
        "imgSrc": "../res/images/mySite.png",
        "linkWebsite": "",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/My-Site",
        "title": "My Personal Website",
        "content": "
This website right here !

It's made with Node.js and Angular
"
    }, {
        "imgSrc": "../res/images/trello.png",
        "linkWebsite": "http://trellouidemo.azurewebsites.net/",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/TrelloUiDemo",
        "title": "Trello UI Demo",
        "content": "
This is a clone of the landing page from Trello

It's supposed to demonstrate that if someone gives me a UI mockup I can do the layout for them.  There are some slight differences such as the font as they use a fairly expensive one

It uses images from Trello but I did the page layout using html and sass
"
    }, {
        "imgSrc": "../res/images/trendy.png",
        "linkWebsite": "https://trendytrendz.azurewebsites.net/",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Trendytrendz",
        "title": "Trendytrendz",
        "content": "
Searches Google and Reddit based on Twitter trending hashtags

It uses the Twitter and Google apis.  Both of these apis have very strict usage limits, so this website is really only a demo

Twitter only allows 1 api call per minute for trend data, so there is a separate Azure Webjob that runs continuously and updates the data for each country which is then cached on the server.  Therefore the trend data may be up to 2 hours old

Made with Node.js/html/sass/javascript
"
    }, {
        "imgSrc": "../res/images/Shortcuts.png",
        "linkWebsite": "",
        "linkDownload": "../res/Shortcuts.zip",
        "linkSource": "https://github.com/jamesryan83/Shortcuts",
        "title": "Shortcuts",
        "content": "
A Windows desktop program used to save a list of shortcuts to a single file which you can then send to people.  Useful in an office environment

It's in C# and WPF and is built on a C#/WPF library which you can see here
"
    }, {
        "imgSrc": "../res/images/easybeam.png",
        "linkWebsite": "https://play.google.com/store/apps/details?id=test.BeamThingAndroid",
        "linkDownload": "",
        "linkSource": "",
        "title": "EasyBeam",
        "content": "
This is an Android app for continuous beam analysis for engineers.  It calculates bending moments, shear forces and deflections of multi-span beams

I made it with Java back in 2011 not long after I started programming, please ignore how hideous it looks :)
"
    }, {
        "imgSrc": "../res/images/Inkscape.png",
        "linkWebsite": "http://jamesryan83.github.io/Get-text-from-Inkscape/",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Get-text-from-Inkscape",
        "title": "Get Text From Inkscape",
        "content": "
This a very small web app that takes an Inkscape file (.svg), parses it, and returns the text
"
    }, {
        "imgSrc": "../res/images/PortalFrame.png",
        "linkWebsite": "",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Portal-Frame-Designer",
        "title": "Portal Frame designer",
        "content": "
Portal Frame Designer was part of my engineering thesis at uni.  The user inputs describe the dimensions and member sizes of a portal frame building.  This data is then sent to an Excel spreadsheet where loads (wind etc.) are applied using VBA, then that data was sent to Multiframe using the Multiframe API.

It's written in C#/WPF and uses Helix Toolkit to display a 3D model of the building
"
    }, {
        "imgSrc": "../res/images/Redmine.png",
        "linkWebsite": "",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Create-Redmine-Issue",
        "title": "Create Redmine Issues",
        "content": "
This is a single page web app that creates Issues on Redmine using the Redmine API

It is intended to be run on a local server with Redmine installed
"
    }]

});
<commit_msg>update link<commit_after>"use strict";

angular.module("mySite").controller("portfolioController", function ($scope) {

    $scope.portfolioData = [{
        "imgSrc": "../res/images/scattertale.png",
        "linkWebsite": "http://scattertale.azurewebsites.net",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/scattertale",
        "title": "Scattertale",
        "content": "
Scattertale is a website for writing and sharing articles.  It has a unique way of creating revisions to parts of articles.  Development started in December 2015 and will be ongoing for sometime

Have a look around the site, create an account if you want.  What is there currently should be working, but it is far from finished

It's made with Node.js and Backbone with a T-SQL Database
"
    }, {
        "imgSrc": "../res/images/witw.png",
        "linkWebsite": "https://play.google.com/store/apps/details?id=com.dreamfire.whereintheworld&hl=en",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Where-In-The-World",
        "title": "Where in the World",
        "content": "
Where in the World is an Android smartphone Google Maps/Streetview based guessing game.  It is similar to Geoguessr, but has a unique  interactive hint system developed for touch screens

It was made with Android Java.  It uses an SQLite database, token based payment system for in app purchases and progressive gameplay mechanics. It also has unlockable categories and levels with randomly selected locations
"
    }, {
        "imgSrc": "../res/images/mySite.png",
        "linkWebsite": "",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/My-Site",
        "title": "My Personal Website",
        "content": "
This website right here !

It's made with Node.js and Angular
"
    }, {
        "imgSrc": "../res/images/trello.png",
        "linkWebsite": "http://trellouidemo.azurewebsites.net/",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/TrelloUiDemo",
        "title": "Trello UI Demo",
        "content": "
This is a clone of the landing page from Trello

It's supposed to demonstrate that if someone gives me a UI mockup I can do the layout for them.  There are some slight differences such as the font as they use a fairly expensive one

It uses images from Trello but I did the page layout using html and sass
"
    }, {
        "imgSrc": "../res/images/trendy.png",
        "linkWebsite": "https://trendytrendz.azurewebsites.net/",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Trendytrendz",
        "title": "Trendytrendz",
        "content": "
Searches Google and Reddit based on Twitter trending hashtags

It uses the Twitter and Google apis.  Both of these apis have very strict usage limits, so this website is really only a demo

Twitter only allows 1 api call per minute for trend data, so there is a separate Azure Webjob that runs continuously and updates the data for each country which is then cached on the server.  Therefore the trend data may be up to 2 hours old

Made with Node.js/html/sass/javascript
"
    }, {
        "imgSrc": "../res/images/Shortcuts.png",
        "linkWebsite": "",
        "linkDownload": "../res/Shortcuts.zip",
        "linkSource": "https://github.com/jamesryan83/Shortcuts",
        "title": "Shortcuts",
        "content": "
A Windows desktop program used to save a list of shortcuts to a single file which you can then send to people.  Useful in an office environment

It's in C# and WPF and is built on a C#/WPF library which you can see here
"
    }, {
        "imgSrc": "../res/images/easybeam.png",
        "linkWebsite": "https://play.google.com/store/apps/details?id=test.BeamThingAndroid",
        "linkDownload": "",
        "linkSource": "",
        "title": "EasyBeam",
        "content": "
This is an Android app for continuous beam analysis for engineers.  It calculates bending moments, shear forces and deflections of multi-span beams

I made it with Java back in 2011 not long after I started programming, please ignore how hideous it looks :)
"
    }, {
        "imgSrc": "../res/images/Inkscape.png",
        "linkWebsite": "http://jamesryan83.github.io/Get-text-from-Inkscape/",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Get-text-from-Inkscape",
        "title": "Get Text From Inkscape",
        "content": "
This a very small web app that takes an Inkscape file (.svg), parses it, and returns the text
"
    }, {
        "imgSrc": "../res/images/PortalFrame.png",
        "linkWebsite": "",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Portal-Frame-Designer",
        "title": "Portal Frame designer",
        "content": "
Portal Frame Designer was part of my engineering thesis at uni.  The user inputs describe the dimensions and member sizes of a portal frame building.  This data is then sent to an Excel spreadsheet where loads (wind etc.) are applied using VBA, then that data was sent to Multiframe using the Multiframe API.

It's written in C#/WPF and uses Helix Toolkit to display a 3D model of the building
"
    }, {
        "imgSrc": "../res/images/Redmine.png",
        "linkWebsite": "",
        "linkDownload": "",
        "linkSource": "https://github.com/jamesryan83/Create-Redmine-Issue",
        "title": "Create Redmine Issues",
        "content": "
This is a single page web app that creates Issues on Redmine using the Redmine API

It is intended to be run on a local server with Redmine installed
"
    }]

});
<|endoftext|>


<commit_before>/*
  mixin-pro.js - multi-inherit with mixin in constructors in JavaScript.

  Copyright 2016, Raymond Xie <rjfun.mobile@gmail.com>.

  Thanks for mixin code by Lee Iverson <leei@sociologi.ca>.

 */

var util = require('util');

/**
 * Create a function simulated class, allow multiple inheritance

  var mapping = {
    constructor: function Foo() {},
    method: function(){},
  });

  Foo = createClass(mapping);
  Foo = createClass(base, mapping);
  Foo = createClass([base1, base2, ...], mapping);

 * By Raymond Xie <rjfun.mobile@gmail.com>, 2016/1/26
 */
function createClass() {
  var mixins, definition;
  switch(arguments.length) {
    case 0:
      throw "class definition required";
      break;
    case 1:
      definition = arguments[0];
      mixins = [];
      break;
    default:
      mixins = arguments[0];
      definition = arguments[1];
      break;
  }

  var base;
  if(typeof definition['constructor'] === 'function') {
    base = definition['constructor'];
  } else {
    base = function(){};
  }

  base.prototype = definition;
  base.prototype.constructor = base;
  base.prototype.instanceOf = function(baseclass) {
    if(this instanceof baseclass) return true;
    return subOf(this.constructor, baseclass);
  };;
  if(!mixins) return base;

  if(Array.isArray(mixins)) {
    for(var i=mixins.length-1; i>=0; i--) {
      base = mixin(base, mixins[i]);
    }
    return base;
  } else {
    return mixin(base, mixins);
  }
}

function subOf(sub, mixin) {
  if(sub === mixin) return true;
  if(sub && sub.constructors) {
    for(var i in sub.constructors) {
      if((sub.constructors[i] !== sub) && subOf(sub.constructors[i], mixin)) return true;
    }
  }
  return false;
}

/**
 * Mix the {mixin} constructor into the {base} constructor.  Ensures that the
 * all properties of {mixin} are available in {ctor} both for the constructor
 * and its prototype.
 *
 * The following are guaranteed consequences of the mixin:
 * * Will not mixin the same constructor twice.
 * * More than one constructor may be mixed in to the same {base}.
 * * Invokes the function {mixin.included(base)} after the mixin, if it is defined.
 * * {new ctor(...)} will invoke the constructors of all mixed in constructors and
 *   the original constructor in reverse order of their being mixed in.
 * * Properties of the {base} constructor and all previous mixins have priority over
 *   those of the {mixin}.
 *
 * @param {Function} base  The constructor that {mixin} will be added to.
 * @param {Function} mixin The constructor that will be mixed in to {base}.
 *
 */
function mixin(base, mixin) {
  var ctor = base;
  if (base.constructors) {
    // Don't mixin the same constructor twice.
    if(subOf(base, mixin)) return base;
    //for (var i in base.constructors) {
    //  if (base.constructors[i] === mixin)
    //    return base;
    //}
    // Remember this new one.
    base.constructors.unshift(mixin);
  } else {
    // Remember all mixed in classes
    base.constructors = [mixin, base];
    // Create a function with the same name, that calls both functions...
    ctor = base.prototype.constructor = mixin_constructor(base.name, base);
    ctor.__proto__ = base;
  }

  // Inject the mixin prototype at the top of the chain
  ctor.prototype = insert_proto(base.prototype, mixin.prototype);
  //inspect_protos(ctor.prototype, "ctor.prototype");

  insert_proto(ctor.__proto__, mixin);
  //inspect_protos(ctor, "ctor");

  // Inform mixin that it has been included
  if (mixin.hasOwnProperty('included')) {
    var incl = mixin.included.call(mixin, ctor);
    if (incl) { ctor = incl; }
  }

  return ctor;
}

function mixin_constructor(name, ctor) {
  var str = "function __ctor() { var c = ctor.constructors; for (var i in c) { c[i].apply(this, arguments); } };".replace(/__ctor/g, name);
  eval(str);
  return eval(name);
}

function isEmpty(ob) {
  for(var i in ob) return false;
  return true;
}

function insert_proto(base, mixin) {
  //console.log('\n' + base.name + ' <- ' + mixin.name);
  //inspect_protos(base,  "\ninserting: base ");
  //inspect_protos(mixin, "\ninserting: mixin");
  var copy = copyInto({}, mixin);
  copy.__mixed_in = true;

  // Find
  for (var p = base, prev = base; p.__mixed_in; prev = p, p = p.__proto__) {}
  if (p === base) { p.__mixed_in = true; } // Mark this as mixed in
  // inspect_protos(copy, "\ninserting: copy");
  copy.__proto__ = prev.__proto__;
  prev.__proto__ = copy;
  //inspect_protos(base, "\ninserted: base");

  // insert further, if mixin itself has been mixed in
  for(var q = mixin.__proto__; q; q = q.__proto__) {
    if(q && !isEmpty(q))
      insert_proto(base, q);
  }

  return base;
}

function copyInto(copy, obj) {
  var names = Object.getOwnPropertyNames(obj);
  for (var i in names) {
    var p = names[i];
    if (p !== 'prototype') {
      var descr = Object.getOwnPropertyDescriptor(obj, p);
      //console.log("obj." + p + " = " + util.inspect(descr));
      Object.defineProperty(copy, p, descr);
    }
  }
  return copy;
}
mixin.copyInto = copyInto;

function inspect_protos(obj, name) {
  console.log(name + " = " + util.inspect(obj));
  var i = 0;
  while (obj.__proto__) {
    obj = obj.__proto__;
    console.log("  __proto__[" + i + "] = " + util.inspect(obj));
    ++i;
  }
}

/**
 * Alias a property that is already defined for this Object. Moves the existing
 * property definition on {obj[method]} to {method}_without_{suffix} and
 * assigns {f} to both {method}_with_{suffix} and {method}.
 *
 * For example, the following code will override the "save" method of the
 * {ctor.prototype} with {my_save} and make the old version available as
 * {save_without_timestamp}.
 *
 *     Mixin.included = function(ctor) {
 *       mixin.alias(ctor.prototype, "save", "timestamp", my_save);
 *     }
 *
 * @param {Object} obj    The target object.
 * @param {String} method The base method name.
 * @param {String} suffix A suffix that will be added to the method name(s).
 * @param {Function} f    The function to use to override the method.
 * @api public
 */
function alias(obj, method, suffix, f) {
  if (obj[method + "_without_" + suffix]) {
    throw(method + "_without_" + suffix + " already defined.");
  }

  var was = obj[method];
  obj[method + "_without_" + suffix] = was;
  obj[method + "_with_" + suffix] = obj[method] = f;
}

mixin.alias = alias;

mixin.createClass = createClass;
mixin.inherit = createClass;

module.exports = mixin;
<commit_msg>Code refactoring<commit_after>/*
  mixin-pro.js - multi-inherit with mixin in constructors in JavaScript.

  Copyright 2016, Raymond Xie <rjfun.mobile@gmail.com>.

  Thanks for mixin code by Lee Iverson <leei@sociologi.ca>.

 */

var util = require('util');

/**
 * Create a function simulated class, allow multiple inheritance

  var mapping = {
    constructor: function Foo() {},
    method: function(){},
  });

  Foo = createClass(mapping);
  Foo = createClass(base, mapping);
  Foo = createClass([base1, base2, ...], mapping);

 * By Raymond Xie <rjfun.mobile@gmail.com>, 2016/1/26
 */
function createClass() {
  var mixins, definition;
  switch(arguments.length) {
    case 0:
      throw new Error('class definition required');
      break;
    case 1:
      mixins = [];
      definition = arguments[0];
      break;
    default:
      mixins = arguments[0];
      definition = arguments[1];
      break;
  }

  var newclass = definition['constructor'];

  if(typeof newclass !== 'function')
    throw new Error('constructor function required');

  if(typeof newclass.name === 'string' && newclass.name.length === 0)
    throw new Error('constructor name required, it will be used as new class name');

  newclass.prototype = definition;
  newclass.prototype.instanceOf = function(baseclass) {
    if(this instanceof baseclass) return true;
    return subOf(this.constructor, baseclass);
  };;

  // if no mixin given, just create a base class
  if(!mixins) return newclass;

  if(Array.isArray(mixins)) {
    // multi inheritance, if mixins is an array of base classes
    for(var i=mixins.length-1; i>=0; i--) {
      newclass = mixin(newclass, mixins[i]);
    }
    return newclass;

  } else {
    // single inheritance
    return mixin(newclass, mixins);
  }
}

// check whether mixin is in inherited chain of child class
function subOf(child, mixin) {
  if(child === mixin) return true;
  if(child && child.constructors) {
    for(var i in child.constructors) {
      var parent = child.constructors[i];

      // avoid dead loop
      if(parent === child) continue;

      if(parent === mixin) return true;
      if(subOf(parent, mixin)) return true;
    }
  }
  return false;
}

/**
 * Mix the {mixin} constructor into the {base} constructor.  Ensures that the
 * all properties of {mixin} are available in {ctor} both for the constructor
 * and its prototype.
 *
 * The following are guaranteed consequences of the mixin:
 * * Will not mixin the same constructor twice.
 * * More than one constructor may be mixed in to the same {base}.
 * * Invokes the function {mixin.included(base)} after the mixin, if it is defined.
 * * {new ctor(...)} will invoke the constructors of all mixed in constructors and
 *   the original constructor in reverse order of their being mixed in.
 * * Properties of the {base} constructor and all previous mixins have priority over
 *   those of the {mixin}.
 *
 * @param {Function} base  The constructor that {mixin} will be added to.
 * @param {Function} mixin The constructor that will be mixed in to {base}.
 *
 */
function mixin(base, mixin) {
  var ctor = base;
  if (base.constructors) {
    // Don't mixin the same constructor if already mixed.
    if(subOf(base, mixin)) return base;
    // Remember this new one.
    base.constructors.unshift(mixin);

  } else {
    // Remember all mixed in classes
    base.constructors = [mixin, base];
    // Create a function with the same name, that calls all inherited constructors ...
    ctor = base.prototype.constructor = mixin_constructor(base.name, base);
    ctor.__proto__ = base;
  }

  // Inject the mixin prototype at the top of the chain
  ctor.prototype = insert_proto(base.prototype, mixin.prototype);
  //inspect_protos(ctor.prototype, "ctor.prototype");

  insert_proto(ctor.__proto__, mixin);
  //inspect_protos(ctor, "ctor");

  // Inform mixin that it has been included
  if (mixin.hasOwnProperty('included')) {
    var incl = mixin.included.call(mixin, ctor);
    if (incl) { ctor = incl; }
  }

  return ctor;
}

function mixin_constructor(name, ctor) {
  var str = "function __ctor() { var c = ctor.constructors; for (var i in c) { c[i].apply(this, arguments); } };".replace(/__ctor/g, name);
  eval(str);
  return eval(name);
}

function isEmpty(ob) {
  for(var i in ob) return false;
  return true;
}

function insert_proto(base, mixin) {
  //console.log('\n' + base.name + ' <- ' + mixin.name);
  //inspect_protos(base,  "\ninserting: base ");
  //inspect_protos(mixin, "\ninserting: mixin");
  var copy = copyInto({}, mixin);
  copy.__mixed_in = true;

  // Find
  for (var p = base, prev = base; p.__mixed_in; prev = p, p = p.__proto__) {}
  if (p === base) { p.__mixed_in = true; } // Mark this as mixed in
  // inspect_protos(copy, "\ninserting: copy");
  copy.__proto__ = prev.__proto__;
  prev.__proto__ = copy;
  //inspect_protos(base, "\ninserted: base");

  // insert further, if mixin itself has been mixed in
  for(var q = mixin.__proto__; q; q = q.__proto__) {
    if(q && !isEmpty(q))
      insert_proto(base, q);
  }

  return base;
}

function copyInto(copy, obj) {
  var names = Object.getOwnPropertyNames(obj);
  for (var i in names) {
    var p = names[i];
    if (p !== 'prototype') {
      var descr = Object.getOwnPropertyDescriptor(obj, p);
      //console.log("obj." + p + " = " + util.inspect(descr));
      Object.defineProperty(copy, p, descr);
    }
  }
  return copy;
}
mixin.copyInto = copyInto;

function inspect_protos(obj, name) {
  console.log(name + " = " + util.inspect(obj));
  var i = 0;
  while (obj.__proto__) {
    obj = obj.__proto__;
    console.log("  __proto__[" + i + "] = " + util.inspect(obj));
    ++i;
  }
}

/**
 * Alias a property that is already defined for this Object. Moves the existing
 * property definition on {obj[method]} to {method}_without_{suffix} and
 * assigns {f} to both {method}_with_{suffix} and {method}.
 *
 * For example, the following code will override the "save" method of the
 * {ctor.prototype} with {my_save} and make the old version available as
 * {save_without_timestamp}.
 *
 *     Mixin.included = function(ctor) {
 *       mixin.alias(ctor.prototype, "save", "timestamp", my_save);
 *     }
 *
 * @param {Object} obj    The target object.
 * @param {String} method The base method name.
 * @param {String} suffix A suffix that will be added to the method name(s).
 * @param {Function} f    The function to use to override the method.
 * @api public
 */
function alias(obj, method, suffix, f) {
  if (obj[method + "_without_" + suffix]) {
    throw(method + "_without_" + suffix + " already defined.");
  }

  var was = obj[method];
  obj[method + "_without_" + suffix] = was;
  obj[method + "_with_" + suffix] = obj[method] = f;
}

mixin.alias = alias;

mixin.createClass = createClass;
mixin.inherit = createClass;

module.exports = mixin;
<|endoftext|>


<commit_before>define(
  'ephox.alloy.construct.ComponentDom',

  [
    'ephox.alloy.dom.DomModification',
    'ephox.alloy.util.ObjIndex',
    'ephox.alloy.util.PrioritySort',
    'ephox.boulder.api.Objects',
    'ephox.compass.Arr',
    'ephox.compass.Obj',
    'ephox.numerosity.api.JSON',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'ephox.perhaps.Options',
    'ephox.perhaps.Result'
  ],

  function (DomModification, ObjIndex, PrioritySort, Objects, Arr, Obj, Json, Fun, Option, Options, Result) {
    var behaviourDom = function (name, modification) {
      return {
        name: Fun.constant(name),
        modification: modification
      };
    };

    var concat = function (chain, aspect) {
      var values = Arr.bind(chain, function (c) {
        return c.modification().getOr([ ]);
      });
      return Result.value(
        Objects.wrap(aspect, values)
      );
    };

    var onlyOne = function (chain, aspect, order) {
      if (chain.length > 1) return Result.error(
        'Multiple behaviours have tried to change DOM "' + aspect + '". The guilty behaviours are: ' +
          Json.stringify(Arr.map(chain, function (b) { return b.name(); })) + '. At this stage, this ' +
          'is not supported. Future releases might provide strategies for resolving this.'
      );
      else if (chain.length === 0) return  Result.value({ });
      else return Result.value(
        chain[0].modification().fold(function () {
          return { };
        }, function (m) {
          return Objects.wrap(aspect, m);
        })
      );
    };

    var duplicate = function (aspect, k, obj, behaviours) {
      return Result.error('Mulitple behaviours have tried to change the _' + k + '_ "' + aspect + '"' +
        '. The guilty behaviours are: ' + Json.stringify(Arr.bind(behaviours, function (b) {
          return b.modification().getOr({})[k] !== undefined ? [ b.name() ] : [ ];
        }), null, 2) + '. This is not currently supported.'
      );
    };

    var safeMerge = function (chain, aspect) {
      var y = Arr.foldl(chain, function (acc, c) {
        var obj = c.modification().getOr({});
        return acc.bind(function (accRest) {
          var parts = Obj.mapToArray(obj, function (v, k) {
            return accRest[k] !== undefined ? duplicate(aspect, k, obj, chain) :
              Result.value(Objects.wrap(k, v));
          });
          return Objects.consolidate(parts, accRest);
        });
      }, Result.value({}));

      return y.map(function (yValue) {
        return Objects.wrap(aspect, yValue);
      });
    };

    var mergeTypes = {
      classes: concat,
      attributes: safeMerge,
      styles: safeMerge,

      // Group these together somehow
      domChildren: onlyOne,
      defChildren: onlyOne,
      innerHtml: onlyOne,

      value: onlyOne
    };

    var combine = function (info, behaviours, base) {
      // Get the Behaviour DOM modifications
      var behaviourDoms = { };
      Arr.each(behaviours, function (behaviour) {
        behaviourDoms[behaviour.name()] = behaviour.exhibit(info, base);
      });

      var byAspect = ObjIndex.byInnerKey(behaviourDoms, behaviourDom);

      var usedAspect = Obj.map(byAspect, function (values, aspect) {
        return Arr.bind(values, function (value) {
          return value.modification().fold(function () {
            return [ ];
          }, function (v) {
            return [ value ];
          });
        });
      });

      var modifications = Obj.mapToArray(usedAspect, function (values, aspect) {
        // TODO: Use hasOwnProperty.
        return Objects.readOptFrom(mergeTypes, aspect).fold(function () {
          return Result.error('Unknown field type: ' + aspect);
        }, function (merger ){
          return merger(values, aspect);
        });
      });

      var consolidated = Objects.consolidate(modifications, {});
      return consolidated.map(DomModification.nu);
    };

    return {
      combine: combine
    };
  }
);<commit_msg>VAN-1: whitespace<commit_after>define(
  'ephox.alloy.construct.ComponentDom',

  [
    'ephox.alloy.dom.DomModification',
    'ephox.alloy.util.ObjIndex',
    'ephox.alloy.util.PrioritySort',
    'ephox.boulder.api.Objects',
    'ephox.compass.Arr',
    'ephox.compass.Obj',
    'ephox.numerosity.api.JSON',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'ephox.perhaps.Options',
    'ephox.perhaps.Result'
  ],

  function (DomModification, ObjIndex, PrioritySort, Objects, Arr, Obj, Json, Fun, Option, Options, Result) {
    var behaviourDom = function (name, modification) {
      return {
        name: Fun.constant(name),
        modification: modification
      };
    };

    var concat = function (chain, aspect) {
      var values = Arr.bind(chain, function (c) {
        return c.modification().getOr([ ]);
      });
      return Result.value(
        Objects.wrap(aspect, values)
      );
    };

    var onlyOne = function (chain, aspect, order) {
      if (chain.length > 1) return Result.error(
        'Multiple behaviours have tried to change DOM "' + aspect + '". The guilty behaviours are: ' +
          Json.stringify(Arr.map(chain, function (b) { return b.name(); })) + '. At this stage, this ' +
          'is not supported. Future releases might provide strategies for resolving this.'
      );
      else if (chain.length === 0) return  Result.value({ });
      else return Result.value(
        chain[0].modification().fold(function () {
          return { };
        }, function (m) {
          return Objects.wrap(aspect, m);
        })
      );
    };

    var duplicate = function (aspect, k, obj, behaviours) {
      return Result.error('Mulitple behaviours have tried to change the _' + k + '_ "' + aspect + '"' +
        '. The guilty behaviours are: ' + Json.stringify(Arr.bind(behaviours, function (b) {
          return b.modification().getOr({})[k] !== undefined ? [ b.name() ] : [ ];
        }), null, 2) + '. This is not currently supported.'
      );
    };

    var safeMerge = function (chain, aspect) {
      var y = Arr.foldl(chain, function (acc, c) {
        var obj = c.modification().getOr({});
        return acc.bind(function (accRest) {
          var parts = Obj.mapToArray(obj, function (v, k) {
            return accRest[k] !== undefined ? duplicate(aspect, k, obj, chain) :
              Result.value(Objects.wrap(k, v));
          });
          return Objects.consolidate(parts, accRest);
        });
      }, Result.value({}));

      return y.map(function (yValue) {
        return Objects.wrap(aspect, yValue);
      });
    };

    var mergeTypes = {
      classes: concat,
      attributes: safeMerge,
      styles: safeMerge,

      // Group these together somehow
      domChildren: onlyOne,
      defChildren: onlyOne,
      innerHtml: onlyOne,

      value: onlyOne
    };

    var combine = function (info, behaviours, base) {
      // Get the Behaviour DOM modifications
      var behaviourDoms = { };
      Arr.each(behaviours, function (behaviour) {
        behaviourDoms[behaviour.name()] = behaviour.exhibit(info, base);
      });

      var byAspect = ObjIndex.byInnerKey(behaviourDoms, behaviourDom);

      var usedAspect = Obj.map(byAspect, function (values, aspect) {
        return Arr.bind(values, function (value) {
          return value.modification().fold(function () {
            return [ ];
          }, function (v) {
            return [ value ];
          });
        });
      });

      var modifications = Obj.mapToArray(usedAspect, function (values, aspect) {
        // TODO: Use hasOwnProperty.
        return Objects.readOptFrom(mergeTypes, aspect).fold(function () {
          return Result.error('Unknown field type: ' + aspect);
        }, function (merger ){
          return merger(values, aspect);
        });
      });

      var consolidated = Objects.consolidate(modifications, {});
      return consolidated.map(DomModification.nu);
    };

    return {
      combine: combine
    };
  }
);<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides class sap.ui.core.ResizeHandler
sap.ui.define(['jquery.sap.global', 'sap/ui/base/Object', 'jquery.sap.act', 'jquery.sap.script'],
	function(jQuery, BaseObject/* , jQuerySap1, jQuerySap */) {
	"use strict";

	// local logger, by default only logging errors
	var log = jQuery.sap.log.getLogger("sap.ui.core.ResizeHandler", jQuery.sap.log.Level.ERROR);

	function lazyInstanceof(o, sModule) {
		var FNClass = sap.ui.require(sModule);
		return typeof FNClass === 'function' && (o instanceof FNClass);
	}

	/**
	 * Reference to the Core (implementation view, not facade)
	 * @type {sap.ui.core.Core}
	 */
	var oCoreRef = null;

	/**
	 * The resize handling API provides firing of resize events on all browsers by regularly
	 * checking the width and height of registered DOM elements or controls and firing events accordingly.
	 *
	 * @namespace
	 * @alias sap.ui.core.ResizeHandler
	 * @extends sap.ui.base.Object
	 * @author SAP SE
	 * @version ${version}
	 * @public
	 */

	var ResizeHandler = BaseObject.extend("sap.ui.core.ResizeHandler", /** @lends sap.ui.core.ResizeHandler.prototype */ {

		constructor : function(oCore) {
			BaseObject.apply(this);

			oCoreRef = oCore;

			this.aResizeListeners = [];
			this.bRegistered = false;

			this.iIdCounter = 0;

			this.fDestroyHandler = this.destroy.bind(this);

			jQuery(window).bind("unload", this.fDestroyHandler);

			jQuery.sap.act.attachActivate(initListener, this);
		}

	});

	function clearListener(){
		if (this.bRegistered) {
			this.bRegistered = false;
			sap.ui.getCore().detachIntervalTimer(this.checkSizes, this);
		}
	}

	function initListener(){
		if (!this.bRegistered && this.aResizeListeners.length > 0) {
			this.bRegistered = true;
			sap.ui.getCore().attachIntervalTimer(this.checkSizes, this);
		}
	}

	/**
	 * Destroy method of the Resize Handler.
	 * It unregisters the event handlers.
	 *
	 * @param {jQuery.Event} oEvent the event that initiated the destruction of the ResizeHandler
	 * @private
	 */
	ResizeHandler.prototype.destroy = function(oEvent) {
		jQuery.sap.act.detachActivate(initListener, this);
		jQuery(window).unbind("unload", this.fDestroyHandler);
		oCoreRef = null;
		this.aResizeListeners = [];
		clearListener.call(this);
	};

	/**
	 * Attaches listener to resize event.
	 *
	 * @param {Element|sap.ui.core.Control} oRef the DOM reference or a control
	 * @param {function} fHandler the event handler function
	 * @return {string} Registration-ID for later detaching.
	 * @private
	 */
	ResizeHandler.prototype.attachListener = function(oRef, fHandler){
		var bIsControl = lazyInstanceof(oRef, 'sap/ui/core/Control'),
			oDom = bIsControl ? oRef.getDomRef() : oRef,
			iWidth = oDom ? oDom.offsetWidth : 0,
			iHeight = oDom ? oDom.offsetHeight : 0,
			sId = "rs-" + Date.now() + "-" + this.iIdCounter++,
			dbg;

		if (bIsControl) {
			dbg = ("Control " + oRef.getId());
		} else if (oRef.id) {
			dbg = oRef.id;
		} else {
			dbg = String(oRef);
		}

		this.aResizeListeners.push({sId: sId, oDomRef: bIsControl ? null : oRef, oControl: bIsControl ? oRef : null, fHandler: fHandler, iWidth: iWidth, iHeight: iHeight, dbg: dbg});
		log.debug("registered " + dbg);

		initListener.call(this);

		return sId;
	};

	/**
	 * Detaches listener from resize event.
	 *
	 * @param {string} Registration-ID returned from attachListener
	 * @private
	 */
	ResizeHandler.prototype.detachListener = function(sId){
		var aResizeListeners = this.aResizeListeners;
		for ( var i = 0; i < aResizeListeners.length; i++ ) {
			if (aResizeListeners[i].sId === sId) {
				aResizeListeners.splice(i, 1);
				log.debug("deregistered " + sId);
				break;
			}
		}

		// if list is empty now, stop interval
		if (aResizeListeners.length === 0) {
			clearListener.call(this);
		}
	};

	/**
	 * Check sizes of resize elements.
	 * @private
	 */
	ResizeHandler.prototype.checkSizes = function() {
		var bDebug = log.isLoggable();
		if ( bDebug ) {
			log.debug("checkSizes:");
		}
		this.aResizeListeners.forEach(function(oResizeListener){
			if (oResizeListener) {
				var bCtrl = !!oResizeListener.oControl,
					oDomRef = bCtrl ? oResizeListener.oControl.getDomRef() : oResizeListener.oDomRef;

				if ( oDomRef && jQuery.contains(document.documentElement, oDomRef)) { //check that domref is still active

					var iOldWidth = oResizeListener.iWidth,
						iOldHeight = oResizeListener.iHeight,
						iNewWidth = oDomRef.offsetWidth,
						iNewHeight = oDomRef.offsetHeight;

					if (iOldWidth != iNewWidth || iOldHeight != iNewHeight) {
						oResizeListener.iWidth = iNewWidth;
						oResizeListener.iHeight = iNewHeight;

						var oEvent = jQuery.Event("resize");
						oEvent.target = oDomRef;
						oEvent.currentTarget = oDomRef;
						oEvent.size = {width: iNewWidth, height: iNewHeight};
						oEvent.oldSize = {width: iOldWidth, height: iOldHeight};
						oEvent.control = bCtrl ? oResizeListener.oControl : null;

						if ( bDebug ) {
							log.debug("resize detected for '" + oResizeListener.dbg + "': " + oEvent.oldSize.width + "x" + oEvent.oldSize.height + " -> " + oEvent.size.width + "x" + oEvent.size.height);
						}

						oResizeListener.fHandler(oEvent);
					}

				}
			}
		});

		if (ResizeHandler._keepActive != true && ResizeHandler._keepActive != false) {
			//initialize default
			ResizeHandler._keepActive = false;
		}

		if (!jQuery.sap.act.isActive() && !ResizeHandler._keepActive) {
			clearListener.call(this);
		}
	};

	/**
	 * Registers the given event handler for resize events on the given DOM element or control.
	 *
	 * Note: This function must not be used before the UI5 framework is initialized.
	 * Please use the {@link sap.ui.core.Core#attachInit init event} of UI5 if you are not sure whether this is the case.
	 *
	 * The resize handler periodically checks the dimensions of the registered reference. Whenever it detects changes, an event is fired.
	 * Be careful when changing dimensions within the event handler which might cause another resize event and so on.
	 *
	 * The available parameters of the resize event are:
	 *

	 *
oEvent.target: The DOM element of which the dimensions were checked

	 *
oEvent.size.width: The current width of the DOM element in pixels

	 *
oEvent.size.height: The current height of the DOM element in pixels

	 *
oEvent.oldSize.width: The previous width of the DOM element in pixels

	 *
oEvent.oldSize.height: The previous height of the DOM element in pixels

	 *
oEvent.control: The control which was given during registration of the event handler (if present)

	 *

	 *
	 * @param {DOMRef|sap.ui.core.Control} oRef The control or the DOM reference for which the given event handler should be registered (beside the window)
	 * @param {function} fHandler
	 *             The event handler which should be called whenever the size of the given reference is changed.
	 *             The event object is passed as first argument to the event handler. See the description of this function for more details about the available parameters of this event.
	 * @return {string}
	 *             A registration ID which can be used for deregistering the event handler, see {@link sap.ui.core.ResizeHandler.deregister}.
	 *             If the UI5 framework is not yet initialized null is returned.
	 * @public
	 */
	ResizeHandler.register = function(oRef, fHandler) {
		if (!oCoreRef || !oCoreRef.oResizeHandler) {
			return null;
		}
		return oCoreRef.oResizeHandler.attachListener(oRef, fHandler);
	};

	/**
	 * Deregisters a previously registered handler for resize events with the given registration ID.
	 *
	 * @param {string} sId
	 *            The registration ID of the handler to deregister. The ID was provided by function {@link sap.ui.core.ResizeHandler.register}
	 *            when the handler was registered.
	 * @public
	 */
	ResizeHandler.deregister = function(sId) {
		if (!oCoreRef || !oCoreRef.oResizeHandler) {
			return;
		}
		oCoreRef.oResizeHandler.detachListener(sId);
	};

	/**
	 * Deregisters all registered handler for resize events for the given control.
	 *
	 * @param {string} sControlId The Id of the control.
	 * @private
	 */
	ResizeHandler.deregisterAllForControl = function(sControlId) {
		if (!oCoreRef || !oCoreRef.oResizeHandler) {
			return;
		}

		oCoreRef.oResizeHandler.aResizeListeners.filter(function(oResizeListener){
			return oResizeListener && oResizeListener.oControl && oResizeListener.oControl.getId() === sControlId;
		}).forEach(function(oResizeListener) {
			ResizeHandler.deregister(oResizeListener.sId);
		});
	};

	return ResizeHandler;

});
<commit_msg>[INTERNAL] ResizeHandler: reduce jQuery usages in the checkSizes method<commit_after>/*!
 * ${copyright}
 */

// Provides class sap.ui.core.ResizeHandler
sap.ui.define(['jquery.sap.global', 'sap/ui/base/Object', 'jquery.sap.act', 'jquery.sap.script'],
	function(jQuery, BaseObject/* , jQuerySap1, jQuerySap */) {
	"use strict";

	// local logger, by default only logging errors
	var log = jQuery.sap.log.getLogger("sap.ui.core.ResizeHandler", jQuery.sap.log.Level.ERROR);

	function lazyInstanceof(o, sModule) {
		var FNClass = sap.ui.require(sModule);
		return typeof FNClass === 'function' && (o instanceof FNClass);
	}

	/**
	 * Reference to the Core (implementation view, not facade)
	 * @type {sap.ui.core.Core}
	 */
	var oCoreRef = null;

	/**
	 * The resize handling API provides firing of resize events on all browsers by regularly
	 * checking the width and height of registered DOM elements or controls and firing events accordingly.
	 *
	 * @namespace
	 * @alias sap.ui.core.ResizeHandler
	 * @extends sap.ui.base.Object
	 * @author SAP SE
	 * @version ${version}
	 * @public
	 */

	var ResizeHandler = BaseObject.extend("sap.ui.core.ResizeHandler", /** @lends sap.ui.core.ResizeHandler.prototype */ {

		constructor : function(oCore) {
			BaseObject.apply(this);

			oCoreRef = oCore;

			this.aResizeListeners = [];
			this.bRegistered = false;

			this.iIdCounter = 0;

			this.fDestroyHandler = this.destroy.bind(this);

			jQuery(window).bind("unload", this.fDestroyHandler);

			jQuery.sap.act.attachActivate(initListener, this);
		}

	});

	function clearListener(){
		if (this.bRegistered) {
			this.bRegistered = false;
			sap.ui.getCore().detachIntervalTimer(this.checkSizes, this);
		}
	}

	function initListener(){
		if (!this.bRegistered && this.aResizeListeners.length > 0) {
			this.bRegistered = true;
			sap.ui.getCore().attachIntervalTimer(this.checkSizes, this);
		}
	}

	/**
	 * Destroy method of the Resize Handler.
	 * It unregisters the event handlers.
	 *
	 * @param {jQuery.Event} oEvent the event that initiated the destruction of the ResizeHandler
	 * @private
	 */
	ResizeHandler.prototype.destroy = function(oEvent) {
		jQuery.sap.act.detachActivate(initListener, this);
		jQuery(window).unbind("unload", this.fDestroyHandler);
		oCoreRef = null;
		this.aResizeListeners = [];
		clearListener.call(this);
	};

	/**
	 * Attaches listener to resize event.
	 *
	 * @param {Element|sap.ui.core.Control} oRef the DOM reference or a control
	 * @param {function} fHandler the event handler function
	 * @return {string} Registration-ID for later detaching.
	 * @private
	 */
	ResizeHandler.prototype.attachListener = function(oRef, fHandler){
		var bIsControl = lazyInstanceof(oRef, 'sap/ui/core/Control'),
			oDom = bIsControl ? oRef.getDomRef() : oRef,
			iWidth = oDom ? oDom.offsetWidth : 0,
			iHeight = oDom ? oDom.offsetHeight : 0,
			sId = "rs-" + Date.now() + "-" + this.iIdCounter++,
			dbg;

		if (bIsControl) {
			dbg = ("Control " + oRef.getId());
		} else if (oRef.id) {
			dbg = oRef.id;
		} else {
			dbg = String(oRef);
		}

		this.aResizeListeners.push({sId: sId, oDomRef: bIsControl ? null : oRef, oControl: bIsControl ? oRef : null, fHandler: fHandler, iWidth: iWidth, iHeight: iHeight, dbg: dbg});
		log.debug("registered " + dbg);

		initListener.call(this);

		return sId;
	};

	/**
	 * Detaches listener from resize event.
	 *
	 * @param {string} Registration-ID returned from attachListener
	 * @private
	 */
	ResizeHandler.prototype.detachListener = function(sId){
		var aResizeListeners = this.aResizeListeners;
		for ( var i = 0; i < aResizeListeners.length; i++ ) {
			if (aResizeListeners[i].sId === sId) {
				aResizeListeners.splice(i, 1);
				log.debug("deregistered " + sId);
				break;
			}
		}

		// if list is empty now, stop interval
		if (aResizeListeners.length === 0) {
			clearListener.call(this);
		}
	};

	/**
	 * Check sizes of resize elements.
	 * @private
	 */
	ResizeHandler.prototype.checkSizes = function() {
		var bDebug = log.isLoggable();
		if ( bDebug ) {
			log.debug("checkSizes:");
		}
		this.aResizeListeners.forEach(function(oResizeListener){
			if (oResizeListener) {
				var bCtrl = !!oResizeListener.oControl,
					oDomRef = bCtrl ? oResizeListener.oControl.getDomRef() : oResizeListener.oDomRef,
					oDocumentElementDomRef = document.documentElement;

				if (oDomRef && ((oDocumentElementDomRef !== oDomRef) && oDocumentElementDomRef.contains(oDomRef))) { // check that oDomRef is still active

					var iOldWidth = oResizeListener.iWidth,
						iOldHeight = oResizeListener.iHeight,
						iNewWidth = oDomRef.offsetWidth,
						iNewHeight = oDomRef.offsetHeight;

					if (iOldWidth != iNewWidth || iOldHeight != iNewHeight) {
						oResizeListener.iWidth = iNewWidth;
						oResizeListener.iHeight = iNewHeight;

						var oEvent = jQuery.Event("resize");
						oEvent.target = oDomRef;
						oEvent.currentTarget = oDomRef;
						oEvent.size = {width: iNewWidth, height: iNewHeight};
						oEvent.oldSize = {width: iOldWidth, height: iOldHeight};
						oEvent.control = bCtrl ? oResizeListener.oControl : null;

						if ( bDebug ) {
							log.debug("resize detected for '" + oResizeListener.dbg + "': " + oEvent.oldSize.width + "x" + oEvent.oldSize.height + " -> " + oEvent.size.width + "x" + oEvent.size.height);
						}

						oResizeListener.fHandler(oEvent);
					}

				}
			}
		});

		if (ResizeHandler._keepActive != true && ResizeHandler._keepActive != false) {
			//initialize default
			ResizeHandler._keepActive = false;
		}

		if (!jQuery.sap.act.isActive() && !ResizeHandler._keepActive) {
			clearListener.call(this);
		}
	};

	/**
	 * Registers the given event handler for resize events on the given DOM element or control.
	 *
	 * Note: This function must not be used before the UI5 framework is initialized.
	 * Please use the {@link sap.ui.core.Core#attachInit init event} of UI5 if you are not sure whether this is the case.
	 *
	 * The resize handler periodically checks the dimensions of the registered reference. Whenever it detects changes, an event is fired.
	 * Be careful when changing dimensions within the event handler which might cause another resize event and so on.
	 *
	 * The available parameters of the resize event are:
	 *

	 *
oEvent.target: The DOM element of which the dimensions were checked

	 *
oEvent.size.width: The current width of the DOM element in pixels

	 *
oEvent.size.height: The current height of the DOM element in pixels

	 *
oEvent.oldSize.width: The previous width of the DOM element in pixels

	 *
oEvent.oldSize.height: The previous height of the DOM element in pixels

	 *
oEvent.control: The control which was given during registration of the event handler (if present)

	 *

	 *
	 * @param {DOMRef|sap.ui.core.Control} oRef The control or the DOM reference for which the given event handler should be registered (beside the window)
	 * @param {function} fHandler
	 *             The event handler which should be called whenever the size of the given reference is changed.
	 *             The event object is passed as first argument to the event handler. See the description of this function for more details about the available parameters of this event.
	 * @return {string}
	 *             A registration ID which can be used for deregistering the event handler, see {@link sap.ui.core.ResizeHandler.deregister}.
	 *             If the UI5 framework is not yet initialized null is returned.
	 * @public
	 */
	ResizeHandler.register = function(oRef, fHandler) {
		if (!oCoreRef || !oCoreRef.oResizeHandler) {
			return null;
		}
		return oCoreRef.oResizeHandler.attachListener(oRef, fHandler);
	};

	/**
	 * Deregisters a previously registered handler for resize events with the given registration ID.
	 *
	 * @param {string} sId
	 *            The registration ID of the handler to deregister. The ID was provided by function {@link sap.ui.core.ResizeHandler.register}
	 *            when the handler was registered.
	 * @public
	 */
	ResizeHandler.deregister = function(sId) {
		if (!oCoreRef || !oCoreRef.oResizeHandler) {
			return;
		}
		oCoreRef.oResizeHandler.detachListener(sId);
	};

	/**
	 * Deregisters all registered handler for resize events for the given control.
	 *
	 * @param {string} sControlId The Id of the control.
	 * @private
	 */
	ResizeHandler.deregisterAllForControl = function(sControlId) {
		if (!oCoreRef || !oCoreRef.oResizeHandler) {
			return;
		}

		oCoreRef.oResizeHandler.aResizeListeners.filter(function(oResizeListener){
			return oResizeListener && oResizeListener.oControl && oResizeListener.oControl.getId() === sControlId;
		}).forEach(function(oResizeListener) {
			ResizeHandler.deregister(oResizeListener.sId);
		});
	};

	return ResizeHandler;

});
<|endoftext|>


<commit_before>import _ from 'underscore';
import { ItemTransformer } from './itemTransformer';

class ReviewDataCache {
  constructor() {
    this._itemTransformer = new ItemTransformer();
    this._itemStores = [];
  }

  getMatchingItem(item) {
    var dtrItem = this._itemTransformer.translateToDtrWeapon(item);

    dtrItem.referenceId = String(dtrItem.referenceId);

    return _.findWhere(this._itemStores, { referenceId: dtrItem.referenceId, roll: dtrItem.roll });
  }

  getRatingData(item) {
    var matchingItem = this.getMatchingItem(item);

    if (!matchingItem) {
      return null;
    }

    return matchingItem;
  }

  toAtMostOneDecimal(rating) {
    if (!rating) {
      return null;
    }

    if ((rating % 1) === 0) {
      return rating;
    }

    return rating.toFixed(1);
  }

  addScore(dtrRating) {
    dtrRating.rating = this.toAtMostOneDecimal(dtrRating.rating);

    this._itemStores.push(dtrRating);
  }

  addUserReviewData(item,
                    userReview) {
    var matchingItem = this.getMatchingItem(item);

    var rating = matchingItem.rating;

    Object.assign(matchingItem,
                  userReview);

    matchingItem.userRating = matchingItem.rating;

    matchingItem.rating = rating;
  }

  addReviewsData(item,
                 reviewsData) {
    var matchingItem = this.getMatchingItem(item);

    matchingItem.reviews = reviewsData.reviews;
    matchingItem.reviewsDataFetched = true;
  }

  getItemStores() {
    return this._itemStores;
  }

  eventuallyPurgeCachedData(item) {
    var tenMinutes = 1000 * 60 * 10;
    var self = this;

    setTimeout(function() {
      var matchingItem = self.getMatchingItem(item);

      matchingItem.reviews = null;
      matchingItem.reviewsDataFetched = false;
    },
      tenMinutes);
  }
}

export { ReviewDataCache };<commit_msg>JSDoc comments for review data cache.<commit_after>import _ from 'underscore';
import { ItemTransformer } from './itemTransformer';

/**
 * Cache of review data.
 * Mixes and matches remote as well as local data to cut down on chatter and prevent data loss on store refreshes.
 *
 * @class ReviewDataCache
 */
class ReviewDataCache {
  constructor() {
    this._itemTransformer = new ItemTransformer();
    this._itemStores = [];
  }

  _getMatchingItem(item) {
    var dtrItem = this._itemTransformer.translateToDtrWeapon(item);

    dtrItem.referenceId = String(dtrItem.referenceId);

    return _.findWhere(this._itemStores, { referenceId: dtrItem.referenceId, roll: dtrItem.roll });
  }

  /**
   * Do we have any locally-cached review data for the given item from the DIM store?
   *
   * @param {any} item
   * @returns {any}
   *
   * @memberof ReviewDataCache
   */
  getRatingData(item) {
    var matchingItem = this._getMatchingItem(item);

    if (!matchingItem) {
      return null;
    }

    return matchingItem;
  }

  _toAtMostOneDecimal(rating) {
    if (!rating) {
      return null;
    }

    if ((rating % 1) === 0) {
      return rating;
    }

    return rating.toFixed(1);
  }

  /**
   * Add (and track) the community score.
   *
   * @param {any} dtrRating
   *
   * @memberof ReviewDataCache
   */
  addScore(dtrRating) {
    dtrRating.rating = this._toAtMostOneDecimal(dtrRating.rating);

    this._itemStores.push(dtrRating);
  }

  /**
   * Keep track of this user review for this (DIM store) item.
   * This supports the workflow where a user types a review but doesn't submit it, a store refresh
   * happens in the background, then they go back to the item.  Or they post data and the DTR API
   * is still feeding back cached data or processing it or whatever.
   * The expectation is that this will be building on top of reviews data that's already been supplied.
   *
   * @param {any} item
   * @param {any} userReview
   *
   * @memberof ReviewDataCache
   */
  addUserReviewData(item,
                    userReview) {
    var matchingItem = this._getMatchingItem(item);

    var rating = matchingItem.rating;

    Object.assign(matchingItem,
                  userReview);

    matchingItem.userRating = matchingItem.rating;

    matchingItem.rating = rating;
  }

  /**
   * Keep track of expanded item review data from the DTR API for this DIM store item.
   * The expectation is that this will be building on top of community score data that's already been supplied.
   *
   * @param {any} item
   * @param {any} reviewsData
   *
   * @memberof ReviewDataCache
   */
  addReviewsData(item,
                 reviewsData) {
    var matchingItem = this._getMatchingItem(item);

    matchingItem.reviews = reviewsData.reviews;
    matchingItem.reviewsDataFetched = true;
  }

  /**
   * Fetch the collection of review data that we've stored locally.
   *
   * @returns {array}
   *
   * @memberof ReviewDataCache
   */
  getItemStores() {
    return this._itemStores;
  }

  /**
   * There's a 10 minute delay between posting an item review to the DTR API
   * and being able to fetch that review from it.
   * To prevent angry bug reports, we'll continue to hang on to local user review data for
   * 10 minutes, then we'll purge it (so that it can be re-pulled).
   *
   * Item is just an item from DIM's stores.
   *
   * @param {any} item
   *
   * @memberof ReviewDataCache
   */
  eventuallyPurgeCachedData(item) {
    var tenMinutes = 1000 * 60 * 10;
    var self = this;

    setTimeout(function() {
      var matchingItem = self._getMatchingItem(item);

      matchingItem.reviews = null;
      matchingItem.reviewsDataFetched = false;
    },
      tenMinutes);
  }
}

export { ReviewDataCache };<|endoftext|>