<commit_before>const program = require('commander');

program
    .version('1.0.0')
    .option('-p, --port <port>', 'specify the websocket port to listen to [9872]', 9872)
    .parse(process.argv);

const io = require('socket.io-client'),
      winston = require('winston'),
      config = require('./config.js');

winston.level = 'debug';
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {'timestamp': true, 'label': 'matchmaker'});

const PORT = program.port;
const SERVICES = config.services;

const ROOMID = 'room1';

winston.info('Unichat matchmaker service');
winston.info('Listening on port ' + PORT);

let serviceSockets = [];
let presenceToUse = '';
let realtimeToUse = '';
let waitingClient = null;
for (var i=0; i < SERVICES.length; i++) {
    let _url = SERVICES[i].url;
    let _type = SERVICES[i].type;

    let _socketIOServer = io.connect(_url, {'forceNew': true});

    _socketIOServer.on('connect', () => {
        winston.debug('Connected to ' + _type + ' service at ' + _url);
        _socketIOServer.emit('register-matchmaker');
    });

    _socketIOServer.on('presence-find-partner', (cookieId) => {
        winston.debug('Finding partner for client with cookie: ' + cookieId);
        if (waitingClient === null) {
            waitingClient = {'cookie': cookieId, 'presenceSocket': _socketIOServer};
        }
        else {
            let roomId = Math.random().toString(36).substr(2, 5);

            _socketIOServer.emit('matchmaker-send-to-room', cookieId, realtimeToUse.url, roomId);
            winston.debug('Sent client with cookie (' + cookieId + ') to room: ' + roomId);
            waitingClient.presenceSocket.emit('matchmaker-send-to-room', waitingClient.cookie, realtimeToUse.url, roomId);
            winston.debug('Sent client with cookie (' + waitingClient.cookie + ') to room: ' + roomId);

            waitingClient = null;
        }
    });

    _socketIOServer.on('presence-client-disconnected', (cookieId) => {
        if (waitingClient !== null) {
            winston.debug('Removing from wait line client: ' + cookieId);
            waitingClient = null;
        }
    });

    serviceSockets.push(_socketIOServer);

    // Define the primary presence/realtime service that traffic is directed at
    if (_type == 'presence') {
        presenceToUse = {'socketIOServer': _socketIOServer, 'url': _url};
        winston.debug('Presence to use: ' + presenceToUse.url);
    }
    else {
        realtimeToUse = {'socketIOServer': _socketIOServer, 'url': _url};
        winston.debug('Realtime to use: ' + realtimeToUse.url);
    }
}
<commit_msg>Remove unused ROOMID constant in matchmaker<commit_after>const program = require('commander');

program
    .version('1.0.0')
    .option('-p, --port <port>', 'specify the websocket port to listen to [9872]', 9872)
    .parse(process.argv);

const io = require('socket.io-client'),
      winston = require('winston'),
      config = require('./config.js');

winston.level = 'debug';
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {'timestamp': true, 'label': 'matchmaker'});

const PORT = program.port;
const SERVICES = config.services;

winston.info('Unichat matchmaker service');
winston.info('Listening on port ' + PORT);

let serviceSockets = [];
let presenceToUse = '';
let realtimeToUse = '';
let waitingClient = null;
for (var i=0; i < SERVICES.length; i++) {
    let _url = SERVICES[i].url;
    let _type = SERVICES[i].type;

    let _socketIOServer = io.connect(_url, {'forceNew': true});

    _socketIOServer.on('connect', () => {
        winston.debug('Connected to ' + _type + ' service at ' + _url);
        _socketIOServer.emit('register-matchmaker');
    });

    _socketIOServer.on('presence-find-partner', (cookieId) => {
        winston.debug('Finding partner for client with cookie: ' + cookieId);
        if (waitingClient === null) {
            waitingClient = {'cookie': cookieId, 'presenceSocket': _socketIOServer};
        }
        else {
            let roomId = Math.random().toString(36).substr(2, 5);

            _socketIOServer.emit('matchmaker-send-to-room', cookieId, realtimeToUse.url, roomId);
            winston.debug('Sent client with cookie (' + cookieId + ') to room: ' + roomId);
            waitingClient.presenceSocket.emit('matchmaker-send-to-room', waitingClient.cookie, realtimeToUse.url, roomId);
            winston.debug('Sent client with cookie (' + waitingClient.cookie + ') to room: ' + roomId);

            waitingClient = null;
        }
    });

    _socketIOServer.on('presence-client-disconnected', (cookieId) => {
        if (waitingClient !== null) {
            winston.debug('Removing from wait line client: ' + cookieId);
            waitingClient = null;
        }
    });

    serviceSockets.push(_socketIOServer);

    // Define the primary presence/realtime service that traffic is directed at
    if (_type == 'presence') {
        presenceToUse = {'socketIOServer': _socketIOServer, 'url': _url};
        winston.debug('Presence to use: ' + presenceToUse.url);
    }
    else {
        realtimeToUse = {'socketIOServer': _socketIOServer, 'url': _url};
        winston.debug('Realtime to use: ' + realtimeToUse.url);
    }
}
<|endoftext|>


<commit_before>
/* medea - an Open Source, WebGL-based 3d engine for next-generation browser games.
 * (or alternatively, for clumsy and mostly useless tech demos written solely for fun)
 *
 * medea is (c) 2011, Alexander C. Gessler
 * licensed under the terms and conditions of a 3 clause BSD license.
 */

medea.define('node',['frustum'],function(undefined) {
	"use strict";

	medea.BB_INFINITE = 'i';
	medea.BB_EMPTY = 'e';

	medea._NODE_FLAG_DIRTY = 0x1;
	medea._NODE_FLAG_DIRTY_BB = 0x2;
	medea._NODE_FLAG_DIRTY_GI = 0x4;

	medea.NODE_FLAG_NO_ROTATION = 0x40;
	medea.NODE_FLAG_NO_SCALING = 0x80;

	medea.NODE_FLAG_USER = 0x100000;

	var id_source = 0;

	this.Node = medea.Class.extend({

		// this is to allow subclasses to have their own flags set when the node's transformation
		// matrix is altered. By default we only set DIRTY.
		trafo_dirty_flag: medea._NODE_FLAG_DIRTY |
			medea._NODE_FLAG_DIRTY_GI |
			medea._NODE_FLAG_DIRTY_BB,

		parent:null,

		init : function(name, flags) {
			this.children = [];
			this.entities = [];
			this.id = id_source++;
			this.name = name || ("UnnamedNode_" + this.id);

			// for culling purposes, saves the index of the frustun plane
			// that caused this node to be culled recently. This exploits
			// temporal coherence in the scene.
			this.plane_hint = [0];

			this.listeners = {
				'OnUpdateGlobalTransform' : {},
				'OnUpdateBB' : {}
			};

			this.lmatrix = mat4.identity(mat4.create());
			this.gmatrix = mat4.create();
			this.gimatrix = mat4.create();

			this.bb = medea.CreateBB();

			this.flags = this.trafo_dirty_flag | (flags || 0);
			this.enabled = true;
		},

		Enabled : function(e) {
			if (e === undefined) {
				return this.enabled;
			}
			this.enabled = e;
		},

		Name : function(n) {
			if (n === undefined) {
				return this.name;
			}
			this.name = n;
		},

		GetEntities: function() {
			return this.entities;
		},

		GetActiveEntities: function(cam) {
			return this.entities;
		},

		AddEntity: function(ent) {
			// #ifdef DEBUG
			medea.DebugAssert(ent instanceof medea.Entity,'need valid entity to attach');
			// #endif

			this.entities.push(ent);
			ent.OnAttach(this);

			this._SetBBDirty();
		},

		// note: when doing this from within Entity.Update(), return medea.ENTITY_UPDATE_WAS_REMOVED
		RemoveEntity: function(ent) {
			var idx = this.entities.indexOf(ent);
			if(idx !== -1) {
				ent.OnDetach(this);

				this._SetBBDirty();
				this.entities.splice(idx,1);
			}
		},

		RemoveAllEntities: function(tag) {
			if(tag === undefined) {
				for (var i = 0; i < this.entities.length; ++i) {
					this.entities[i].OnDetach(this);
				}

				this.entities = [];
				this._SetBBDirty();
				return;
			}
			for (var i = 0; i < this.entities.length; ++i) {
				var ent = this.entities[i];
				if(ent.Tag() !== tag) {
					continue;
				}

				ent.OnDetach(this);

				this._SetBBDirty();
				this.entities.splice(i,1);
			}
		},

		FilterEntities : function(classes, callback) {
			if (Array.isArray(classes)) {
				var ce = classes.length;
				for (var i = 0, e = this.entities.length; i < e; ++i) {
					var ent = this.entities[i];
					for (var c = 0; c < ce; ++c) {
						if (ent instanceof classes[c]) {
							callback(ent);
						}
					}
				}
				return;
			}

			for (var i = 0, e = this.entities.length; i < e; ++i) {
				var ent = this.entities[i];
				if (ent instanceof classes) {
					callback(ent);
				}
			}
		},

		GetChildren: function() {
			return this.children;
		},

		GetParent: function() {
			return this.parent;
		},

		AddChild: function(child) {
			// #ifdef DEBUG
			medea.DebugAssert(child !== this,'cannot attach a node to itself');
			// #endif

			if(typeof child !== 'object' || !( child instanceof medea.Node )) {
				child = new medea.Node(child);
			}

			if(child.parent === this) {
				return;
			}

			this.children.push(child);
			this._SetBBDirty();

			child.OnAttach(this);
			return child;
		},

		RemoveChild: function(child) {
			var idx = this.children.indexOf(child);
			if(idx !== -1) {
				// #ifdef DEBUG
				medea.DebugAssert(child.parent === this,'inconsistent value for child.parent');
				// #endif

				this._SetBBDirty();
				child.OnAttach(null);

				this.children.splice(idx,1);
				child.parent = null;
			}
		},

		OnAttach : function(parent) {
			// #ifdef DEBUG
			medea.DebugAssert(parent !== this,'cannot attach node to itself');
			// #endif

			this.parent = parent;
			this._SetTrafoDirty();
		},

		Update: function(dtime) {
			// all regular updates are carried out lazily, so this is a no-op
		},

		GetWorldBB: function() {
			this._UpdateBB();
			return this.bb;
		},

		GetBB: function() {
			this._UpdateBB();
			return this.bb;
		},

		Cull : function(frustum) {
			return medea.BBInFrustum(frustum, this.GetWorldBB(), this.plane_hint);
		},

		// pure getter, nowadays deprecated
		GetLocalTransform: function() {
			return this.lmatrix;
		},

		LocalTransform: function(l, no_copy) {
			if(l === undefined) {
				return this.lmatrix;
			}

			this._SetTrafoDirty();

			if(no_copy) {
				this.lmatrix = l;
			}
			else {
				mat4.set(l, this.lmatrix);
			}
		},

		GetGlobalTransform: function() {
			this._UpdateGlobalTransform();
			return this.gmatrix;
		},

		GetInverseGlobalTransform: function() {
			this._UpdateInverseGlobalTransform();
			return this.gimatrix;
		},

		TryGetInverseGlobalTransform: function() {
			return this.flags & medea._NODE_FLAG_DIRTY_GI ? null : this.gimatrix;
		},

		Translate: function(vec) {
			mat4.translate(this.lmatrix,vec);
			this._SetTrafoDirty();
			return this;
		},

		TransformBy : function(mat) {
			mat4.multiply(this.lmatrix, mat, this.lmatrix);
			this._SetTrafoDirty();
			return this;
		},

		Rotate: function(angle,axis) {
			// #ifdef DEBUG
			medea.DebugAssert(!(this.flags & medea.NODE_FLAG_NO_ROTATION),'node cannot be rotated');
			// #endif

			mat4.rotate(this.lmatrix,angle,axis);
			this._SetTrafoDirty();
			return this;
		},

		Scale: function(s) {
			// #ifdef DEBUG
			medea.DebugAssert(!(this.flags & medea.NODE_FLAG_NO_SCALING),'node cannot be scaled');
			// #endif

			mat4.scale(this.lmatrix, typeof s === 'number' ? [s,s,s] : s);
			this._SetTrafoDirty();
			return this;
		},

		ScaleToFit : function(s) {
			var bb = this.GetBB(), m = Math.max, e;
			medea.DebugAssert(bb.length === 2, 'must be AABB');
			e = m(-bb[0][0],bb[1][0]);
			e = m(e,m(-bb[0][1],bb[1][1]));
			e = m(e,m(-bb[0][2],bb[1][2]));
			if(e > 1e-6) {
				this.Scale((s===undefined?1.0:s)/e);
			}
		},

		Center : function(s) {
			var bb = this.GetBB();
			medea.DebugAssert(bb.length === 2, 'must be AABB');
			var x = bb[1][0] - bb[0][0];
			var y = bb[1][1] - bb[0][1];
			var z = bb[1][2] - bb[0][2];
			var vec = [-(x/2 + bb[0][0]),-(y/2 + bb[0][1]),-(z/2 + bb[0][2])];
			this.Translate(vec);
		},

		ResetTransform: function() {
			mat4.identity(this.lmatrix);
			this._SetTrafoDirty();
			return this;
		},

		LocalXAxis: function(l) {
			if(l === undefined) {
				return [this.lmatrix[0],this.lmatrix[1],this.lmatrix[2]];
			}
			var m = this.lmatrix;
			m[0] = l[0];
			m[1] = l[1];
			m[2] = l[2];
			this._SetTrafoDirty();
		},

		LocalYAxis: function(l) {
			if(l === undefined) {
				return [this.lmatrix[4],this.lmatrix[5],this.lmatrix[6]];
			}
			var m = this.lmatrix;
			m[4] = l[0];
			m[5] = l[1];
			m[6] = l[2];
			this._SetTrafoDirty();
		},

		LocalZAxis: function(l) {
			if(l === undefined) {
				return [this.lmatrix[8],this.lmatrix[9],this.lmatrix[10]];
			}
			var m = this.lmatrix;
			m[8] = l[0];
			m[9] = l[1];
			m[10] = l[2];
			this._SetTrafoDirty();
		},

		LocalPos: function(l) {
			if(l === undefined) {
				return [this.lmatrix[12],this.lmatrix[13],this.lmatrix[14]];
			}
			var m = this.lmatrix;
			m[12] = l[0];
			m[13] = l[1];
			m[14] = l[2];
			this._SetTrafoDirty();
		},

		GetWorldPos : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[12],this.gmatrix[13],this.gmatrix[14]];
		},

		GetWorldXAxis : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[0],this.gmatrix[1],this.gmatrix[2]];
		},

		GetWorldYAxis : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[4],this.gmatrix[5],this.gmatrix[6]];
		},

		GetWorldZAxis : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[8],this.gmatrix[9],this.gmatrix[10]];
		},

		AddListener : function(what,l, key) {
			// #ifdef DEBUG
			if(!(what in this.listeners)) {
				medea.DebugAssert('listener not recognized ' + what);
			}
			// #endif
			this.listeners[what][key] = l;
		},

		RemoveListener : function(key) {
			for(var k in this.listeners) {
				try {
					delete this.listeners[k][key];
				}
				catch(e) {
				}
			}
		},

		_UpdateGlobalTransform: function() {
			if (!(this.flags & medea._NODE_FLAG_DIRTY)) {
				return;
			}
			this.flags &= ~medea._NODE_FLAG_DIRTY;
			if (this.parent) {
				mat4.multiply(this.parent.GetGlobalTransform(),this.lmatrix,this.gmatrix);
			}
			else {
				this.gmatrix = mat4.create( this.lmatrix );
			}

			this._FireListener("OnUpdateGlobalTransform");
		},

		_UpdateInverseGlobalTransform: function() {
			if (!(this.flags & medea._NODE_FLAG_DIRTY_GI)) {
				return;
			}

			this._UpdateGlobalTransform();

			this.flags &= ~medea._NODE_FLAG_DIRTY_GI;
			mat4.inverse(this.gmatrix,this.gimatrix);
		},

		_SetTrafoDirty : function() {
			this.flags |= this.trafo_dirty_flag;
			this._SetBBDirty();

			for( var i = 0, c = this.children, l = c.length; i < l; ++i) {
				c[i]._SetTrafoDirty();
			}
		},

		_SetBBDirty : function() {
			var node = this, flag = medea._NODE_FLAG_DIRTY_BB;
			do {
				node.flags |= flag;
				node = node.parent;
			}
			while(node != null);
		},

		_UpdateBB: function() {
			if (!(this.flags & medea._NODE_FLAG_DIRTY_BB)) {
				return;
			}
			this.flags &= ~medea._NODE_FLAG_DIRTY_BB;
			var bbs = [];

			for( var i = 0, c = this.children, l = c.length; i < l; ++i) {
				bbs.push(c[i].GetWorldBB());
			}

			// TODO: avoid temporary matrices
			var trafo = this.GetGlobalTransform();
			for( var i = 0, c = this.entities, l = c.length; i < l; ++i) {
				bbs.push(medea.TransformBB( c[i].BB(), trafo ));
			}

			this.bb = medea.MergeBBs(bbs);

			// #ifdef DEBUG
			medea.DebugAssert(!!this.bb,"bounding box computation failed, but it shouldn't have");
			// #endif

			this._FireListener("OnUpdateBB");
			return this.bb;
		},

		_FireListener : function(what) {
			var l = this.listeners[what];
			if(l) {
				for(var k in l) {
					l[k].apply(this,arguments);
				}
			}
		}
	});

	//
	medea.CreateNode = function(name, flags) {
		return new medea.Node(name, flags);
	};
});

<commit_msg>node: fix missing local medea object.<commit_after>
/* medea - an Open Source, WebGL-based 3d engine for next-generation browser games.
 * (or alternatively, for clumsy and mostly useless tech demos written solely for fun)
 *
 * medea is (c) 2011, Alexander C. Gessler
 * licensed under the terms and conditions of a 3 clause BSD license.
 */

medea.define('node',['frustum'],function(undefined) {
	"use strict";

	var medea = this;

	medea.BB_INFINITE = 'i';
	medea.BB_EMPTY = 'e';

	medea._NODE_FLAG_DIRTY = 0x1;
	medea._NODE_FLAG_DIRTY_BB = 0x2;
	medea._NODE_FLAG_DIRTY_GI = 0x4;

	medea.NODE_FLAG_NO_ROTATION = 0x40;
	medea.NODE_FLAG_NO_SCALING = 0x80;

	medea.NODE_FLAG_USER = 0x100000;

	var id_source = 0;

	medea.Node = medea.Class.extend({

		// this is to allow subclasses to have their own flags set when the node's transformation
		// matrix is altered. By default we only set DIRTY.
		trafo_dirty_flag: medea._NODE_FLAG_DIRTY |
			medea._NODE_FLAG_DIRTY_GI |
			medea._NODE_FLAG_DIRTY_BB,

		parent:null,

		init : function(name, flags) {
			this.children = [];
			this.entities = [];
			this.id = id_source++;
			this.name = name || ("UnnamedNode_" + this.id);

			// for culling purposes, saves the index of the frustun plane
			// that caused this node to be culled recently. This exploits
			// temporal coherence in the scene.
			this.plane_hint = [0];

			this.listeners = {
				'OnUpdateGlobalTransform' : {},
				'OnUpdateBB' : {}
			};

			this.lmatrix = mat4.identity(mat4.create());
			this.gmatrix = mat4.create();
			this.gimatrix = mat4.create();

			this.bb = medea.CreateBB();

			this.flags = this.trafo_dirty_flag | (flags || 0);
			this.enabled = true;
		},

		Enabled : function(e) {
			if (e === undefined) {
				return this.enabled;
			}
			this.enabled = e;
		},

		Name : function(n) {
			if (n === undefined) {
				return this.name;
			}
			this.name = n;
		},

		GetEntities: function() {
			return this.entities;
		},

		GetActiveEntities: function(cam) {
			return this.entities;
		},

		AddEntity: function(ent) {
			// #ifdef DEBUG
			medea.DebugAssert(ent instanceof medea.Entity,'need valid entity to attach');
			// #endif

			this.entities.push(ent);
			ent.OnAttach(this);

			this._SetBBDirty();
		},

		// note: when doing this from within Entity.Update(), return medea.ENTITY_UPDATE_WAS_REMOVED
		RemoveEntity: function(ent) {
			var idx = this.entities.indexOf(ent);
			if(idx !== -1) {
				ent.OnDetach(this);

				this._SetBBDirty();
				this.entities.splice(idx,1);
			}
		},

		RemoveAllEntities: function(tag) {
			if(tag === undefined) {
				for (var i = 0; i < this.entities.length; ++i) {
					this.entities[i].OnDetach(this);
				}

				this.entities = [];
				this._SetBBDirty();
				return;
			}
			for (var i = 0; i < this.entities.length; ++i) {
				var ent = this.entities[i];
				if(ent.Tag() !== tag) {
					continue;
				}

				ent.OnDetach(this);

				this._SetBBDirty();
				this.entities.splice(i,1);
			}
		},

		FilterEntities : function(classes, callback) {
			if (Array.isArray(classes)) {
				var ce = classes.length;
				for (var i = 0, e = this.entities.length; i < e; ++i) {
					var ent = this.entities[i];
					for (var c = 0; c < ce; ++c) {
						if (ent instanceof classes[c]) {
							callback(ent);
						}
					}
				}
				return;
			}

			for (var i = 0, e = this.entities.length; i < e; ++i) {
				var ent = this.entities[i];
				if (ent instanceof classes) {
					callback(ent);
				}
			}
		},

		GetChildren: function() {
			return this.children;
		},

		GetParent: function() {
			return this.parent;
		},

		AddChild: function(child) {
			// #ifdef DEBUG
			medea.DebugAssert(child !== this,'cannot attach a node to itself');
			// #endif

			if(typeof child !== 'object' || !( child instanceof medea.Node )) {
				child = new medea.Node(child);
			}

			if(child.parent === this) {
				return;
			}

			this.children.push(child);
			this._SetBBDirty();

			child.OnAttach(this);
			return child;
		},

		RemoveChild: function(child) {
			var idx = this.children.indexOf(child);
			if(idx !== -1) {
				// #ifdef DEBUG
				medea.DebugAssert(child.parent === this,'inconsistent value for child.parent');
				// #endif

				this._SetBBDirty();
				child.OnAttach(null);

				this.children.splice(idx,1);
				child.parent = null;
			}
		},

		OnAttach : function(parent) {
			// #ifdef DEBUG
			medea.DebugAssert(parent !== this,'cannot attach node to itself');
			// #endif

			this.parent = parent;
			this._SetTrafoDirty();
		},

		Update: function(dtime) {
			// all regular updates are carried out lazily, so this is a no-op
		},

		GetWorldBB: function() {
			this._UpdateBB();
			return this.bb;
		},

		GetBB: function() {
			this._UpdateBB();
			return this.bb;
		},

		Cull : function(frustum) {
			return medea.BBInFrustum(frustum, this.GetWorldBB(), this.plane_hint);
		},

		// pure getter, nowadays deprecated
		GetLocalTransform: function() {
			return this.lmatrix;
		},

		LocalTransform: function(l, no_copy) {
			if(l === undefined) {
				return this.lmatrix;
			}

			this._SetTrafoDirty();

			if(no_copy) {
				this.lmatrix = l;
			}
			else {
				mat4.set(l, this.lmatrix);
			}
		},

		GetGlobalTransform: function() {
			this._UpdateGlobalTransform();
			return this.gmatrix;
		},

		GetInverseGlobalTransform: function() {
			this._UpdateInverseGlobalTransform();
			return this.gimatrix;
		},

		TryGetInverseGlobalTransform: function() {
			return this.flags & medea._NODE_FLAG_DIRTY_GI ? null : this.gimatrix;
		},

		Translate: function(vec) {
			mat4.translate(this.lmatrix,vec);
			this._SetTrafoDirty();
			return this;
		},

		TransformBy : function(mat) {
			mat4.multiply(this.lmatrix, mat, this.lmatrix);
			this._SetTrafoDirty();
			return this;
		},

		Rotate: function(angle,axis) {
			// #ifdef DEBUG
			medea.DebugAssert(!(this.flags & medea.NODE_FLAG_NO_ROTATION),'node cannot be rotated');
			// #endif

			mat4.rotate(this.lmatrix,angle,axis);
			this._SetTrafoDirty();
			return this;
		},

		Scale: function(s) {
			// #ifdef DEBUG
			medea.DebugAssert(!(this.flags & medea.NODE_FLAG_NO_SCALING),'node cannot be scaled');
			// #endif

			mat4.scale(this.lmatrix, typeof s === 'number' ? [s,s,s] : s);
			this._SetTrafoDirty();
			return this;
		},

		ScaleToFit : function(s) {
			var bb = this.GetBB(), m = Math.max, e;
			medea.DebugAssert(bb.length === 2, 'must be AABB');
			e = m(-bb[0][0],bb[1][0]);
			e = m(e,m(-bb[0][1],bb[1][1]));
			e = m(e,m(-bb[0][2],bb[1][2]));
			if(e > 1e-6) {
				this.Scale((s===undefined?1.0:s)/e);
			}
		},

		Center : function(s) {
			var bb = this.GetBB();
			medea.DebugAssert(bb.length === 2, 'must be AABB');
			var x = bb[1][0] - bb[0][0];
			var y = bb[1][1] - bb[0][1];
			var z = bb[1][2] - bb[0][2];
			var vec = [-(x/2 + bb[0][0]),-(y/2 + bb[0][1]),-(z/2 + bb[0][2])];
			this.Translate(vec);
		},

		ResetTransform: function() {
			mat4.identity(this.lmatrix);
			this._SetTrafoDirty();
			return this;
		},

		LocalXAxis: function(l) {
			if(l === undefined) {
				return [this.lmatrix[0],this.lmatrix[1],this.lmatrix[2]];
			}
			var m = this.lmatrix;
			m[0] = l[0];
			m[1] = l[1];
			m[2] = l[2];
			this._SetTrafoDirty();
		},

		LocalYAxis: function(l) {
			if(l === undefined) {
				return [this.lmatrix[4],this.lmatrix[5],this.lmatrix[6]];
			}
			var m = this.lmatrix;
			m[4] = l[0];
			m[5] = l[1];
			m[6] = l[2];
			this._SetTrafoDirty();
		},

		LocalZAxis: function(l) {
			if(l === undefined) {
				return [this.lmatrix[8],this.lmatrix[9],this.lmatrix[10]];
			}
			var m = this.lmatrix;
			m[8] = l[0];
			m[9] = l[1];
			m[10] = l[2];
			this._SetTrafoDirty();
		},

		LocalPos: function(l) {
			if(l === undefined) {
				return [this.lmatrix[12],this.lmatrix[13],this.lmatrix[14]];
			}
			var m = this.lmatrix;
			m[12] = l[0];
			m[13] = l[1];
			m[14] = l[2];
			this._SetTrafoDirty();
		},

		GetWorldPos : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[12],this.gmatrix[13],this.gmatrix[14]];
		},

		GetWorldXAxis : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[0],this.gmatrix[1],this.gmatrix[2]];
		},

		GetWorldYAxis : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[4],this.gmatrix[5],this.gmatrix[6]];
		},

		GetWorldZAxis : function() {
			this._UpdateGlobalTransform();
			return [this.gmatrix[8],this.gmatrix[9],this.gmatrix[10]];
		},

		AddListener : function(what,l, key) {
			// #ifdef DEBUG
			if(!(what in this.listeners)) {
				medea.DebugAssert('listener not recognized ' + what);
			}
			// #endif
			this.listeners[what][key] = l;
		},

		RemoveListener : function(key) {
			for(var k in this.listeners) {
				try {
					delete this.listeners[k][key];
				}
				catch(e) {
				}
			}
		},

		_UpdateGlobalTransform: function() {
			if (!(this.flags & medea._NODE_FLAG_DIRTY)) {
				return;
			}
			this.flags &= ~medea._NODE_FLAG_DIRTY;
			if (this.parent) {
				mat4.multiply(this.parent.GetGlobalTransform(),this.lmatrix,this.gmatrix);
			}
			else {
				this.gmatrix = mat4.create( this.lmatrix );
			}

			this._FireListener("OnUpdateGlobalTransform");
		},

		_UpdateInverseGlobalTransform: function() {
			if (!(this.flags & medea._NODE_FLAG_DIRTY_GI)) {
				return;
			}

			this._UpdateGlobalTransform();

			this.flags &= ~medea._NODE_FLAG_DIRTY_GI;
			mat4.inverse(this.gmatrix,this.gimatrix);
		},

		_SetTrafoDirty : function() {
			this.flags |= this.trafo_dirty_flag;
			this._SetBBDirty();

			for( var i = 0, c = this.children, l = c.length; i < l; ++i) {
				c[i]._SetTrafoDirty();
			}
		},

		_SetBBDirty : function() {
			var node = this, flag = medea._NODE_FLAG_DIRTY_BB;
			do {
				node.flags |= flag;
				node = node.parent;
			}
			while(node != null);
		},

		_UpdateBB: function() {
			if (!(this.flags & medea._NODE_FLAG_DIRTY_BB)) {
				return;
			}
			this.flags &= ~medea._NODE_FLAG_DIRTY_BB;
			var bbs = [];

			for( var i = 0, c = this.children, l = c.length; i < l; ++i) {
				bbs.push(c[i].GetWorldBB());
			}

			// TODO: avoid temporary matrices
			var trafo = this.GetGlobalTransform();
			for( var i = 0, c = this.entities, l = c.length; i < l; ++i) {
				bbs.push(medea.TransformBB( c[i].BB(), trafo ));
			}

			this.bb = medea.MergeBBs(bbs);

			// #ifdef DEBUG
			medea.DebugAssert(!!this.bb,"bounding box computation failed, but it shouldn't have");
			// #endif

			this._FireListener("OnUpdateBB");
			return this.bb;
		},

		_FireListener : function(what) {
			var l = this.listeners[what];
			if(l) {
				for(var k in l) {
					l[k].apply(this,arguments);
				}
			}
		}
	});

	//
	medea.CreateNode = function(name, flags) {
		return new medea.Node(name, flags);
	};
});

<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react'
import openURL from '../util/open-url'
import {defaultColor, fontSizeToSizeStyle, lineClamp, metaData} from './text.meta.native'
import * as Styled from '../styles'
import shallowEqual from 'shallowequal'
import {NativeClipboard, NativeText, NativeStyleSheet, NativeAlert} from './native-wrappers.native'
import type {Props, TextType, Background} from './text'

const StyledText = Styled.styled(NativeText)({}, props => props.style)

const backgroundModes = [
  'Normal',
  'Announcements',
  'Success',
  'Information',
  'HighRisk',
  'Documentation',
  'Terminal',
]

const styleMap = Object.keys(metaData).reduce(
  (map, type: TextType) => {
    const meta = metaData[type]
    backgroundModes.forEach(mode => {
      map[`${type}:${mode}`] = {
        ...fontSizeToSizeStyle(meta.fontSize),
        color: meta.colorForBackgroundMode[mode] || defaultColor(mode),
        ...meta.styleOverride,
      }
    })
    return map
  },
  {
    center: {textAlign: 'center'},
  }
)

const styles = NativeStyleSheet.create(styleMap)

// Init common styles for perf

class Text extends Component<Props> {
  static defaultProps = {
    allowFontScaling: false,
  }
  _nativeText: any

  highlightText() {
    // ignored
  }

  focus() {
    if (this._nativeText) {
      this._nativeText.focus()
    }
  }

  _urlClick = () => {
    openURL(this.props.onClickURL)
  }

  _urlCopy = (url: ?string) => {
    if (!url) return
    NativeClipboard.setString(url)
  }

  _urlChooseOption = () => {
    const url = this.props.onLongPressURL
    if (!url) return
    NativeAlert.alert('', url, [
      {style: 'cancel', text: 'Cancel'},
      {onPress: () => openURL(url), text: 'Open Link'},
      {onPress: () => this._urlCopy(url), text: 'Copy Link'},
    ])
  }

  shouldComponentUpdate(nextProps: Props): boolean {
    return !shallowEqual(this.props, nextProps, (obj, oth, key) => {
      if (key === 'style') {
        return shallowEqual(obj, oth)
      } else if (key === 'children' && this.props.plainText && nextProps.plainText) {
        // child will be plain text
        return shallowEqual(obj, oth)
      }
      return undefined
    })
  }

  render() {
    const baseStyle = styles[`${this.props.type}:${this.props.backgroundMode || 'Normal'}`]
    const dynamicStyle = {
      ...(this.props.backgroundMode === 'Normal'
        ? {}
        : _getStyle(
            this.props.type,
            this.props.backgroundMode,
            this.props.lineClamp,
            !!this.props.onClick,
            !!this.props.underline
          )),
    }

    let style
    if (!Object.keys(dynamicStyle).length) {
      style =
        this.props.style || this.props.center
          ? [baseStyle, this.props.center && styles.center, this.props.style]
          : baseStyle
    } else {
      style = [baseStyle, dynamicStyle, this.props.center && styles.center, this.props.style]
    }

    const onPress =
      this.props.onClick ||
      (this.props.onClickURL ? this._urlClick : undefined) ||
      // If selectable and there isn't already an onClick handler,
      // make a dummy one so that it shows the selection (on iOS).
      (this.props.selectable ? () => {} : undefined)

    const onLongPress =
      this.props.onLongPress || (this.props.onLongPressURL ? this._urlChooseOption : undefined)

    return (
      <StyledText
        ref={ref => {
          this._nativeText = ref
        }}
        selectable={this.props.selectable}
        style={style}
        {...lineClamp(this.props.lineClamp, this.props.ellipsizeMode)}
        onPress={onPress}
        onLongPress={onLongPress}
        allowFontScaling={this.props.allowFontScaling}
      >
        {this.props.children}
      </StyledText>
    )
  }
}

// external things call this so leave the original alone
function _getStyle(
  type: TextType,
  backgroundMode?: Background = 'Normal',
  lineClampNum?: ?number,
  clickable?: ?boolean,
  forceUnderline: boolean
) {
  const meta = metaData[type]
  const colorStyle = {color: meta.colorForBackgroundMode[backgroundMode] || defaultColor(backgroundMode)}
  const textDecoration = meta.isLink || forceUnderline ? {textDecorationLine: 'underline'} : {}

  return {
    ...colorStyle,
    ...textDecoration,
  }
}
function getStyle(
  type: TextType,
  backgroundMode?: Background = 'Normal',
  lineClampNum?: ?number,
  clickable?: ?boolean
) {
  const meta = metaData[type]
  const sizeStyle = fontSizeToSizeStyle(meta.fontSize)
  const colorStyle = {color: meta.colorForBackgroundMode[backgroundMode] || defaultColor(backgroundMode)}
  const textDecoration = meta.isLink && backgroundMode !== 'Normal' ? {textDecorationLine: 'underline'} : {}

  return {
    ...sizeStyle,
    ...colorStyle,
    ...textDecoration,
    ...meta.styleOverride,
  }
}

export default Text
export {getStyle}
export {Text as TextMixed}
export {allTextTypes} from './text.shared'
<commit_msg>don't underline all links on mobile<commit_after>// @flow
import React, {Component} from 'react'
import openURL from '../util/open-url'
import {defaultColor, fontSizeToSizeStyle, lineClamp, metaData} from './text.meta.native'
import * as Styled from '../styles'
import shallowEqual from 'shallowequal'
import {NativeClipboard, NativeText, NativeStyleSheet, NativeAlert} from './native-wrappers.native'
import type {Props, TextType, Background} from './text'

const StyledText = Styled.styled(NativeText)({}, props => props.style)

const backgroundModes = [
  'Normal',
  'Announcements',
  'Success',
  'Information',
  'HighRisk',
  'Documentation',
  'Terminal',
]

const styleMap = Object.keys(metaData).reduce(
  (map, type: TextType) => {
    const meta = metaData[type]
    backgroundModes.forEach(mode => {
      map[`${type}:${mode}`] = {
        ...fontSizeToSizeStyle(meta.fontSize),
        color: meta.colorForBackgroundMode[mode] || defaultColor(mode),
        ...meta.styleOverride,
      }
    })
    return map
  },
  {
    center: {textAlign: 'center'},
  }
)

const styles = NativeStyleSheet.create(styleMap)

// Init common styles for perf

class Text extends Component<Props> {
  static defaultProps = {
    allowFontScaling: false,
  }
  _nativeText: any

  highlightText() {
    // ignored
  }

  focus() {
    if (this._nativeText) {
      this._nativeText.focus()
    }
  }

  _urlClick = () => {
    openURL(this.props.onClickURL)
  }

  _urlCopy = (url: ?string) => {
    if (!url) return
    NativeClipboard.setString(url)
  }

  _urlChooseOption = () => {
    const url = this.props.onLongPressURL
    if (!url) return
    NativeAlert.alert('', url, [
      {style: 'cancel', text: 'Cancel'},
      {onPress: () => openURL(url), text: 'Open Link'},
      {onPress: () => this._urlCopy(url), text: 'Copy Link'},
    ])
  }

  shouldComponentUpdate(nextProps: Props): boolean {
    return !shallowEqual(this.props, nextProps, (obj, oth, key) => {
      if (key === 'style') {
        return shallowEqual(obj, oth)
      } else if (key === 'children' && this.props.plainText && nextProps.plainText) {
        // child will be plain text
        return shallowEqual(obj, oth)
      }
      return undefined
    })
  }

  render() {
    const baseStyle = styles[`${this.props.type}:${this.props.backgroundMode || 'Normal'}`]
    const dynamicStyle = {
      ...(this.props.backgroundMode === 'Normal'
        ? {}
        : _getStyle(
            this.props.type,
            this.props.backgroundMode,
            this.props.lineClamp,
            !!this.props.onClick,
            !!this.props.underline
          )),
    }

    let style
    if (!Object.keys(dynamicStyle).length) {
      style =
        this.props.style || this.props.center
          ? [baseStyle, this.props.center && styles.center, this.props.style]
          : baseStyle
    } else {
      style = [baseStyle, dynamicStyle, this.props.center && styles.center, this.props.style]
    }

    const onPress =
      this.props.onClick ||
      (this.props.onClickURL ? this._urlClick : undefined) ||
      // If selectable and there isn't already an onClick handler,
      // make a dummy one so that it shows the selection (on iOS).
      (this.props.selectable ? () => {} : undefined)

    const onLongPress =
      this.props.onLongPress || (this.props.onLongPressURL ? this._urlChooseOption : undefined)

    return (
      <StyledText
        ref={ref => {
          this._nativeText = ref
        }}
        selectable={this.props.selectable}
        style={style}
        {...lineClamp(this.props.lineClamp, this.props.ellipsizeMode)}
        onPress={onPress}
        onLongPress={onLongPress}
        allowFontScaling={this.props.allowFontScaling}
      >
        {this.props.children}
      </StyledText>
    )
  }
}

// external things call this so leave the original alone
function _getStyle(
  type: TextType,
  backgroundMode?: Background = 'Normal',
  lineClampNum?: ?number,
  clickable?: ?boolean,
  forceUnderline: boolean
) {
  if (backgroundMode === 'Normal') {
    return forceUnderline ? {textDecorationLine: 'underline'} : {}
  }
  const meta = metaData[type]
  const colorStyle = {color: meta.colorForBackgroundMode[backgroundMode] || defaultColor(backgroundMode)}
  const textDecoration = meta.isLink ? {textDecorationLine: 'underline'} : {}

  return {
    ...colorStyle,
    ...textDecoration,
  }
}
function getStyle(
  type: TextType,
  backgroundMode?: Background = 'Normal',
  lineClampNum?: ?number,
  clickable?: ?boolean
) {
  const meta = metaData[type]
  const sizeStyle = fontSizeToSizeStyle(meta.fontSize)
  const colorStyle = {color: meta.colorForBackgroundMode[backgroundMode] || defaultColor(backgroundMode)}
  const textDecoration = meta.isLink && backgroundMode !== 'Normal' ? {textDecorationLine: 'underline'} : {}

  return {
    ...sizeStyle,
    ...colorStyle,
    ...textDecoration,
    ...meta.styleOverride,
  }
}

export default Text
export {getStyle}
export {Text as TextMixed}
export {allTextTypes} from './text.shared'
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react'
import openURL from '../util/open-url'
import {fontSizeToSizeStyle, lineClamp, metaData} from './text.meta.native'
import * as Styles from '../styles'
import shallowEqual from 'shallowequal'
import {NativeClipboard, NativeText, NativeStyleSheet, NativeAlert} from './native-wrappers.native'
import type {Props, TextType} from './text'

const StyledText = Styles.styled(NativeText)({}, props => props.style)

const modes = ['positive', 'negative']

const styleMap = Object.keys(metaData).reduce(
  (map, type: TextType) => {
    const meta = metaData[type]
    modes.forEach(mode => {
      map[`${type}:${mode}`] = {
        ...fontSizeToSizeStyle(meta.fontSize),
        color: meta.colorForBackground[mode] || Styles.globalColors.black,
        ...meta.styleOverride,
      }
    })
    return map
  },
  {
    center: {textAlign: 'center'},
  }
)

const styles = NativeStyleSheet.create(styleMap)

// Init common styles for perf

class Text extends Component<Props> {
  static defaultProps = {
    allowFontScaling: false,
  }
  _nativeText: any

  highlightText() {
    // ignored
  }

  focus() {
    if (this._nativeText) {
      this._nativeText.focus()
    }
  }

  _urlClick = () => {
    openURL(this.props.onClickURL)
  }

  _urlCopy = (url: ?string) => {
    if (!url) return
    NativeClipboard.setString(url)
  }

  _urlChooseOption = () => {
    const url = this.props.onLongPressURL
    if (!url) return
    NativeAlert.alert('', url, [
      {style: 'cancel', text: 'Cancel'},
      {onPress: () => openURL(url), text: 'Open Link'},
      {onPress: () => this._urlCopy(url), text: 'Copy Link'},
    ])
  }

  shouldComponentUpdate(nextProps: Props): boolean {
    return !shallowEqual(this.props, nextProps, (obj, oth, key) => {
      if (key === 'style') {
        return shallowEqual(obj, oth)
      } else if (key === 'children' && this.props.plainText && nextProps.plainText) {
        // child will be plain text
        return shallowEqual(obj, oth)
      }
      return undefined
    })
  }

  render() {
    const baseStyle = styles[`${this.props.type}:${this.props.negative ? 'negative' : 'positive'}`]
    const dynamicStyle = this.props.negative
      ? _getStyle(
          this.props.type,
          this.props.negative,
          this.props.lineClamp,
          !!this.props.onClick,
          !!this.props.underline
        )
      : {}

    let style
    if (!Object.keys(dynamicStyle).length) {
      style =
        this.props.style || this.props.center
          ? [baseStyle, this.props.center && styles.center, this.props.style]
          : baseStyle
    } else {
      style = [baseStyle, dynamicStyle, this.props.center && styles.center, this.props.style]
    }

    const onPress =
      this.props.onClick ||
      (this.props.onClickURL ? this._urlClick : undefined) ||
      // If selectable and there isn't already an onClick handler,
      // make a dummy one so that it shows the selection (on iOS).
      (this.props.selectable ? () => {} : undefined)

    const onLongPress =
      this.props.onLongPress || (this.props.onLongPressURL ? this._urlChooseOption : undefined)

    return (
      <StyledText
        ref={ref => {
          this._nativeText = ref
        }}
        selectable={this.props.selectable}
        style={style}
        {...lineClamp(this.props.lineClamp, this.props.ellipsizeMode)}
        onPress={onPress}
        onLongPress={onLongPress}
        allowFontScaling={this.props.allowFontScaling}
      >
        {this.props.children}
      </StyledText>
    )
  }
}

// external things call this so leave the original alone
function _getStyle(
  type: TextType,
  negative?: boolean,
  lineClampNum?: ?number,
  clickable?: ?boolean,
  forceUnderline: boolean
) {
  if (!negative) {
    return forceUnderline ? {textDecorationLine: 'underline'} : {}
  }
  // negative === true
  const meta = metaData[type]
  const colorStyle = {color: meta.colorForBackground.negative}
  const textDecoration = meta.isLink ? {textDecorationLine: 'underline'} : {}

  return {
    ...colorStyle,
    ...textDecoration,
  }
}
function getStyle(type: TextType, negative?: boolean, lineClampNum?: ?number, clickable?: ?boolean) {
  const meta = metaData[type]
  const sizeStyle = fontSizeToSizeStyle(meta.fontSize)
  const colorStyle = {color: meta.colorForBackground[negative ? 'negative' : 'positive']}
  const textDecoration = meta.isLink && negative ? {textDecorationLine: 'underline'} : {}

  return {
    ...sizeStyle,
    ...colorStyle,
    ...textDecoration,
    ...meta.styleOverride,
  }
}

export default Text
export {getStyle}
export {Text as TextMixed}
export {allTextTypes} from './text.shared'
<commit_msg>fix native Text underline prop (#16789)<commit_after>// @flow
import React, {Component} from 'react'
import openURL from '../util/open-url'
import {fontSizeToSizeStyle, lineClamp, metaData} from './text.meta.native'
import * as Styles from '../styles'
import shallowEqual from 'shallowequal'
import {NativeClipboard, NativeText, NativeStyleSheet, NativeAlert} from './native-wrappers.native'
import type {Props, TextType} from './text'

const StyledText = Styles.styled(NativeText)({}, props => props.style)

const modes = ['positive', 'negative']

const styleMap = Object.keys(metaData).reduce(
  (map, type: TextType) => {
    const meta = metaData[type]
    modes.forEach(mode => {
      map[`${type}:${mode}`] = {
        ...fontSizeToSizeStyle(meta.fontSize),
        color: meta.colorForBackground[mode] || Styles.globalColors.black,
        ...meta.styleOverride,
        ...(meta.isLink ? {textDecorationLine: 'underline'} : {}),
      }
    })
    return map
  },
  {
    center: {textAlign: 'center'},
  }
)

const styles = NativeStyleSheet.create(styleMap)

// Init common styles for perf

class Text extends Component<Props> {
  static defaultProps = {
    allowFontScaling: false,
  }
  _nativeText: any

  highlightText() {
    // ignored
  }

  focus() {
    if (this._nativeText) {
      this._nativeText.focus()
    }
  }

  _urlClick = () => {
    openURL(this.props.onClickURL)
  }

  _urlCopy = (url: ?string) => {
    if (!url) return
    NativeClipboard.setString(url)
  }

  _urlChooseOption = () => {
    const url = this.props.onLongPressURL
    if (!url) return
    NativeAlert.alert('', url, [
      {style: 'cancel', text: 'Cancel'},
      {onPress: () => openURL(url), text: 'Open Link'},
      {onPress: () => this._urlCopy(url), text: 'Copy Link'},
    ])
  }

  shouldComponentUpdate(nextProps: Props): boolean {
    return !shallowEqual(this.props, nextProps, (obj, oth, key) => {
      if (key === 'style') {
        return shallowEqual(obj, oth)
      } else if (key === 'children' && this.props.plainText && nextProps.plainText) {
        // child will be plain text
        return shallowEqual(obj, oth)
      }
      return undefined
    })
  }

  render() {
    const baseStyle = styles[`${this.props.type}:${this.props.negative ? 'negative' : 'positive'}`]
    const dynamicStyle = this.props.underline ? {textDecorationLine: 'underline'} : {}

    let style
    if (!Object.keys(dynamicStyle).length) {
      style =
        this.props.style || this.props.center
          ? [baseStyle, this.props.center && styles.center, this.props.style]
          : baseStyle
    } else {
      style = [baseStyle, dynamicStyle, this.props.center && styles.center, this.props.style]
    }

    const onPress =
      this.props.onClick ||
      (this.props.onClickURL ? this._urlClick : undefined) ||
      // If selectable and there isn't already an onClick handler,
      // make a dummy one so that it shows the selection (on iOS).
      (this.props.selectable ? () => {} : undefined)

    const onLongPress =
      this.props.onLongPress || (this.props.onLongPressURL ? this._urlChooseOption : undefined)

    return (
      <StyledText
        ref={ref => {
          this._nativeText = ref
        }}
        selectable={this.props.selectable}
        style={style}
        {...lineClamp(this.props.lineClamp, this.props.ellipsizeMode)}
        onPress={onPress}
        onLongPress={onLongPress}
        allowFontScaling={this.props.allowFontScaling}
      >
        {this.props.children}
      </StyledText>
    )
  }
}

// external things call this
function getStyle(type: TextType, negative?: boolean, lineClampNum?: ?number, clickable?: ?boolean) {
  const meta = metaData[type]
  const sizeStyle = fontSizeToSizeStyle(meta.fontSize)
  const colorStyle = {color: meta.colorForBackground[negative ? 'negative' : 'positive']}
  const textDecoration = meta.isLink && negative ? {textDecorationLine: 'underline'} : {}

  return {
    ...sizeStyle,
    ...colorStyle,
    ...textDecoration,
    ...meta.styleOverride,
  }
}

export default Text
export {getStyle}
export {Text as TextMixed}
export {allTextTypes} from './text.shared'
<|endoftext|>


<commit_before>/*
 * showfor.js
 * Scripts for the showfor browser/os detection.
 *
 * Depends on: browserdetect.js
 * Optional for dhtml selects: jquery.selectbox-1.2.js
 */

(function($) {

"use strict";  // following the Ricky kbox awesomeness

var OSES, BROWSERS, VERSIONS, MISSING_MSG;
var ShowFor = {
    // Return the browser and version that appears to be running. Possible
    // values resemble {fx4, fx35, m1, m11}. Return undefined if the currently
    // running browser can't be identified.
    detectBrowser: function() {
        function getVersionGroup(browser, version) {
            if ((browser === undefined) || (version === undefined) || !VERSIONS[browser]) {
                return;
            }

            for (var i = 0; i < VERSIONS[browser].length; i++) {
                if (version < VERSIONS[browser][i][0]) {
                    return browser + VERSIONS[browser][i][1];
                }
            }
        }
        return getVersionGroup(BrowserDetect.browser, BrowserDetect.version);
    },

    // Treat the hash fragment of the URL as a querystring (e.g.
    // #os=this&browser=that), and return an object with a property for each
    // param. May not handle URL escaping yet.
    hashFragment: function() {
        var o = {},
            args = document.location.hash.substr(1).split('&'),
            chunks;
        for (var i = 0; i < args.length; i++) {
            chunks = args[i].split('=');
            o[chunks[0]] = chunks[1];
        }
        return o;
    },

    // Hide/show the proper page sections that are marked with {for} tags as
    // applying to only certain browsers or OSes. Update the table of contents
    // to reflect what was hidden/shown.
    initForTags: function() {
        var self = this,
            $osMenu = $('#os'),
            $browserMenu = $('#browser'),
            $origBrowserOptions = $browserMenu.find('option').clone(),
            $body = $('body'),
            hash = self.hashFragment(),
            isSetManually;

        OSES = $osMenu.data('oses');  // {'mac': true, 'win': true, ...}
        BROWSERS = $browserMenu.data('browsers');  // {'fx4': true, ...}
        VERSIONS = $browserMenu.data('version-groups');  // {'fx': [[3.4999, '3'], [3.9999, '35']], 'm': [[1.0999, '1'], [1.9999, '11']]}
        MISSING_MSG = gettext('[missing header]');

        // Make the 'Table of Contents' header localizable.
        $('#toc > h2').text(gettext('Table of Contents'));

        function updateForsAndToc() {
            // Hide and show document sections accordingly:
            self.showAndHideFors($('select#os').val(),
                                 $('select#browser').val());

            // Update the table of contents in case headers were hidden or shown:
            $('#toc > :not(h2)').remove(); // __TOC__ generates <ul/>'s.
            $('#toc').append(self.filteredToc($('#doc-content'), '#toc h2'));

            return false;
        }

        // If there isn't already a hash for purposes of actual navigation,
        // stick our {for} settings in there.
        function updateHashFragment() {
            var hash = self.hashFragment();

            // Kind of a shortcut. What we really want to know is "Is there anything in the hash fragment that isn't a {for} selector?"
            if (!document.location.hash || hash.hasOwnProperty("os") || hash.hasOwnProperty("browser")) {
                var newHash = "os=" + $osMenu.val() + "&browser=" + $browserMenu.val();
                document.location.replace(document.location.href.split('#')[0] + '#' + newHash);
            }
        }

        // Persist the menu selection in a cookie and hash fragment.
        function persistSelection() {
            $.cookie("for_os", $osMenu.val(), {path: '/'});
            $.cookie("for_browser", $browserMenu.val(), {path: '/'});
            updateHashFragment();
        }

        // Clear the menu selection cookies.
        function clearSelectionCookies() {
            $.cookie("for_os", null, {path: '/'});
            $.cookie("for_browser", null, {path: '/'});
        }

        // Get the dependency based on the currently selected OS
        function getCurrentDependency() {
            return $osMenu.find('[value="' + $osMenu.val() + '"]')
                          .data('dependency');
        }

        //Handle OS->Browser dependencies
        function handleDependencies(evt, noRedirect) {
            var currentDependency = getCurrentDependency(),
                currentBrowser, newBrowser, availableBrowsers;

            if (!noRedirect && $body.is('.mobile, .desktop') &&
                !$body.is('.' + currentDependency)) {
                // If we are on the mobile page and select a desktop OS,
                // redirect to the desktop home page. And vice-versa.
                // TODO: maybe use data-* attrs for the URLs?
                persistSelection();
                var url = document.location.href;
                if ($body.is('.mobile')) {
                    document.location = url.replace('/mobile', '/home');
                } else {
                    document.location = url.replace('/home', '/mobile');
                }
            }

            currentBrowser = $browserMenu.val();
            availableBrowsers = $origBrowserOptions.filter(
                '[data-dependency="' + currentDependency + '"]');
            $browserMenu.empty().append(availableBrowsers);

            // Set browser to same version (frex, m4->fx4), if possible.
            var version = currentBrowser.replace(/^\D+/,'');
            $browserMenu.find('option').each(function() {
                var $this = $(this);
                if ($this.val().replace(/^\D+/,'') == version) {
                    $browserMenu.val($this.val());
                }
            });
            self.updateShowforSelectors();
        }

        // Select the right item from the browser or OS menu, taking cues from
        // the following places, in order: the URL hash fragment, the cookie,
        // and client detection. Return whether the item appears to have
        // selected manually: that is, via a cookie or a hash fragment.
        function setSelectorValue(cookieName, hashName, hash, detector, $menu) {
            var initial = hash[hashName],
                isManual = true;
            if (!initial) {
                initial = $.cookie(cookieName);
                if (!initial) {
                    initial = detector();
                    isManual = false;
                }
            }
            if (initial) {
                $menu.val(initial);  // does not fire change event
                self.updateShowforSelectors();
            }
            return isManual;
        }

        // Set the selector value to the first option that doesn't
        // have the passed in dependency.
        function setSelectorDefault($select, dependency) {
            $select.val(
                $select.find('option:not([data-dependency="' + dependency +
                             '"]):first').attr('value'));
        }

        // If we are on mobile or desktop home page, make sure
        // appropriate OS is selected
        function checkSelectorValues() {
            var currentDependency,
                isManual = false;

            if ($body.is('.desktop, .mobile')) {
                currentDependency = getCurrentDependency();
                // currentDependency will be 'desktop' or 'mobile'
                // Make sure we are on the corresponding home page. Otherwise,
                // change the selection appropriately.
                if (!$body.is('.' + currentDependency)) {
                    var $detectedOS = $osMenu.find('[value=' + BrowserDetect.OS + ']');
                    if ($detectedOS.data('dependency') != currentDependency) {
                        // The detected OS is valid. Make it the new selection.
                        $osMenu.val($detectedOS.attr('value'));
                        $browserMenu.val(ShowFor.detectBrowser());
                        clearSelectionCookies();
                    } else {
                        // Force a new selection.
                        setSelectorDefault($osMenu, currentDependency);
                        setSelectorDefault($browserMenu, currentDependency);

                        // Set the cookie so that the selection sticks when
                        // browsing to articles.
                        persistSelection();
                        isManual = true;
                    }
                }
            }
            return isManual;
        }

        // Select the sniffed, cookied, or hashed browser or OS if there is one:
        isSetManually = setSelectorValue("for_os", "os", hash, function() { return BrowserDetect.OS; }, $osMenu);
        isSetManually |= setSelectorValue("for_browser", "browser", hash, ShowFor.detectBrowser, $browserMenu);
        isSetManually |= checkSelectorValues();

        // Possibly change the settings based on dependency rules:
        handleDependencies(null, true);

        if (isSetManually) {
            updateHashFragment();
        }

        $osMenu.change(handleDependencies);
        $osMenu.change(persistSelection);
        $osMenu.change(updateForsAndToc);
        $browserMenu.change(persistSelection);
        $browserMenu.change(updateForsAndToc);

        // Fire off the change handler for the first time:
        updateForsAndToc();
    },

    // Return a table of contents (an <ul>) listing the visible headers within
    // elements in the $pageBody set.
    //
    // The highest header level found within $pageBody is considered to be the
    // top of the TOC: if $pageBody has h2s but no h1s, h2s will be used as the
    // first level of the TOC. Missing headers (such as if you follow an h2
    // directly with an h4) are noted prominently so you can fix them.
    //
    // excludesSelector is an optional jQuery selector for excluding certain
    // headings from the table of contents.
    filteredToc: function($pageBody, excludesSelector) {
        function headerLevel(index, hTag) {
            return parseInt(hTag.tagName.charAt(1), 10);
        }

        var $headers = $pageBody.find(':header:not(:hidden)'),  // :hidden is a little overkill, but it's short.
            $root = $('<ul />'),
            $cur_ul = $root,
            ul_level = Math.min.apply(Math, $headers.map(headerLevel).get());

        // For each header in the document, look upward until you hit something that's hidden. If nothing is found, add the header to the TOC.
        $headers.each(function addIfShown(index) {
            var h_level = headerLevel(0, this),
                $h = $(this);

            if (excludesSelector && $h.is(excludesSelector)) {
                // Skip excluded headers.
                return;
            }

            // If we're too far down the tree, walk up it.
            for (; ul_level > h_level; ul_level--) {
                $cur_ul = $cur_ul.parent().closest('ul');
            }

            // If we're too far up the tree, walk down it, create <ul>s until we aren't:
            for (; ul_level < h_level; ul_level++) {
                var $last_li = $cur_ul.children().last();
                if ($last_li.length === 0) {
                    $last_li = $('<li />').append($('<em />')
                                                  .text(MISSING_MSG))
                                          .appendTo($cur_ul);
                }
                // Now the current <ul> ends in an <li>, one way or another.
                $cur_ul = $('<ul />').appendTo($last_li);
            }

            // Now $cur_ul is at exactly the right level to add a header by appending an <li>.
            // Clone the header, remove any hidden elements and get the text,
            // and replace back with the clone.
            var $tmpClone = $h.clone(),
                text = $h.find(':hidden').remove().end().text();
            $h.replaceWith($tmpClone);
            $cur_ul.append($('<li />').text(text).wrapInner($('<a>').attr('href', '#' + $h.attr('id'))));
        });
        return $root;
    },

    // Set the {for} nodes to the proper visibility for the given OS and
    // browser combination.
    //
    // Hidden are {for}s that {list at least one OS but not the passed-in one}
    // or that {list at least one browser but not the passed-in one}. Also, the
    // entire condition can be inverted by prefixing it with "not ", as in {for
    // not mac,linux}.
    showAndHideFors: function(os, browser) {
        $('.for').each(function(index) {
            var osAttrs = {}, browserAttrs = {},
                foundAnyOses = false, foundAnyBrowsers = false,
                forData,
                isInverted,
                shouldHide;

            // Catch the "not" operator if it's there:
            forData = $(this).data('for');
            if (!forData) {
                // If the data-for attribute is missing, move on.
                return;
            }

            isInverted = forData.substring(0, 4) == 'not ';
            if (isInverted) {
                forData = forData.substring(4);  // strip off "not "
            }

            // Divide {for} attrs into OSes and browsers:
            $(forData.split(',')).each(function(index) {
                if (OSES[this] != undefined) {
                    osAttrs[this] = true;
                    foundAnyOses = true;
                } else if (BROWSERS[this] != undefined) {
                    browserAttrs[this] = true;
                    foundAnyBrowsers = true;
                }
            });

            shouldHide = (foundAnyOses && osAttrs[os] == undefined) ||
                         (foundAnyBrowsers && browserAttrs[browser] == undefined);
            if ((shouldHide && !isInverted) || (!shouldHide && isInverted)) {
                $(this).hide();  // saves original visibility, which is nice but not necessary
            }
            else {
                $(this).show();  // restores original visibility
            }
        });
    },

    updateShowforSelectors: function() {
        if ($.fn.selectbox) {
            $('#support-for input.selectbox, #support-for div.selectbox-wrapper').remove();
            $('#support-for select').selectbox();
        } else {
            $('#support-for select').removeAttr('disabled');
        }
    }

};

window.ShowFor = ShowFor;

})(jQuery);
<commit_msg>Added special cases for SHOWFOR. * Show android/m4 when desktop browser is selected. * Show win/fx4 when mobile browser is selected.<commit_after>/*
 * showfor.js
 * Scripts for the showfor browser/os detection.
 *
 * Depends on: browserdetect.js
 * Optional for dhtml selects: jquery.selectbox-1.2.js
 */

(function($) {

"use strict";  // following the Ricky kbox awesomeness

var OSES, BROWSERS, VERSIONS, MISSING_MSG;
var ShowFor = {
    // Return the browser and version that appears to be running. Possible
    // values resemble {fx4, fx35, m1, m11}. Return undefined if the currently
    // running browser can't be identified.
    detectBrowser: function() {
        function getVersionGroup(browser, version) {
            if ((browser === undefined) || (version === undefined) || !VERSIONS[browser]) {
                return;
            }

            for (var i = 0; i < VERSIONS[browser].length; i++) {
                if (version < VERSIONS[browser][i][0]) {
                    return browser + VERSIONS[browser][i][1];
                }
            }
        }
        return getVersionGroup(BrowserDetect.browser, BrowserDetect.version);
    },

    // Treat the hash fragment of the URL as a querystring (e.g.
    // #os=this&browser=that), and return an object with a property for each
    // param. May not handle URL escaping yet.
    hashFragment: function() {
        var o = {},
            args = document.location.hash.substr(1).split('&'),
            chunks;
        for (var i = 0; i < args.length; i++) {
            chunks = args[i].split('=');
            o[chunks[0]] = chunks[1];
        }
        return o;
    },

    // Hide/show the proper page sections that are marked with {for} tags as
    // applying to only certain browsers or OSes. Update the table of contents
    // to reflect what was hidden/shown.
    initForTags: function() {
        var self = this,
            $osMenu = $('#os'),
            $browserMenu = $('#browser'),
            $origBrowserOptions = $browserMenu.find('option').clone(),
            $body = $('body'),
            hash = self.hashFragment(),
            isSetManually;

        OSES = $osMenu.data('oses');  // {'mac': true, 'win': true, ...}
        BROWSERS = $browserMenu.data('browsers');  // {'fx4': true, ...}
        VERSIONS = $browserMenu.data('version-groups');  // {'fx': [[3.4999, '3'], [3.9999, '35']], 'm': [[1.0999, '1'], [1.9999, '11']]}
        MISSING_MSG = gettext('[missing header]');

        // Make the 'Table of Contents' header localizable.
        $('#toc > h2').text(gettext('Table of Contents'));

        function updateForsAndToc() {
            // Hide and show document sections accordingly:
            self.showAndHideFors($('select#os').val(),
                                 $('select#browser').val());

            // Update the table of contents in case headers were hidden or shown:
            $('#toc > :not(h2)').remove(); // __TOC__ generates <ul/>'s.
            $('#toc').append(self.filteredToc($('#doc-content'), '#toc h2'));

            return false;
        }

        // If there isn't already a hash for purposes of actual navigation,
        // stick our {for} settings in there.
        function updateHashFragment() {
            var hash = self.hashFragment();

            // Kind of a shortcut. What we really want to know is "Is there anything in the hash fragment that isn't a {for} selector?"
            if (!document.location.hash || hash.hasOwnProperty("os") || hash.hasOwnProperty("browser")) {
                var newHash = "os=" + $osMenu.val() + "&browser=" + $browserMenu.val();
                document.location.replace(document.location.href.split('#')[0] + '#' + newHash);
            }
        }

        // Persist the menu selection in a cookie and hash fragment.
        function persistSelection() {
            $.cookie("for_os", $osMenu.val(), {path: '/'});
            $.cookie("for_browser", $browserMenu.val(), {path: '/'});
            updateHashFragment();
        }

        // Clear the menu selection cookies.
        function clearSelectionCookies() {
            $.cookie("for_os", null, {path: '/'});
            $.cookie("for_browser", null, {path: '/'});
        }

        // Get the dependency based on the currently selected OS
        function getCurrentDependency() {
            return $osMenu.find('[value="' + $osMenu.val() + '"]')
                          .data('dependency');
        }

        //Handle OS->Browser dependencies
        function handleDependencies(evt, noRedirect) {
            var currentDependency = getCurrentDependency(),
                currentBrowser, newBrowser, availableBrowsers;

            if (!noRedirect && $body.is('.mobile, .desktop') &&
                !$body.is('.' + currentDependency)) {
                // If we are on the mobile page and select a desktop OS,
                // redirect to the desktop home page. And vice-versa.
                // TODO: maybe use data-* attrs for the URLs?
                persistSelection();
                var url = document.location.href;
                if ($body.is('.mobile')) {
                    document.location = url.replace('/mobile', '/home');
                } else {
                    document.location = url.replace('/home', '/mobile');
                }
            }

            currentBrowser = $browserMenu.val();
            availableBrowsers = $origBrowserOptions.filter(
                '[data-dependency="' + currentDependency + '"]');
            $browserMenu.empty().append(availableBrowsers);

            // Set browser to same version (frex, m4->fx4), if possible.
            var version = currentBrowser.replace(/^\D+/,'');
            $browserMenu.find('option').each(function() {
                var $this = $(this);
                if ($this.val().replace(/^\D+/,'') == version) {
                    $browserMenu.val($this.val());
                }
            });
            self.updateShowforSelectors();
        }

        // Select the right item from the browser or OS menu, taking cues from
        // the following places, in order: the URL hash fragment, the cookie,
        // and client detection. Return whether the item appears to have
        // selected manually: that is, via a cookie or a hash fragment.
        function setSelectorValue(cookieName, hashName, hash, detector, $menu) {
            var initial = hash[hashName],
                isManual = true;
            if (!initial) {
                initial = $.cookie(cookieName);
                if (!initial) {
                    initial = detector();
                    isManual = false;
                }
            }
            if (initial) {
                $menu.val(initial);  // does not fire change event
                self.updateShowforSelectors();
            }
            return isManual;
        }

        // Set the selector value to the first option that doesn't
        // have the passed in dependency.
        function setSelectorDefault($select, dependency) {
            $select.val(
                $select.find('option:not([data-dependency="' + dependency +
                             '"]):first').attr('value'));
        }

        // If we are on mobile or desktop home page, make sure
        // appropriate OS is selected
        function checkSelectorValues() {
            var currentDependency,
                isManual = false;

            if ($body.is('.desktop, .mobile')) {
                currentDependency = getCurrentDependency();
                // currentDependency will be 'desktop' or 'mobile'
                // Make sure we are on the corresponding home page. Otherwise,
                // change the selection appropriately.
                if (!$body.is('.' + currentDependency)) {
                    var $detectedOS = $osMenu.find('[value=' + BrowserDetect.OS + ']');
                    if ($detectedOS.data('dependency') != currentDependency) {
                        // The detected OS is valid. Make it the new selection.
                        $osMenu.val($detectedOS.attr('value'));
                        $browserMenu.val(ShowFor.detectBrowser());
                        clearSelectionCookies();
                    } else {
                        // Force a new selection.
                        setSelectorDefault($osMenu, currentDependency);
                        setSelectorDefault($browserMenu, currentDependency);

                        // Set the cookie so that the selection sticks when
                        // browsing to articles.
                        persistSelection();
                        isManual = true;
                    }
                }
            }
            return isManual;
        }

        // Select the sniffed, cookied, or hashed browser or OS if there is one:
        isSetManually = setSelectorValue("for_os", "os", hash, function() { return BrowserDetect.OS; }, $osMenu);
        isSetManually |= setSelectorValue("for_browser", "browser", hash, ShowFor.detectBrowser, $browserMenu);
        isSetManually |= checkSelectorValues();

        // Possibly change the settings based on dependency rules:
        handleDependencies(null, true);

        if (isSetManually) {
            updateHashFragment();
        }

        $osMenu.change(handleDependencies);
        $osMenu.change(persistSelection);
        $osMenu.change(updateForsAndToc);
        $browserMenu.change(persistSelection);
        $browserMenu.change(updateForsAndToc);

        // Fire off the change handler for the first time:
        updateForsAndToc();
    },

    // Return a table of contents (an <ul>) listing the visible headers within
    // elements in the $pageBody set.
    //
    // The highest header level found within $pageBody is considered to be the
    // top of the TOC: if $pageBody has h2s but no h1s, h2s will be used as the
    // first level of the TOC. Missing headers (such as if you follow an h2
    // directly with an h4) are noted prominently so you can fix them.
    //
    // excludesSelector is an optional jQuery selector for excluding certain
    // headings from the table of contents.
    filteredToc: function($pageBody, excludesSelector) {
        function headerLevel(index, hTag) {
            return parseInt(hTag.tagName.charAt(1), 10);
        }

        var $headers = $pageBody.find(':header:not(:hidden)'),  // :hidden is a little overkill, but it's short.
            $root = $('<ul />'),
            $cur_ul = $root,
            ul_level = Math.min.apply(Math, $headers.map(headerLevel).get());

        // For each header in the document, look upward until you hit something that's hidden. If nothing is found, add the header to the TOC.
        $headers.each(function addIfShown(index) {
            var h_level = headerLevel(0, this),
                $h = $(this);

            if (excludesSelector && $h.is(excludesSelector)) {
                // Skip excluded headers.
                return;
            }

            // If we're too far down the tree, walk up it.
            for (; ul_level > h_level; ul_level--) {
                $cur_ul = $cur_ul.parent().closest('ul');
            }

            // If we're too far up the tree, walk down it, create <ul>s until we aren't:
            for (; ul_level < h_level; ul_level++) {
                var $last_li = $cur_ul.children().last();
                if ($last_li.length === 0) {
                    $last_li = $('<li />').append($('<em />')
                                                  .text(MISSING_MSG))
                                          .appendTo($cur_ul);
                }
                // Now the current <ul> ends in an <li>, one way or another.
                $cur_ul = $('<ul />').appendTo($last_li);
            }

            // Now $cur_ul is at exactly the right level to add a header by appending an <li>.
            // Clone the header, remove any hidden elements and get the text,
            // and replace back with the clone.
            var $tmpClone = $h.clone(),
                text = $h.find(':hidden').remove().end().text();
            $h.replaceWith($tmpClone);
            $cur_ul.append($('<li />').text(text).wrapInner($('<a>').attr('href', '#' + $h.attr('id'))));
        });
        return $root;
    },

    // Set the {for} nodes to the proper visibility for the given OS and
    // browser combination.
    //
    // Hidden are {for}s that {list at least one OS but not the passed-in one}
    // or that {list at least one browser but not the passed-in one}. Also, the
    // entire condition can be inverted by prefixing it with "not ", as in {for
    // not mac,linux}.
    showAndHideFors: function(os, browser) {
        $('.for').each(function(index) {
            var osAttrs = {}, browserAttrs = {},
                foundAnyOses = false, foundAnyBrowsers = false,
                forData,
                isInverted,
                shouldHide;

            // Catch the "not" operator if it's there:
            forData = $(this).data('for');
            if (!forData) {
                // If the data-for attribute is missing, move on.
                return;
            }

            isInverted = forData.substring(0, 4) == 'not ';
            if (isInverted) {
                forData = forData.substring(4);  // strip off "not "
            }

            // Divide {for} attrs into OSes and browsers:
            $(forData.split(',')).each(function(index) {
                if (OSES[this] != undefined) {
                    osAttrs[this] = true;
                    foundAnyOses = true;
                } else if (BROWSERS[this] != undefined) {
                    browserAttrs[this] = true;
                    foundAnyBrowsers = true;
                }
            });

            shouldHide = ((foundAnyOses && osAttrs[os] == undefined) ||
                         (foundAnyBrowsers && browserAttrs[browser] == undefined)) &&
                         // Special cases ):
                         // TODO: make this easier to maintain somehow?
                         // Show android/m4 on desktop selection
                         !(osAttrs['android'] && os !== 'maemo' /* only one mobile browser ATM */) &&
                         !(browserAttrs['m4'] && browser !== 'm4' && (osAttrs['android'] || !foundAnyOses)) &&
                         // Show win/fx4 on mobile selection
                         !(osAttrs['win'] && (os === 'android' || os == 'maemo') && (browserAttrs['fx4'] || !foundAnyBrowsers)) &&
                         !(browserAttrs['fx4'] && browser === 'm4' && (osAttrs['win'] || !foundAnyOses));

            if ((shouldHide && !isInverted) || (!shouldHide && isInverted)) {
                $(this).hide();  // saves original visibility, which is nice but not necessary
            }
            else {
                $(this).show();  // restores original visibility
            }
        });
    },

    updateShowforSelectors: function() {
        if ($.fn.selectbox) {
            $('#support-for input.selectbox, #support-for div.selectbox-wrapper').remove();
            $('#support-for select').selectbox();
        } else {
            $('#support-for select').removeAttr('disabled');
        }
    }

};

window.ShowFor = ShowFor;

})(jQuery);
<|endoftext|>


<commit_before>/*
Copyright (c) 2007 Alexander MacCaw

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.
*/

if (typeof Prototype == 'undefined') throw("Juggernaut error. Prototype could not be found.");
if (Prototype.Version < "1.6") throw("Juggernaut error. Prototype 1.6.0 is required.");

var Juggernaut = Class.create({
  is_connected: false,
  attempting_to_reconnect: false,
  ever_been_connected: false,
  hasFirebug: "console" in window && "firebug" in window.console && window.console.firebug.indexOf("1.0") > -1,

  logger: function(msg) {
    if (this.options.debug) {
      msg = "Juggernaut: " + msg + " on " + this.options.host + ':' + this.options.port;
      this.hasFirebug ? console.log(msg) : alert(msg);
    }
  },

  initialize: function(options) {
    this.options = options.evalJSON();
    Event.observe(window, 'load', function() {
      this.appendFlashObject();
      juggernaut = this;
    }.bind(this));
  },

  initialized: function(){
    this.connect();
  },

  broadcast: function(body, type, client_ids, channels){
    var msg = new Hash();
    msg.set('command',  'broadcast');
    msg.set('body',     body);
    msg.set('type',     this.type);
    if(channels)  msg.set('channels', channels);
    if(client_ids) msg.set('client_ids', client_ids);
    this.sendData(msg.toJSON());
  },

  sendData: function(data){
    this.swf.sendData(escape(data));
  },

  connect: function(){
    if(!this.is_connected) this.swf.connect(this.options.host, this.options.port);
  },

  disconnect: function(){
    if(this.is_connected) {
      this.swf.disconnect();
      this.is_connected = false;
    }
  },

  connected: function(e) {
    var handshake = new Hash();
    handshake.set('command', 'subscribe');
    if(this.options.session_id) handshake.set('session_id', this.options.session_id);
    if(this.options.client_id)  handshake.set('client_id',  this.options.client_id);
    if(this.options.channels)   handshake.set('channels',   this.options.channels);
    if(this.currentMsgId) {
      handshake.set('last_msg_id', this.currentMsgId);
      handshake.set('signature',   this.currentSignature);
    }
    this.sendData(handshake.toJSON());
    this.ever_been_connected = true;
    this.is_connected = true;
    setTimeout(function(){
      if(this.is_connected) this.attempting_to_reconnect = false;
    }.bind(this), 1 * 1000);
    this.logger('Connected');
  },

  receiveData: function(e) {
     var msg = unescape(e.memo.toString()).evalJSON();
     this.currentMsgId = msg.id;
     this.currentSignature = msg.signature;
     this.logger("Received data:\n" + msg.body + "\n");
     eval(msg.body);
  },

  appendFlashObject: function() {
    if($(this.options.swf_name)) {
      throw("Juggernaut error. 'swf_name' must be unique per juggernaut instance.");
    }
    this.element = new Element('div', {
      className: 'juggernaut'
    });
    this.element.setStyle({
      height: this.options.height,
      width:  this.options.width
    });
    $(document.body).insert({ bottom: this.element });
    this.so = new SWFObject(
      this.options.swf_address,
      this.options.swf_name,
      this.options.width,
      this.options.height,
      this.options.flash_version,
      this.options.flash_color
    );
    this.so.useExpressInstall(this.options.ei_swf_address);
	  this.so.addVariable("bridgeName", this.options.bridge_name);
    this.so.write(this.element);
    this.swf = $(this.options.swf_name);
  },

  refreshFlashObject: function(){
    this.so.write(this.element);
    this.swf = $(this.options.swf_name);
  },

  errorConnecting: function(e) {
    this.is_connected = false;
    if(!this.attempting_to_reconnect) {
      this.logger('There has been an error connecting');
      this.reconnect();
    }
  },

  disconnected: function(e) {
    this.is_connected = false;
    if(!this.attempting_to_reconnect) {
      this.logger('Connection has been lost');
      this.reconnect();
    }
  },

  reconnect: function(){
    if(this.options.reconnect_attempts){
      this.attempting_to_reconnect = true;
      this.logger('Will attempt to reconnect ' + this.options.reconnect_attempts + ' times,\
the first in ' + (this.options.reconnect_intervals || 3) + ' seconds');
      for(var i=0; i < this.options.reconnect_attempts; i++){
        setTimeout(function(){
          if(!this.is_connected){
            this.logger('Attempting reconnect');
            if(!this.ever_been_connected){
              this.refreshFlashObject();
            } else {
              this.connect();
            }
          }
        }.bind(this), (this.options.reconnect_intervals || 3) * 1000 * (i + 1))
      }
    }
  }

});<commit_msg><commit_after>/*
Copyright (c) 2007 Alexander MacCaw

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.
*/

if (typeof Prototype == 'undefined') throw("Juggernaut error. Prototype could not be found.");
if (Prototype.Version < "1.6") throw("Juggernaut error. Prototype 1.6.0 is required.");

var Juggernaut = Class.create({
  is_connected: false,
  attempting_to_reconnect: false,
  ever_been_connected: false,
  hasFirebug: "console" in window && "firebug" in window.console && window.console.firebug.indexOf("1.0") > -1,

  logger: function(msg) {
    if (this.options.debug) {
      msg = "Juggernaut: " + msg + " on " + this.options.host + ':' + this.options.port;
      this.hasFirebug ? console.log(msg) : alert(msg);
    }
  },

  initialize: function(options) {
    this.options = options.evalJSON();
    Event.observe(window, 'load', function() {
      this.appendFlashObject();
      juggernaut = this;
    }.bind(this));
  },

  initialized: function(){
    this.connect();
  },

  broadcast: function(body, type, client_ids, channels){
    var msg = new Hash();
    msg.set('command',  'broadcast');
    msg.set('body',     body);
    msg.set('type',     this.type);
    if(channels)  msg.set('channels', channels);
    if(client_ids) msg.set('client_ids', client_ids);
    this.sendData(msg.toJSON());
  },

  sendData: function(data){
    this.swf.sendData(escape(data));
  },

  connect: function(){
    if(!this.is_connected) this.swf.connect(this.options.host, this.options.port);
  },

  disconnect: function(){
    if(this.is_connected) {
      this.swf.disconnect();
      this.is_connected = false;
    }
  },

  connected: function(e) {
    var handshake = new Hash();
    handshake.set('command', 'subscribe');
    if(this.options.session_id) handshake.set('session_id', this.options.session_id);
    if(this.options.client_id)  handshake.set('client_id',  this.options.client_id);
    if(this.options.channels)   handshake.set('channels',   this.options.channels);
    if(this.currentMsgId) {
      handshake.set('last_msg_id', this.currentMsgId);
      handshake.set('signature',   this.currentSignature);
    }
    this.sendData(handshake.toJSON());
    this.ever_been_connected = true;
    this.is_connected = true;
    setTimeout(function(){
      if(this.is_connected) this.attempting_to_reconnect = false;
    }.bind(this), 1 * 1000);
    this.logger('Connected');
  },

  receiveData: function(e) {
     var msg = unescape(e.toString()).evalJSON();
     this.currentMsgId = msg.id;
     this.currentSignature = msg.signature;
     this.logger("Received data:\n" + msg.body + "\n");
     eval(msg.body);
  },

  appendFlashObject: function() {
    if($(this.options.swf_name)) {
      throw("Juggernaut error. 'swf_name' must be unique per juggernaut instance.");
    }
    this.element = new Element('div', {
      className: 'juggernaut'
    });
    this.element.setStyle({
      height: this.options.height,
      width:  this.options.width
    });
    $(document.body).insert({ bottom: this.element });
    this.so = new SWFObject(
      this.options.swf_address,
      this.options.swf_name,
      this.options.width,
      this.options.height,
      this.options.flash_version,
      this.options.flash_color
    );
    this.so.useExpressInstall(this.options.ei_swf_address);
	  this.so.addVariable("bridgeName", this.options.bridge_name);
    this.so.write(this.element);
    this.swf = $(this.options.swf_name);
  },

  refreshFlashObject: function(){
    this.so.write(this.element);
    this.swf = $(this.options.swf_name);
  },

  errorConnecting: function(e) {
    this.is_connected = false;
    if(!this.attempting_to_reconnect) {
      this.logger('There has been an error connecting');
      this.reconnect();
    }
  },

  disconnected: function(e) {
    this.is_connected = false;
    if(!this.attempting_to_reconnect) {
      this.logger('Connection has been lost');
      this.reconnect();
    }
  },

  reconnect: function(){
    if(this.options.reconnect_attempts){
      this.attempting_to_reconnect = true;
      this.logger('Will attempt to reconnect ' + this.options.reconnect_attempts + ' times,\
the first in ' + (this.options.reconnect_intervals || 3) + ' seconds');
      for(var i=0; i < this.options.reconnect_attempts; i++){
        setTimeout(function(){
          if(!this.is_connected){
            this.logger('Attempting reconnect');
            if(!this.ever_been_connected){
              this.refreshFlashObject();
            } else {
              this.connect();
            }
          }
        }.bind(this), (this.options.reconnect_intervals || 3) * 1000 * (i + 1))
      }
    }
  }

});<|endoftext|>


<commit_before>// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
<commit_msg>Delete underscore.js<commit_after><|endoftext|>


<commit_before><commit_msg>fixed bug<commit_after><|endoftext|>


<commit_before><commit_msg>[#9] example migration for watchlist<commit_after><|endoftext|>


<commit_before><commit_msg>fetch options from referenced strings<commit_after><|endoftext|>