<commit_before>'use strict';

var Router = require('../src/js/router.js'),
  PageView = require('../src/js/framework/page');

describe('Router', function() {

  var router;

  beforeEach(function() {
    router = new Router();
  });

  describe('The Routes', function() {

    describe('home', function() {
      xit('should load the home screen', function() {
        router.home();
      });
    });

    describe('contacts', function() {
      xit('should load the contacts screen', function() {
        router.contacts();
        expect(true).toBeTruthy();
      });
    });

  });

  describe('changeView', function() {

    var view;

    beforeEach(function() {
      view = new PageView();
    });

    describe('Loading a new view', function() {

      it('should set the set the current view', function() {
        router.changeView(view);
        expect(router.currentView).toEqual(view);
      });

      it('should render the current view', function() {
        spyOn(view, 'render').and.callThrough();
        router.changeView(view);
        expect(view.render).toHaveBeenCalled();
      });

      it('should setup the buttons', function() {
        spyOn(view, 'setButtonEvents');
        router.changeView(view);
        expect(view.setButtonEvents).toHaveBeenCalled();
      });

      xit('should render the view in the watch face', function() {

      });

    });

    describe('When there is an existing view', function() {

      it('should remove the old view', function() {
        spyOn(router, '_removeOldView');
        router.currentView = new PageView();
        router.changeView(view);
        expect(router._removeOldView).toHaveBeenCalled();
      });

    });

  });

});
<commit_msg>Better test coverage for router<commit_after>'use strict';

var Router = require('../src/js/router.js'),
  PageView = require('../src/js/framework/page'),
  HomePage = require('../src/js/views/homePage'),
  ContactPage = require('../src/js/views/contactsPage');

describe('Router', function() {

  var router;

  beforeEach(function() {
    router = new Router();
  });

  describe('The Routes', function() {

    beforeEach(function () {
      spyOn(router, 'changeView');
    });

    describe('home', function() {
      it('should load the home screen', function() {
        router.home();
        var isHomePage = router.changeView.calls.argsFor(0)[0] instanceof HomePage;
        expect(isHomePage).toBeTruthy();
      });
    });

    describe('contacts', function() {
      it('should load the contacts screen', function() {
        router.contacts();
        var isContactPage = router.changeView.calls.argsFor(0)[0] instanceof ContactPage;
        expect(isContactPage).toBeTruthy();
      });
    });

  });

  describe('changeView', function() {

    var view;

    beforeEach(function() {
      view = new PageView();
    });

    describe('Loading a new view', function() {

      it('should set the set the current view', function() {
        router.changeView(view);
        expect(router.currentView).toEqual(view);
      });

      it('should render the current view', function() {
        spyOn(view, 'render').and.callThrough();
        router.changeView(view);
        expect(view.render).toHaveBeenCalled();
      });

      it('should setup the buttons', function() {
        spyOn(view, 'setButtonEvents');
        router.changeView(view);
        expect(view.setButtonEvents).toHaveBeenCalled();
      });

      xit('should render the view in the watch face', function() {

      });

    });

    describe('When there is an existing view', function() {

      it('should remove the old view', function() {
        spyOn(router, '_removeOldView');
        router.currentView = new PageView();
        router.changeView(view);
        expect(router._removeOldView).toHaveBeenCalled();
      });

    });

  });

});
<|endoftext|>


<commit_before>
Ext.namespace("GeoExplorer");

/** api: constructor
 *  A tool to display all previously saved versions of a map, along with date and user.
 */
GeoExplorer.MapSnapshotGrid =  function(mapid) {

/*
var snapReader = new Ext.data.JsonReader({
    // metadata configuration options:
    idProperty: 'id',
    // the fields config option will internally create an Ext.data.Record
    // constructor that provides mapping for reading the record data objects
    fields: [
        // map Record's 'firstname' field to data object's key of same name
        {name: 'user', mapping: 'user'},
        // map Record's 'job' field to data object's 'occupation' key
        {name: 'date', mapping: 'created_dttm'},

        {name: 'link', mapping: 'snapshot'}
    ]
});
*/

var store = new Ext.data.JsonStore({
    url: '/maps/history/' + mapid,
    fields: [{name:'created', type: 'date'}, 'user', 'url', 'map'],
    idProperty: 'url',
    root: '',
    sortInfo: {
        field: 'created',
        direction: 'DESC' // or 'DESC' (case sensitive for local sorting)
    }
});

    var renderDate = function(value, p, record){
        return String.format(
                '{2}',
                record.data.map, record.id, value);
    };

    var renderUser = function(value, p, record){
        return String.format(
                '{1}',
                value, value);
    };

var grid = new Ext.grid.GridPanel({
        width:400,
        height:300,
        store: store,
        trackMouseOver:false,

        // grid columns
        columns:[{
            header: "Revision Date",
            dataIndex: 'created',
            width: 200,
            renderer: renderDate,
            sortable: true
        },{
            header: "URL",
            dataIndex: 'url',
            width: 10,
            hidden: true,
            sortable: false
        },{
            header: "User",
            dataIndex: 'user',
            width: 200,
            align: 'right',
            renderer: renderUser,
            sortable: true
        },{
            header: "Map",
            dataIndex: 'map',
            width: 10,
            align: 'right',
            hidden:true,
            sortable: false
        }],

        // customize view config
        viewConfig: {
            forceFit:true
        }
    });

    var historyWindow = new Ext.Window({
            title: 'Map Revision History',
            closeAction: 'destroy',
            items: grid,
            modal: true,
            autoScroll: true
        });

    // trigger the data store load
    store.load();

    historyWindow.show();

}<commit_msg>Remove old unused code<commit_after>
Ext.namespace("GeoExplorer");

/** api: constructor
 *  A tool to display all previously saved versions of a map, along with date and user.
 */
GeoExplorer.MapSnapshotGrid =  function(mapid) {

var store = new Ext.data.JsonStore({
    url: '/maps/history/' + mapid,
    fields: [{name:'created', type: 'date'}, 'user', 'url', 'map'],
    idProperty: 'url',
    root: '',
    sortInfo: {
        field: 'created',
        direction: 'DESC' // or 'DESC' (case sensitive for local sorting)
    }
});

    var renderDate = function(value, p, record){
        return String.format(
                '{2}',
                record.data.map, record.id, value);
    };

    var renderUser = function(value, p, record){
        return String.format(
                '{1}',
                value, value);
    };

var grid = new Ext.grid.GridPanel({
        width:400,
        height:300,
        store: store,
        trackMouseOver:false,

        // grid columns
        columns:[{
            header: "Revision Date",
            dataIndex: 'created',
            width: 200,
            renderer: renderDate,
            sortable: true
        },{
            header: "URL",
            dataIndex: 'url',
            width: 10,
            hidden: true,
            sortable: false
        },{
            header: "User",
            dataIndex: 'user',
            width: 200,
            align: 'right',
            renderer: renderUser,
            sortable: true
        },{
            header: "Map",
            dataIndex: 'map',
            width: 10,
            align: 'right',
            hidden:true,
            sortable: false
        }],

        // customize view config
        viewConfig: {
            forceFit:true
        }
    });

    var historyWindow = new Ext.Window({
            title: 'Map Revision History',
            closeAction: 'destroy',
            items: grid,
            modal: true,
            autoScroll: true
        });

    // trigger the data store load
    store.load();

    historyWindow.show();

}<|endoftext|>


<commit_before>if (!window.console) {
    var nop = function() { };
    window.console = { log: nop,
		       warn: nop,
		       error: nop };
}

var dummyData = '';
for(var i = 0; i < 16 * 1024; i++)
    dummyData += 'Z';

function human(i) {
    var unit = '';
    var units = ['K', 'M', 'G', 'T'];
    while(i > 1024) {
	i /= 1024;
	unit = units.shift();
    }
    return (Math.round(i * 100) / 100) + ' ' + unit;
}

function Ping(ws) {
    this.ws = ws;
    this.rttSum = 0;
    this.pings = 0;
    $('#ping').text('Opening...');
}

Ping.proto = 'ping';

Ping.prototype.onOpen = function() {
    this.ping();
    $('#ping').text('Open');
};

Ping.prototype.onMessage = function(msg) {
    if (msg !== 'pong') {
	console.warn('Not a pong, but: ' + msg);
	return;
    }

    var rtt = Date.now() - this.lastPing;
    this.pings++;
    this.rttSum += rtt;
    if (!this.minRtt ||
	rtt < this.minRtt)
	this.minRtt = rtt;
    if (!this.maxRtt ||
	rtt > this.maxRtt)
	this.maxRtt = rtt;

    $('#ping').empty();
    $('#ping').append(this.pings + ' pongs, avg: ' +
		    Math.round(this.rttSum / this.pings) +
		    ' ms
min: ' + this.minRtt +
		    ' ms, max: ' + this.maxRtt + 'ms');

    if (!this.done)
	this.ping();  // next
    else
	this.ws.close();
};

Ping.prototype.ping = function() {
    this.ws.send('ping');

    this.lastPing = Date.now();
};

Ping.prototype.onDone = function() {
    // Don't close immediately but wait for the last pong
    this.done = true;
};

function Download(ws) {
    this.ws = ws;
    $('#download').text('Opening...');
}

Download.proto = 'download';

Download.prototype.onOpen = function() {
    this.ws.send(getDuration());
    $('#download').text('Open');
};

Download.prototype.onMessage = function(msg) {
    var that = this;

    if (!this.startTime) {
	this.startTime = Date.now();
	this.lastMessage = this.startTime + 1;
	this.bytesRecvd = 0;
    } else {
	this.lastMessage = Date.now();
	this.bytesRecvd += msg.length;

	// Schedule (complex) update:
	if (!this.textUpdate) {
	    this.textUpdate = setTimeout(function() {
		delete that.textUpdate;

		var elapsed = that.lastMessage - that.startTime;
		$('#download').empty();
		$('#download').append(human(that.bytesRecvd * 1000 / (elapsed)) + 'B/s
' +
				      human(that.bytesRecvd) + 'B in ' + elapsed +
				      ' ms');
	    }, 100);
	}
    }
};

Download.prototype.onDone = function() {
    this.ws.close();
};

function Upload(ws) {
    this.ws = ws;
    $('#upload').text('Opening...');
}

Upload.proto = 'upload';

Upload.prototype.onOpen = function() {
    var that = this;
    this.startTime = Date.now();
    this.lastMessage = this.startTime + 1;
    this.bytesSent = 0;
    this.interval = setInterval(function() {
	var sent = false;
	while(that.ws.bufferedAmount < 1) {
	    that.ws.send(dummyData);
	    that.lastMessage = Date.now();
	    that.bytesSent += dummyData.length;
	    sent = true;
	}
	if (sent && !that.textUpdate) {
	    that.textUpdate = setTimeout(function() {
		delete that.textUpdate;

		var elapsed = that.lastMessage - that.startTime;
		$('#upload').empty();
		$('#upload').append(human(that.bytesSent * 1000 / elapsed) + 'B/s
' +
				    human(that.bytesSent) + 'B in ' + elapsed +
				    ' ms');
	    }, 100);
	}
    }, 1);
};

Upload.prototype.onDone = function() {
    clearInterval(this.interval);
    this.ws.close();
};

function runTest(t, cb) {
    var url = 'ws://' + document.location.host + '/';
    var ws = new WebSocket(url, t.proto);
    var test = new t(ws);

    ws.onopen = function() {
	console.log('open');
	test.onOpen();
    };
    ws.onmessage = function(ev) {
	test.onMessage(ev.data);
    };
    ws.onclose = function() {
	console.log('close');
	cb();
    };
    ws.onerror = function(e) {
	console.error(e);
	test.onError(e);
    };

    setTimeout(function() {
	test.onDone();
    }, getDuration() * 1000);
}

if (!WebSocket) {
    $('#broken').text('Your browser does not support the WebSocket protocol :-(');
} else {
    $('#broken').remove();

    $('body').append('
<input type="submit" id="run" value="Run"> each test for <input id="duration" value="5" size="2"> seconds
');
    $('body').append('

');
    var fields = { ping: 'Roundtrip delay time',
		   download: 'Downstream bandwidth',
		   upload: 'Upstream bandwidth' };
    for(var id in fields) {
	$('#results').append('
' + fields[id] + '

TBD
');
    }

    $('#run').click(function() {
	$('#run').attr('disabled', 'disabled');
	runTest(Ping, function() {
	    runTest(Download, function() {
		runTest(Upload, function() {
		    $('#run').attr('disabled', '');
		});
	    });
	});
    });
}

function getDuration() {
    return parseInt($('#duration').val(), 10);
}
<commit_msg>speedtest client: console stub fix<commit_after>if (window && !window.console) {
    var nop = function() { };
    window.console = { log: nop,
		       warn: nop,
		       error: nop };
}

var dummyData = '';
for(var i = 0; i < 16 * 1024; i++)
    dummyData += 'Z';

function human(i) {
    var unit = '';
    var units = ['K', 'M', 'G', 'T'];
    while(i > 1024) {
	i /= 1024;
	unit = units.shift();
    }
    return (Math.round(i * 100) / 100) + ' ' + unit;
}

function Ping(ws) {
    this.ws = ws;
    this.rttSum = 0;
    this.pings = 0;
    $('#ping').text('Opening...');
}

Ping.proto = 'ping';

Ping.prototype.onOpen = function() {
    this.ping();
    $('#ping').text('Open');
};

Ping.prototype.onMessage = function(msg) {
    if (msg !== 'pong') {
	console.warn('Not a pong, but: ' + msg);
	return;
    }

    var rtt = Date.now() - this.lastPing;
    this.pings++;
    this.rttSum += rtt;
    if (!this.minRtt ||
	rtt < this.minRtt)
	this.minRtt = rtt;
    if (!this.maxRtt ||
	rtt > this.maxRtt)
	this.maxRtt = rtt;

    $('#ping').empty();
    $('#ping').append(this.pings + ' pongs, avg: ' +
		    Math.round(this.rttSum / this.pings) +
		    ' ms
min: ' + this.minRtt +
		    ' ms, max: ' + this.maxRtt + 'ms');

    if (!this.done)
	this.ping();  // next
    else
	this.ws.close();
};

Ping.prototype.ping = function() {
    this.ws.send('ping');

    this.lastPing = Date.now();
};

Ping.prototype.onDone = function() {
    // Don't close immediately but wait for the last pong
    this.done = true;
};

function Download(ws) {
    this.ws = ws;
    $('#download').text('Opening...');
}

Download.proto = 'download';

Download.prototype.onOpen = function() {
    this.ws.send(getDuration());
    $('#download').text('Open');
};

Download.prototype.onMessage = function(msg) {
    var that = this;

    if (!this.startTime) {
	this.startTime = Date.now();
	this.lastMessage = this.startTime + 1;
	this.bytesRecvd = 0;
    } else {
	this.lastMessage = Date.now();
	this.bytesRecvd += msg.length;

	// Schedule (complex) update:
	if (!this.textUpdate) {
	    this.textUpdate = setTimeout(function() {
		delete that.textUpdate;

		var elapsed = that.lastMessage - that.startTime;
		$('#download').empty();
		$('#download').append(human(that.bytesRecvd * 1000 / (elapsed)) + 'B/s
' +
				      human(that.bytesRecvd) + 'B in ' + elapsed +
				      ' ms');
	    }, 100);
	}
    }
};

Download.prototype.onDone = function() {
    this.ws.close();
};

function Upload(ws) {
    this.ws = ws;
    $('#upload').text('Opening...');
}

Upload.proto = 'upload';

Upload.prototype.onOpen = function() {
    var that = this;
    this.startTime = Date.now();
    this.lastMessage = this.startTime + 1;
    this.bytesSent = 0;
    this.interval = setInterval(function() {
	var sent = false;
	while(that.ws.bufferedAmount < 1) {
	    that.ws.send(dummyData);
	    that.lastMessage = Date.now();
	    that.bytesSent += dummyData.length;
	    sent = true;
	}
	if (sent && !that.textUpdate) {
	    that.textUpdate = setTimeout(function() {
		delete that.textUpdate;

		var elapsed = that.lastMessage - that.startTime;
		$('#upload').empty();
		$('#upload').append(human(that.bytesSent * 1000 / elapsed) + 'B/s
' +
				    human(that.bytesSent) + 'B in ' + elapsed +
				    ' ms');
	    }, 100);
	}
    }, 1);
};

Upload.prototype.onDone = function() {
    clearInterval(this.interval);
    this.ws.close();
};

function runTest(t, cb) {
    var url = 'ws://' + document.location.host + '/';
    var ws = new WebSocket(url, t.proto);
    var test = new t(ws);

    ws.onopen = function() {
	console.log('open');
	test.onOpen();
    };
    ws.onmessage = function(ev) {
	test.onMessage(ev.data);
    };
    ws.onclose = function() {
	console.log('close');
	cb();
    };
    ws.onerror = function(e) {
	console.error(e);
	test.onError(e);
    };

    setTimeout(function() {
	test.onDone();
    }, getDuration() * 1000);
}

if (!WebSocket) {
    $('#broken').text('Your browser does not support the WebSocket protocol :-(');
} else {
    $('#broken').remove();

    $('body').append('
<input type="submit" id="run" value="Run"> each test for <input id="duration" value="5" size="2"> seconds
');
    $('body').append('

');
    var fields = { ping: 'Roundtrip delay time',
		   download: 'Downstream bandwidth',
		   upload: 'Upstream bandwidth' };
    for(var id in fields) {
	$('#results').append('
' + fields[id] + '

TBD
');
    }

    $('#run').click(function() {
	$('#run').attr('disabled', 'disabled');
	runTest(Ping, function() {
	    runTest(Download, function() {
		runTest(Upload, function() {
		    $('#run').attr('disabled', '');
		});
	    });
	});
    });
}

function getDuration() {
    return parseInt($('#duration').val(), 10);
}
<|endoftext|>


<commit_before>/**
 * instant.js
 *
 * @license MIT
 * @author Timm Albers (http://timmalbers.de)
 * @version 1.0.5
 */
const instant = (function() {
	var model;
	var views = [];
	var renderJobs = [];
	var config = {
		compile: function(source) {
			return Handlebars.compile(source)
		},
		render: function(template, model) {
			return template(model);
		}
	};

	/**
	 * Initialize action listeners.
	 */
	(function() {
		$('body').on('input', function(e) {
			var $focusedElement = $(':focus'),
				focusedElementId = $focusedElement.attr('data-instant-id'),
				caretPosition = $focusedElement.caret(),
				$target = $(e.target),
				path = $target.attr('data-instant-attribute')
					.split('.'),
				newValue = $target.text() || $target.val();

			// Update model.
			var value = model;
			for(var i in path) {
				if(path.length-1 == i) {
					if(value) {
						if(value[path[i]].hasOwnProperty('text')) {
							value[path[i]].setProperty(newValue);
						} else if(value[path[i]] !== newValue) {
							value[path[i]] = newValue;
						}
					}
				} else {
					value = value[path[i]];

					if(Array.isArray(value)) {
						var index = 0;
						$target.parents('[data-container]').find('[data-bind-value], [data-bind-text]').each(function() {
							if($(this).is($target)) {
								value = value[index];
							}

							index++;
						});
					}
				}
			}

			$focusedElement = $('[data-instant-id=' + focusedElementId + ']');
			$focusedElement.focus().caret(caretPosition);
		});
	})();

	/**
	 * Attaches a property change listener recursivly to the given object.
	 *
	 * @param <Object> o
	 */
	var attachPropertyChangeListener = function(o) {
		// Walk through propertys.
		for(var property in o) {
			if(o.hasOwnProperty(property)) {
				// Recursion.
				if(typeof o[property] === 'object') {
					attachPropertyChangeListener(o[property]);
				} else {
					// Attach the listener.
					(function() {
						var value = o[property];
						Object.defineProperty(o, property, {
							get: function() {
								return value;
							},
							set: function(value_) {
								value = value_;
								render();
								return value;
							}
						});
					})();
				}
			}
		}
	};

	/**
	 * Inserts the library specific tags into the DOM.
	 *
	 * @param <String> source
	 * @return <String> result
	 */
	var insertInstantAttributes = function(source) {
		source = source.replace(/.[^<]*data-bind-.[^>]*>({{.*}})*/g, function(match, a, offset, original) {
			var path = [],
				previous = original.substr(0, offset);

			var oldPrevious = '';
			while(oldPrevious !== previous) {
				oldPrevious = previous;
				previous = previous.replace(/{{#.[^}]*}}[^}}]*{{\/.[^{{#]*}}/g, '');
			}

			var splits = previous.split('{{#');
			splits.shift();
			for(var i in splits) {
				var parts = splits[i].split('}}')[0].split(' ');
				path.push(parts[parts.length-1]);
			}

			var attributeName = match
					.replace(/.*\{\{/, '')
					.replace(/\}\}.*/, '');
			path.push(attributeName);

			return match
					.replace(/>/, ' data-instant-attribute="' + path.join('.') + '">')
					.replace(/>/, ' data-instant-id="' + ('' + Math.random()).replace(/0./, '') + '">');
		});

		return source;
	};

	/**
	 * Parse the DOM, search for views and initialize those.
	 */
	var updateViews = function() {
		views = [];

		$('script[type="text/template"]').each(function() {
			views.push({
				name: $(this).attr('data-view'),
				// TODO
				template: config.compile(insertInstantAttributes($(this).html()))
			});
		});
	};

	/**
	 * Renders the compiled templates.
	 *
	 * @param template (optional)
	 */
	var render = function(template) {
		if(renderJobs.length && model) {
			for(var i = 0; i < renderJobs.length; i++) {
				var template = renderJobs[i].template;

				if(typeof template === 'function') {
					renderJobs[i].toString = function() {
						return config.render(template, model);
					};
				}
			}

			renderJobs = [];
		}

		if(template) {
			if(model) {
				return config.render(template, model);
			} else {
				var renderJob = {
					template: template
				};
				renderJobs.push(renderJob);

				return renderJob;
			}
		} else {
			for(var i in views) {
				var html = config.render(views[i].template, model);

				$(':not(script)[data-view="' + views[i].name + '"]').each(function() {
					$(this).html(html);
				});
			}
		}
	};

	return {
		/**
		 * Sets and initializes the model. Can ben used once; or multiple times to overwrite the set model.
		 *
		 * @param <Object> model
		 * @return <Object> instant.js
		 */
		model: function(model_) {
			attachPropertyChangeListener(model_);
			model = model_;

			updateViews();
			render();

			// Return instant for method chaining.
			return instant;
		},

		/**
		 * Sets the compiler function.
		 *
		 * @param <Function> compiler
		 * @return <Object> instant.js
		 */
		compiler: function(compiler) {
			config.compile = compiler;

			// Return instant for method chaning.
			return instant;
		},

		/**
		 * Sets the renderer function.
		 *
		 * @param <Function> renderer
		 * @return <Object> instant.js
		 */
		renderer: function(renderer) {
			config.render = renderer;

			// Return instant for method chaning.
			return instant;
	    },

		/**
		 * Sets the document title.
		 *
		 * @param <String> title
		 * @return <String> title
		 */
		docTitle: function(title) {
			$(document).attr('title', title);

			return title;
		},

		/**
		 * Listens for changes of the current attribute.
		 *
		 * @param <String> att
		 * @param <Function> callback
		 * @return <String> att
		 */
		listen: function(att, callback_) {
			var o = {
				text: att,
				// TODO
				setProperty: function(value) {
					var result = callback_(value);
					if(result) value = result;
					this.text = value;
				},
				toString: function() {
					return this.text;
				}
			};

			// TODO
			(function() {
				var value = att;
				var callback = callback_;
				Object.defineProperty(o, 'text', {
					get: function() {
						return value;
					},
					set: function(value_) {
						console.log('rkwje');
						alert("kj");
						// value = value_;

						// callback();
						return value;
					}
				});
			})();

			return o;
		},

		// TODO
		template: function(viewName) {
			updateViews();
			var template = '';
			for(var i in views) {
				if(views[i].name === viewName) template = views[i].template;
			}

			return render(template);
		}
	};
})();<commit_msg>Fixed a bug with the propertyChangeListeners for arrays<commit_after>/**
 * instant.js
 *
 * @license MIT
 * @author Timm Albers (http://timmalbers.de)
 * @version 1.0.6
 */
const instant = (function() {
	var model;
	var views = [];
	var renderJobs = [];
	var config = {
		compile: function(source) {
			return Handlebars.compile(source)
		},
		render: function(template, model) {
			return template(model);
		}
	};

	/**
	 * Initialize action listeners.
	 */
	(function() {
		$('body').on('input', function(e) {
			var $focusedElement = $(':focus'),
				focusedElementId = $focusedElement.attr('data-instant-id'),
				caretPosition = $focusedElement.caret(),
				$target = $(e.target),
				path = $target.attr('data-instant-attribute')
					.split('.'),
				newValue = $target.text() || $target.val();

			// Update model.
			var value = model;
			for(var i in path) {
				if(path.length-1 == i) {
					if(value) {
						if(value[path[i]].hasOwnProperty('text')) {
							value[path[i]].setProperty(newValue);
						} else if(value[path[i]] !== newValue) {
							value[path[i]] = newValue;
						}
					}
				} else {
					value = value[path[i]];

					if(Array.isArray(value)) {
						var index = 0;
						$target.parents('[data-container]').find('[data-bind-value], [data-bind-text]').each(function() {
							if($(this).is($target)) {
								value = value[index];
							}

							index++;
						});
					}
				}
			}

			$focusedElement = $('[data-instant-id=' + focusedElementId + ']');
			$focusedElement.focus().caret(caretPosition);
		});
	})();

	/**
	 * Attaches a property change listener recursivly to the given object.
	 *
	 * @param <Object> o
	 */
	var attachPropertyChangeListener = function(o) {
		// Walk through propertys.
		for(var property in o) {
			if(o.hasOwnProperty(property)) {
				// Recursion.
				if(typeof o[property] === 'object') {
					attachPropertyChangeListener(o[property]);
				} else {
					// Attach the listener.
					(function() {
						var value = o[property];
						Object.defineProperty(o, property, {
							get: function() {
								return value;
							},
							set: function(value_) {
								value = value_;

								// Ensure that every attribute (i.e. new added array elements) has listeners attached.
								attachPropertyChangeListener(model);
								render();
								return value;
							}
						});
					})();
				}
			}
		}
	};

	/**
	 * Inserts the library specific tags into the DOM.
	 *
	 * @param <String> source
	 * @return <String> result
	 */
	var insertInstantAttributes = function(source) {
		source = source.replace(/.[^<]*data-bind-.[^>]*>({{.*}})*/g, function(match, a, offset, original) {
			var path = [],
				previous = original.substr(0, offset);

			var oldPrevious = '';
			while(oldPrevious !== previous) {
				oldPrevious = previous;
				previous = previous.replace(/{{#.[^}]*}}[^}}]*{{\/.[^{{#]*}}/g, '');
			}

			var splits = previous.split('{{#');
			splits.shift();
			for(var i in splits) {
				var parts = splits[i].split('}}')[0].split(' ');
				path.push(parts[parts.length-1]);
			}

			var attributeName = match
					.replace(/.*\{\{/, '')
					.replace(/\}\}.*/, '');
			path.push(attributeName);

			return match
					.replace(/>/, ' data-instant-attribute="' + path.join('.') + '">')
					.replace(/>/, ' data-instant-id="' + ('' + Math.random()).replace(/0./, '') + '">');
		});

		return source;
	};

	/**
	 * Parse the DOM, search for views and initialize those.
	 */
	var updateViews = function() {
		views = [];

		$('script[type="text/template"]').each(function() {
			views.push({
				name: $(this).attr('data-view'),
				// TODO
				template: config.compile(insertInstantAttributes($(this).html()))
			});
		});
	};

	/**
	 * Renders the compiled templates.
	 *
	 * @param template (optional)
	 */
	var render = function(template) {
		if(renderJobs.length && model) {
			for(var i = 0; i < renderJobs.length; i++) {
				var template = renderJobs[i].template;

				if(typeof template === 'function') {
					renderJobs[i].toString = function() {
						return config.render(template, model);
					};
				}
			}

			renderJobs = [];
		}

		if(template) {
			if(model) {
				return config.render(template, model);
			} else {
				var renderJob = {
					template: template
				};
				renderJobs.push(renderJob);

				return renderJob;
			}
		} else {
			for(var i in views) {
				var html = config.render(views[i].template, model);

				$(':not(script)[data-view="' + views[i].name + '"]').each(function() {
					$(this).html(html);
				});
			}
		}
	};

	return {
		/**
		 * Sets and initializes the model. Can ben used once; or multiple times to overwrite the set model.
		 *
		 * @param <Object> model
		 * @return <Object> instant.js
		 */
		model: function(model_) {
			attachPropertyChangeListener(model_);
			model = model_;

			updateViews();
			render();

			// Return instant for method chaining.
			return instant;
		},

		/**
		 * Sets the compiler function.
		 *
		 * @param <Function> compiler
		 * @return <Object> instant.js
		 */
		compiler: function(compiler) {
			config.compile = compiler;

			// Return instant for method chaning.
			return instant;
		},

		/**
		 * Sets the renderer function.
		 *
		 * @param <Function> renderer
		 * @return <Object> instant.js
		 */
		renderer: function(renderer) {
			config.render = renderer;

			// Return instant for method chaning.
			return instant;
	    },

		/**
		 * Sets the document title.
		 *
		 * @param <String> title
		 * @return <String> title
		 */
		docTitle: function(title) {
			$(document).attr('title', title);

			return title;
		},

		/**
		 * Listens for changes of the current attribute.
		 *
		 * @param <String> att
		 * @param <Function> callback
		 * @return <String> att
		 */
		listen: function(att, callback_) {
			var o = {
				text: att,
				// TODO
				setProperty: function(value) {
					var result = callback_(value);
					if(result) value = result;
					this.text = value;
				},
				toString: function() {
					return this.text;
				}
			};

			// TODO
			(function() {
				var value = att;
				var callback = callback_;
				Object.defineProperty(o, 'text', {
					get: function() {
						return value;
					},
					set: function(value_) {
						console.log('rkwje');
						alert("kj");
						// value = value_;

						// callback();
						return value;
					}
				});
			})();

			return o;
		},

		// TODO
		template: function(viewName) {
			updateViews();
			var template = '';
			for(var i in views) {
				if(views[i].name === viewName) template = views[i].template;
			}

			return render(template);
		}
	};
})();<|endoftext|>


<commit_before>(function (root, factory) {
    if (typeof define === 'function' && define.amd) define([], factory);
    else if (typeof exports === 'object') module.exports = factory();
    else root.Inverse = factory();
})(this, function () {
    'use strict';

    var Inverse = function() {
        this._boundCallbacks = {};
        this._singletonCallbacks = {};
        this._instantiatedSingletons = {};
        this._registeredObjects = {};
    };

    var proto = Inverse.prototype;

    proto.make = function(name) {
        if (this._registeredObjects.hasOwnProperty(name)) {
            return this._registeredObjects[name];
        }

        if (this._singletonCallbacks.hasOwnProperty(name)) {
            if (!this._instantiatedSingletons.hasOwnProperty(name)) {
                this._instantiatedSingletons[name] = this._singletonCallbacks[name](this);
            }

            return this._instantiatedSingletons[name];
        }

        if (this._boundCallbacks.hasOwnProperty(name)) {
            return this._boundCallbacks[name](this);
        }

        return null;
    };

    proto.bind = function(name, callback) {
        this._boundCallbacks[name] = callback;
    };

    proto.singleton = function(name, callback) {
        this._singletonCallbacks[name] = callback;
    };

    proto.register = function(name, object) {
        this._registeredObjects[name] = object;
    };

    return Inverse;
});
<commit_msg>Pass arguments in to the constructors.<commit_after>(function (root, factory) {
    if (typeof define === 'function' && define.amd) define([], factory);
    else if (typeof exports === 'object') module.exports = factory();
    else root.Inverse = factory();
})(this, function () {
    'use strict';

    var Inverse = function() {
        this._boundCallbacks = {};
        this._singletonCallbacks = {};
        this._instantiatedSingletons = {};
        this._registeredObjects = {};
    };

    Inverse.prototype.make = function(name) {
        if (this._registeredObjects.hasOwnProperty(name)) {
            return this._registeredObjects[name];
        }

        var args = Array.prototype.slice.call(arguments, 1);

        args.unshift(this);

        if (this._singletonCallbacks.hasOwnProperty(name)) {
            if (!this._instantiatedSingletons.hasOwnProperty(name)) {
                this._instantiatedSingletons[name] = this._singletonCallbacks[name].apply(this, args);
            }

            return this._instantiatedSingletons[name];
        }

        if (this._boundCallbacks.hasOwnProperty(name)) {
            return this._boundCallbacks[name].apply(this, args);
        }

        return null;
    };

    Inverse.prototype.bind = function(name, callback) {
        this._boundCallbacks[name] = callback;
    };

    Inverse.prototype.singleton = function(name, callback) {
        this._singletonCallbacks[name] = callback;
    };

    Inverse.prototype.register = function(name, object) {
        this._registeredObjects[name] = object;
    };

    return Inverse;
});
<|endoftext|>


<commit_before>"use strict";

var C = require("../const");
var util = require("../util");
var neume = require("../namespace");
var toSeconds = require("../util/toSeconds");

var INIT = 0;
var START = 1;
var MAX_RENDERING_SEC = C.MAX_RENDERING_SEC;

var schedId = 1;

function NeuContext(destination, duration) {
  this.context = this;
  this.destination = destination;
  this.audioContext = destination.context;
  this.sampleRate = this.audioContext.sampleRate;
  this.listener = this.audioContext.listener;
  this.analyser = this.audioContext.createAnalyser();

  this._bpm = 120;
  this._scriptProcessor = null;
  this._audioBuses = [];
  this._processBufSize = C.PROCESS_BUF_SIZE;
  this._inlet = null;

  this.connect(this.analyser, this.destination);

  Object.defineProperties(this, {
    currentTime: {
      get: function() {
        return this._currentTime || this.audioContext.currentTime;
      },
      enumerable: true
    },
    bpm: {
      get: function() {
        return this._bpm;
      },
      set: function(value) {
        this._bpm = Math.max(1e-6, util.finite(value));
      },
      enumerable: true
    },
  });

  this._duration = duration;
  this.reset();
}
NeuContext.$$name = "NeuContext";

Object.keys(neume.webaudio.AudioContext.prototype).forEach(function(key) {
  var desc = Object.getOwnPropertyDescriptor(neume.webaudio.AudioContext.prototype, key);

  /* istanbul ignore next */
  if (typeof desc.value !== "function") {
    return;
  }

  var method = neume.webaudio.AudioContext.prototype[key];

  NeuContext.prototype[key] = function() {
    return method.apply(this.audioContext, arguments);
  };
});

NeuContext.prototype.getAudioBus = function(index) {
  index = util.clip(util.int(util.defaults(index, 0)), 0, C.AUDIO_BUS_CHANNELS);
  if (!this._audioBuses[index]) {
    this._audioBuses[index] = new neume.AudioBus(this);
  }
  return this._audioBuses[index];
};

NeuContext.prototype.reset = function() {
  if (this._inlet) {
    this._inlet.disconnect();
  }

  this._audioBuses.splice(0).forEach(function(bus) {
    bus.toAudioNode().disconnect();
  }, this);

  this._inlet = this._audioBuses[0] = this.getAudioBus(0);
  this.connect(this._inlet, this.analyser);

  this.disconnect(this._scriptProcessor);

  this._events = [];
  this._nextTicks = [];
  this._state = INIT;
  this._currentTime = 0;
  this._scriptProcessor = null;

  return this;
};

NeuContext.prototype.start = function() {
  if (this._state === INIT) {
    this._state = START;
    if (this.audioContext instanceof neume.webaudio.OfflineAudioContext) {
      startRendering.call(this);
    } else {
      startAudioTimer.call(this);
    }
  }
  return this;
};

function startRendering() {
  this._currentTimeIncr = util.clip(util.finite(this._duration), 0, MAX_RENDERING_SEC);
  onaudioprocess.call(this, { playbackTime: 0 });
}

function startAudioTimer() {
  var context = this.audioContext;
  var scriptProcessor = context.createScriptProcessor(this._processBufSize, 1, 1);
  var bufferSource = context.createBufferSource();

  this._currentTimeIncr = this._processBufSize / context.sampleRate;
  this._scriptProcessor = scriptProcessor;
  scriptProcessor.onaudioprocess = onaudioprocess.bind(this);

  // this is needed for iOS Safari
  bufferSource.start(0);
  this.connect(bufferSource, scriptProcessor);

  this.connect(scriptProcessor, context.destination);
}

NeuContext.prototype.stop = function() {
  return this;
};

NeuContext.prototype.sched = function(time, callback, ctx) {
  time = util.finite(time);

  if (typeof callback !== "function") {
    return 0;
  }

  var events = this._events;
  var event = {
    id: schedId++,
    time: time,
    callback: callback,
    context: ctx || this
  };

  if (events.length === 0 || events[events.length - 1].time <= time) {
    events.push(event);
  } else {
    for (var i = 0, imax = events.length; i < imax; i++) {
      if (time < events[i].time) {
        events.splice(i, 0, event);
        break;
      }
    }
  }

  return event.id;
};

NeuContext.prototype.unsched = function(id) {
  id = util.finite(id);

  if (id !== 0) {
    var events = this._events;
    for (var i = 0, imax = events.length; i < imax; i++) {
      if (id === events[i].id) {
        events.splice(i, 1);
        break;
      }
    }
  }

  return id;
};

NeuContext.prototype.nextTick = function(callback, ctx) {
  this._nextTicks.push(callback.bind(ctx || this));
  return this;
};

NeuContext.prototype.toAudioNode = function(obj) {
  if (obj && obj.toAudioNode) {
    obj = obj.toAudioNode();
  } else if (typeof obj === "number") {
    obj = new neume.DC(this, obj).toAudioNode();
  }
  if (!(obj instanceof neume.webaudio.AudioNode)) {
    obj = null;
  }
  return obj;
};

NeuContext.prototype.toAudioBuffer = function(obj) {
  if (obj && obj.toAudioBuffer) {
    return obj.toAudioBuffer();
  }
  if (!(obj instanceof neume.webaudio.AudioBuffer)) {
    obj = null;
  }
  return obj;
};

NeuContext.prototype.connect = function(from, to) {
  if (to) {
    if (Array.isArray(from)) {
      if (from.length) {
        new neume.Sum(this, from).connect(to);
      }
    } else if (from instanceof neume.Component || from instanceof neume.UGen) {
      from.connect(to);
    } else if (to instanceof neume.webaudio.AudioParam) {
      if (typeof from === "number") {
        to.value = util.finite(from);
      } else {
        from = this.toAudioNode(from);
        if (from) {
          from.connect(to);
        }
      }
    } else if (to instanceof neume.webaudio.AudioNode) {
      from = this.toAudioNode(from);
      if (from) {
        from.connect(to);
      }
    } else if (to instanceof neume.AudioBus) {
      this.connect(from, to.toAudioNode());
    }
    if (to.onconnected) {
      to.onconnected(from);
    }
  }
  return this;
};

NeuContext.prototype.disconnect = function(node) {
  if (node) {
    if (typeof node.disconnect === "function") {
      node.disconnect();
      if (node.$$outputs) {
        node.$$outputs.forEach(function(to) {
          return to.ondisconnected && to.ondisconnected(node);
        });
      }
    } else if (Array.isArray(node)) {
      node.forEach(function(node) {
        this.disconnect(node);
      }, this);
    }
  }
  return this;
};

NeuContext.prototype.toSeconds = function(value) {
  return toSeconds(value, this._bpm, this.sampleRate, this.currentTime);
};

function onaudioprocess(e) {
  // Safari 7.0.6 does not support e.playbackTime
  var currentTime = e.playbackTime || /* istanbul ignore next */ this.audioContext.currentTime;
  var nextCurrentTime = currentTime + this._currentTimeIncr;
  var events = this._events;

  this._currentTime = currentTime;

  this._nextTicks.splice(0).forEach(function(callback) {
    callback(currentTime);
  });

  while (events.length && events[0].time <= nextCurrentTime) {
    var event = events.shift();

    this._currentTime = Math.max(this._currentTime, event.time);

    event.callback.call(event.context, event.time);
  }
}

module.exports = neume.Context = NeuContext;
<commit_msg>clean NeuContext#reset()<commit_after>"use strict";

var C = require("../const");
var util = require("../util");
var neume = require("../namespace");
var toSeconds = require("../util/toSeconds");

var INIT = 0;
var START = 1;
var MAX_RENDERING_SEC = C.MAX_RENDERING_SEC;

var schedId = 1;

function NeuContext(destination, duration) {
  this.context = this;
  this.destination = destination;
  this.audioContext = destination.context;
  this.sampleRate = this.audioContext.sampleRate;
  this.listener = this.audioContext.listener;
  this.analyser = this.audioContext.createAnalyser();

  this._bpm = 120;
  this._scriptProcessor = null;
  this._audioBuses = [];
  this._processBufSize = C.PROCESS_BUF_SIZE;

  this.connect(this.analyser, this.destination);

  Object.defineProperties(this, {
    currentTime: {
      get: function() {
        return this._currentTime || this.audioContext.currentTime;
      },
      enumerable: true
    },
    bpm: {
      get: function() {
        return this._bpm;
      },
      set: function(value) {
        this._bpm = Math.max(1e-6, util.finite(value));
      },
      enumerable: true
    },
  });

  this._duration = duration;
  this.reset();
}
NeuContext.$$name = "NeuContext";

Object.keys(neume.webaudio.AudioContext.prototype).forEach(function(key) {
  var desc = Object.getOwnPropertyDescriptor(neume.webaudio.AudioContext.prototype, key);

  /* istanbul ignore next */
  if (typeof desc.value !== "function") {
    return;
  }

  var method = neume.webaudio.AudioContext.prototype[key];

  NeuContext.prototype[key] = function() {
    return method.apply(this.audioContext, arguments);
  };
});

NeuContext.prototype.getAudioBus = function(index) {
  index = util.clip(util.int(util.defaults(index, 0)), 0, C.AUDIO_BUS_CHANNELS);
  if (!this._audioBuses[index]) {
    this._audioBuses[index] = new neume.AudioBus(this);
  }
  return this._audioBuses[index];
};

NeuContext.prototype.reset = function() {
  this._audioBuses.splice(0).forEach(function(bus) {
    bus.toAudioNode().disconnect();
  }, this);
  this.disconnect(this._scriptProcessor);

  this._events = [];
  this._nextTicks = [];
  this._state = INIT;
  this._currentTime = 0;
  this._scriptProcessor = null;

  return this;
};

NeuContext.prototype.start = function() {
  if (this._state === INIT) {
    this._state = START;
    this.connect(this.getAudioBus(0).outlet, this.analyser);
    if (this.audioContext instanceof neume.webaudio.OfflineAudioContext) {
      startRendering.call(this);
    } else {
      startAudioTimer.call(this);
    }
  }
  return this;
};

function startRendering() {
  this._currentTimeIncr = util.clip(util.finite(this._duration), 0, MAX_RENDERING_SEC);
  onaudioprocess.call(this, { playbackTime: 0 });
}

function startAudioTimer() {
  var context = this.audioContext;
  var scriptProcessor = context.createScriptProcessor(this._processBufSize, 1, 1);
  var bufferSource = context.createBufferSource();

  this._currentTimeIncr = this._processBufSize / context.sampleRate;
  this._scriptProcessor = scriptProcessor;
  scriptProcessor.onaudioprocess = onaudioprocess.bind(this);

  // this is needed for iOS Safari
  bufferSource.start(0);
  this.connect(bufferSource, scriptProcessor);

  this.connect(scriptProcessor, context.destination);
}

NeuContext.prototype.stop = function() {
  return this;
};

NeuContext.prototype.sched = function(time, callback, ctx) {
  time = util.finite(time);

  if (typeof callback !== "function") {
    return 0;
  }

  var events = this._events;
  var event = {
    id: schedId++,
    time: time,
    callback: callback,
    context: ctx || this
  };

  if (events.length === 0 || events[events.length - 1].time <= time) {
    events.push(event);
  } else {
    for (var i = 0, imax = events.length; i < imax; i++) {
      if (time < events[i].time) {
        events.splice(i, 0, event);
        break;
      }
    }
  }

  return event.id;
};

NeuContext.prototype.unsched = function(id) {
  id = util.finite(id);

  if (id !== 0) {
    var events = this._events;
    for (var i = 0, imax = events.length; i < imax; i++) {
      if (id === events[i].id) {
        events.splice(i, 1);
        break;
      }
    }
  }

  return id;
};

NeuContext.prototype.nextTick = function(callback, ctx) {
  this._nextTicks.push(callback.bind(ctx || this));
  return this;
};

NeuContext.prototype.toAudioNode = function(obj) {
  if (obj && obj.toAudioNode) {
    obj = obj.toAudioNode();
  } else if (typeof obj === "number") {
    obj = new neume.DC(this, obj).toAudioNode();
  }
  if (!(obj instanceof neume.webaudio.AudioNode)) {
    obj = null;
  }
  return obj;
};

NeuContext.prototype.toAudioBuffer = function(obj) {
  if (obj && obj.toAudioBuffer) {
    return obj.toAudioBuffer();
  }
  if (!(obj instanceof neume.webaudio.AudioBuffer)) {
    obj = null;
  }
  return obj;
};

NeuContext.prototype.connect = function(from, to) {
  if (to) {
    if (Array.isArray(from)) {
      if (from.length) {
        new neume.Sum(this, from).connect(to);
      }
    } else if (from instanceof neume.Component || from instanceof neume.UGen) {
      from.connect(to);
    } else if (to instanceof neume.webaudio.AudioParam) {
      if (typeof from === "number") {
        to.value = util.finite(from);
      } else {
        from = this.toAudioNode(from);
        if (from) {
          from.connect(to);
        }
      }
    } else if (to instanceof neume.webaudio.AudioNode) {
      from = this.toAudioNode(from);
      if (from) {
        from.connect(to);
      }
    } else if (to instanceof neume.AudioBus) {
      this.connect(from, to.toAudioNode());
    }
    if (to.onconnected) {
      to.onconnected(from);
    }
  }
  return this;
};

NeuContext.prototype.disconnect = function(node) {
  if (node) {
    if (typeof node.disconnect === "function") {
      node.disconnect();
      if (node.$$outputs) {
        node.$$outputs.forEach(function(to) {
          return to.ondisconnected && to.ondisconnected(node);
        });
      }
    } else if (Array.isArray(node)) {
      node.forEach(function(node) {
        this.disconnect(node);
      }, this);
    }
  }
  return this;
};

NeuContext.prototype.toSeconds = function(value) {
  return toSeconds(value, this._bpm, this.sampleRate, this.currentTime);
};

function onaudioprocess(e) {
  // Safari 7.0.6 does not support e.playbackTime
  var currentTime = e.playbackTime || /* istanbul ignore next */ this.audioContext.currentTime;
  var nextCurrentTime = currentTime + this._currentTimeIncr;
  var events = this._events;

  this._currentTime = currentTime;

  this._nextTicks.splice(0).forEach(function(callback) {
    callback(currentTime);
  });

  while (events.length && events[0].time <= nextCurrentTime) {
    var event = events.shift();

    this._currentTime = Math.max(this._currentTime, event.time);

    event.callback.call(event.context, event.time);
  }
}

module.exports = neume.Context = NeuContext;
<|endoftext|>


<commit_before>/**
 * Adjust the styles of selected elements or return the requested value.
 * @method css
 * @memberof Grindstone
 * @param {object|string} styles object with style properties or single style in a string
 * @param {string} value new style value to apply
 * @returns {object|string} Collection or style value
 * @example
 * $('#selector').style('display');
 * $('#selector').style('display', 'block');
 * $('#selector').style({ display: 'block', color: 'red' });
 */

const css = function(styles, value) {
	let returnedStyle, returnStyle;
	this.each(function() {
		if (typeof styles === 'object') {
			Object.assign(this.style, styles);
		} else if (typeof styles === 'string' && (value === undefined || value === null)) {
			returnedStyle = this.style[styles];
			returnStyle = true;
		} else if (typeof styles === 'string') {
			this.style[styles] = value;
		}
	});
	return returnStyle ? returnedStyle : this;
};

$.fn.css = css;

// @if !NODE_ENV='production'
module.exports = css;
// @endif
<commit_msg>Fool proofing<commit_after>/**
 * Adjust the styles of selected elements or return the requested value.
 * @method css
 * @memberof Grindstone
 * @param {object|string} styles object with style properties or single style in a string
 * @param {string} value new style value to apply
 * @returns {object|string} Collection or style value
 * @example
 * $('#selector').style('display');
 * $('#selector').style('display', 'block');
 * $('#selector').style({ display: 'block', color: 'red' });
 */

const css = function(styles, value) {
	let returnedStyle, returnStyle;
	this.each(function() {
		if (typeof styles === 'object' && !Array.isArray(styles)) {
			Object.assign(this.style, styles);
		} else if (typeof styles === 'string' && (value === undefined || value === null)) {
			returnedStyle = this.style[styles];
			returnStyle = true;
		} else if (typeof styles === 'string') {
			this.style[styles] = value;
		}
	});
	return returnStyle ? returnedStyle : this;
};

$.fn.css = css;

// @if !NODE_ENV='production'
module.exports = css;
// @endif
<|endoftext|>


<commit_before>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import MatrixClientPeg from "../../MatrixClientPeg";
import isIp from "is-ip";
import utils from 'matrix-js-sdk/lib/utils';
import SpecPermalinkConstructor, {baseUrl as matrixtoBaseUrl} from "./SpecPermalinkConstructor";
import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
import RiotPermalinkConstructor from "./RiotPermalinkConstructor";

const SdkConfig = require("../../SdkConfig");

// The maximum number of servers to pick when working out which servers
// to add to permalinks. The servers are appended as ?via=example.org
const MAX_SERVER_CANDIDATES = 3;

// Permalinks can have servers appended to them so that the user
// receiving them can have a fighting chance at joining the room.
// These servers are called "candidates" at this point because
// it is unclear whether they are going to be useful to actually
// join in the future.
//
// We pick 3 servers based on the following criteria:
//
//   Server 1: The highest power level user in the room, provided
//   they are at least PL 50. We don't calculate "what is a moderator"
//   here because it is less relevant for the vast majority of rooms.
//   We also want to ensure that we get an admin or high-ranking mod
//   as they are less likely to leave the room. If no user happens
//   to meet this criteria, we'll pick the most popular server in the
//   room.
//
//   Server 2: The next most popular server in the room (in user
//   distribution). This cannot be the same as Server 1. If no other
//   servers are available then we'll only return Server 1.
//
//   Server 3: The next most popular server by user distribution. This
//   has the same rules as Server 2, with the added exception that it
//   must be unique from Server 1 and 2.

// Rationale for popular servers: It's hard to get rid of people when
// they keep flocking in from a particular server. Sure, the server could
// be ACL'd in the future or for some reason be evicted from the room
// however an event like that is unlikely the larger the room gets. If
// the server is ACL'd at the time of generating the link however, we
// shouldn't pick them. We also don't pick IP addresses.

// Note: we don't pick the server the room was created on because the
// homeserver should already be using that server as a last ditch attempt
// and there's less of a guarantee that the server is a resident server.
// Instead, we actively figure out which servers are likely to be residents
// in the future and try to use those.

// Note: Users receiving permalinks that happen to have all 3 potential
// servers fail them (in terms of joining) are somewhat expected to hunt
// down the person who gave them the link to ask for a participating server.
// The receiving user can then manually append the known-good server to
// the list and magically have the link work.

export class RoomPermalinkCreator {
    // We support being given a roomId as a fallback in the event the `room` object
    // doesn't exist or is not healthy for us to rely on. For example, loading a
    // permalink to a room which the MatrixClient doesn't know about.
    constructor(room, roomId = null) {
        this._room = room;
        this._roomId = room ? room.roomId : roomId;
        this._highestPlUserId = null;
        this._populationMap = null;
        this._bannedHostsRegexps = null;
        this._allowedHostsRegexps = null;
        this._serverCandidates = null;
        this._started = false;

        if (!this._roomId) {
            throw new Error("Failed to resolve a roomId for the permalink creator to use");
        }

        this.onMembership = this.onMembership.bind(this);
        this.onRoomState = this.onRoomState.bind(this);
    }

    load() {
        if (!this._room || !this._room.currentState) {
            // Under rare and unknown circumstances it is possible to have a room with no
            // currentState, at least potentially at the early stages of joining a room.
            // To avoid breaking everything, we'll just warn rather than throw as well as
            // not bother updating the various aspects of the share link.
            console.warn("Tried to load a permalink creator with no room state");
            return;
        }
        this._updateAllowedServers();
        this._updateHighestPlUser();
        this._updatePopulationMap();
        this._updateServerCandidates();
    }

    start() {
        this.load();
        this._room.on("RoomMember.membership", this.onMembership);
        this._room.on("RoomState.events", this.onRoomState);
        this._started = true;
    }

    stop() {
        this._room.removeListener("RoomMember.membership", this.onMembership);
        this._room.removeListener("RoomState.events", this.onRoomState);
        this._started = false;
    }

    isStarted() {
        return this._started;
    }

    forEvent(eventId) {
        return getPermalinkConstructor().forEvent(this._roomId, eventId, this._serverCandidates);
    }

    forRoom() {
        return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates);
    }

    onRoomState(event) {
        switch (event.getType()) {
            case "m.room.server_acl":
                this._updateAllowedServers();
                this._updateHighestPlUser();
                this._updatePopulationMap();
                this._updateServerCandidates();
                return;
            case "m.room.power_levels":
                this._updateHighestPlUser();
                this._updateServerCandidates();
                return;
        }
    }

    onMembership(evt, member, oldMembership) {
        const userId = member.userId;
        const membership = member.membership;
        const serverName = getServerName(userId);
        const hasJoined = oldMembership !== "join" && membership === "join";
        const hasLeft = oldMembership === "join" && membership !== "join";

        if (hasLeft) {
            this._populationMap[serverName]--;
        } else if (hasJoined) {
            this._populationMap[serverName]++;
        }

        this._updateHighestPlUser();
        this._updateServerCandidates();
    }

    _updateHighestPlUser() {
        const plEvent = this._room.currentState.getStateEvents("m.room.power_levels", "");
        if (plEvent) {
            const content = plEvent.getContent();
            if (content) {
                const users = content.users;
                if (users) {
                    const entries = Object.entries(users);
                    const allowedEntries = entries.filter(([userId]) => {
                        const member = this._room.getMember(userId);
                        if (!member || member.membership !== "join") {
                            return false;
                        }
                        const serverName = getServerName(userId);
                        return !isHostnameIpAddress(serverName) &&
                            !isHostInRegex(serverName, this._bannedHostsRegexps) &&
                            isHostInRegex(serverName, this._allowedHostsRegexps);
                    });
                    const maxEntry = allowedEntries.reduce((max, entry) => {
                        return (entry[1] > max[1]) ? entry : max;
                    }, [null, 0]);
                    const [userId, powerLevel] = maxEntry;
                    // object wasn't empty, and max entry wasn't a demotion from the default
                    if (userId !== null && powerLevel >= 50) {
                        this._highestPlUserId = userId;
                        return;
                    }
                }
            }
        }
        this._highestPlUserId = null;
    }

    _updateAllowedServers() {
        const bannedHostsRegexps = [];
        let allowedHostsRegexps = [new RegExp(".*")]; // default allow everyone
        if (this._room.currentState) {
            const aclEvent = this._room.currentState.getStateEvents("m.room.server_acl", "");
            if (aclEvent && aclEvent.getContent()) {
                const getRegex = (hostname) => new RegExp("^" + utils.globToRegexp(hostname, false) + "$");

                const denied = aclEvent.getContent().deny || [];
                denied.forEach(h => bannedHostsRegexps.push(getRegex(h)));

                const allowed = aclEvent.getContent().allow || [];
                allowedHostsRegexps = []; // we don't want to use the default rule here
                allowed.forEach(h => allowedHostsRegexps.push(getRegex(h)));
            }
        }
        this._bannedHostsRegexps = bannedHostsRegexps;
        this._allowedHostsRegexps = allowedHostsRegexps;
    }

    _updatePopulationMap() {
        const populationMap: { [server: string]: number } = {};
        for (const member of this._room.getJoinedMembers()) {
            const serverName = getServerName(member.userId);
            if (!populationMap[serverName]) {
                populationMap[serverName] = 0;
            }
            populationMap[serverName]++;
        }
        this._populationMap = populationMap;
    }

    _updateServerCandidates() {
        let candidates = [];
        if (this._highestPlUserId) {
            candidates.push(getServerName(this._highestPlUserId));
        }

        const serversByPopulation = Object.keys(this._populationMap)
            .sort((a, b) => this._populationMap[b] - this._populationMap[a])
            .filter(a => {
                return !candidates.includes(a) &&
                    !isHostnameIpAddress(a) &&
                    !isHostInRegex(a, this._bannedHostsRegexps) &&
                    isHostInRegex(a, this._allowedHostsRegexps);
            });

        const remainingServers = serversByPopulation.slice(0, MAX_SERVER_CANDIDATES - candidates.length);
        candidates = candidates.concat(remainingServers);

        this._serverCandidates = candidates;
    }
}

export function makeUserPermalink(userId) {
    return getPermalinkConstructor().forUser(userId);
}

export function makeRoomPermalink(roomId) {
    if (!roomId) {
        throw new Error("can't permalink a falsey roomId");
    }

    // If the roomId isn't actually a room ID, don't try to list the servers.
    // Aliases are already routable, and don't need extra information.
    if (roomId[0] !== '!') return getPermalinkConstructor().forRoom(roomId, []);

    const client = MatrixClientPeg.get();
    const room = client.getRoom(roomId);
    if (!room) {
        return getPermalinkConstructor().forRoom(roomId, []);
    }
    const permalinkCreator = new RoomPermalinkCreator(room);
    permalinkCreator.load();
    return permalinkCreator.forRoom();
}

export function makeGroupPermalink(groupId) {
    return getPermalinkConstructor().forGroup(groupId);
}

export function isPermalinkHost(host: string): boolean {
    return getPermalinkConstructor().isPermalinkHost(host);
}

function getPermalinkConstructor(): PermalinkConstructor {
    if (SdkConfig.get()['permalinkPrefix'] && SdkConfig.get()['permalinkPrefix'] !== matrixtoBaseUrl) {
        return new RiotPermalinkConstructor(SdkConfig.get()['permalinkPrefix']);
    }

    return new SpecPermalinkConstructor();
}

export function parsePermalink(fullUrl: string): PermalinkParts {
    const riotPrefix = SdkConfig.get()['permalinkPrefix'];
    if (fullUrl.startsWith(matrixtoBaseUrl)) {
        return new SpecPermalinkConstructor().parsePermalink(fullUrl);
    } else if (riotPrefix && fullUrl.startsWith(riotPrefix)) {
        return new RiotPermalinkConstructor(riotPrefix).parsePermalink(fullUrl);
    }

    return null; // not a permalink we can handle
}

function getServerName(userId) {
    return userId.split(":").splice(1).join(":");
}

function getHostnameFromMatrixDomain(domain) {
    if (!domain) return null;
    return new URL(`https://${domain}`).hostname;
}

function isHostInRegex(hostname, regexps) {
    hostname = getHostnameFromMatrixDomain(hostname);
    if (!hostname) return true; // assumed
    if (regexps.length > 0 && !regexps[0].test) throw new Error(regexps[0]);

    return regexps.filter(h => h.test(hostname)).length > 0;
}

function isHostnameIpAddress(hostname) {
    hostname = getHostnameFromMatrixDomain(hostname);
    if (!hostname) return false;

    // is-ip doesn't want IPv6 addresses surrounded by brackets, so
    // take them off.
    if (hostname.startsWith("[") && hostname.endsWith("]")) {
        hostname = hostname.substring(1, hostname.length - 1);
    }

    return isIp(hostname);
}
<commit_msg>Always check if the permalink is a spec permalink<commit_after>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import MatrixClientPeg from "../../MatrixClientPeg";
import isIp from "is-ip";
import utils from 'matrix-js-sdk/lib/utils';
import SpecPermalinkConstructor, {baseUrl as matrixtoBaseUrl} from "./SpecPermalinkConstructor";
import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
import RiotPermalinkConstructor from "./RiotPermalinkConstructor";

const SdkConfig = require("../../SdkConfig");

// The maximum number of servers to pick when working out which servers
// to add to permalinks. The servers are appended as ?via=example.org
const MAX_SERVER_CANDIDATES = 3;

// Permalinks can have servers appended to them so that the user
// receiving them can have a fighting chance at joining the room.
// These servers are called "candidates" at this point because
// it is unclear whether they are going to be useful to actually
// join in the future.
//
// We pick 3 servers based on the following criteria:
//
//   Server 1: The highest power level user in the room, provided
//   they are at least PL 50. We don't calculate "what is a moderator"
//   here because it is less relevant for the vast majority of rooms.
//   We also want to ensure that we get an admin or high-ranking mod
//   as they are less likely to leave the room. If no user happens
//   to meet this criteria, we'll pick the most popular server in the
//   room.
//
//   Server 2: The next most popular server in the room (in user
//   distribution). This cannot be the same as Server 1. If no other
//   servers are available then we'll only return Server 1.
//
//   Server 3: The next most popular server by user distribution. This
//   has the same rules as Server 2, with the added exception that it
//   must be unique from Server 1 and 2.

// Rationale for popular servers: It's hard to get rid of people when
// they keep flocking in from a particular server. Sure, the server could
// be ACL'd in the future or for some reason be evicted from the room
// however an event like that is unlikely the larger the room gets. If
// the server is ACL'd at the time of generating the link however, we
// shouldn't pick them. We also don't pick IP addresses.

// Note: we don't pick the server the room was created on because the
// homeserver should already be using that server as a last ditch attempt
// and there's less of a guarantee that the server is a resident server.
// Instead, we actively figure out which servers are likely to be residents
// in the future and try to use those.

// Note: Users receiving permalinks that happen to have all 3 potential
// servers fail them (in terms of joining) are somewhat expected to hunt
// down the person who gave them the link to ask for a participating server.
// The receiving user can then manually append the known-good server to
// the list and magically have the link work.

export class RoomPermalinkCreator {
    // We support being given a roomId as a fallback in the event the `room` object
    // doesn't exist or is not healthy for us to rely on. For example, loading a
    // permalink to a room which the MatrixClient doesn't know about.
    constructor(room, roomId = null) {
        this._room = room;
        this._roomId = room ? room.roomId : roomId;
        this._highestPlUserId = null;
        this._populationMap = null;
        this._bannedHostsRegexps = null;
        this._allowedHostsRegexps = null;
        this._serverCandidates = null;
        this._started = false;

        if (!this._roomId) {
            throw new Error("Failed to resolve a roomId for the permalink creator to use");
        }

        this.onMembership = this.onMembership.bind(this);
        this.onRoomState = this.onRoomState.bind(this);
    }

    load() {
        if (!this._room || !this._room.currentState) {
            // Under rare and unknown circumstances it is possible to have a room with no
            // currentState, at least potentially at the early stages of joining a room.
            // To avoid breaking everything, we'll just warn rather than throw as well as
            // not bother updating the various aspects of the share link.
            console.warn("Tried to load a permalink creator with no room state");
            return;
        }
        this._updateAllowedServers();
        this._updateHighestPlUser();
        this._updatePopulationMap();
        this._updateServerCandidates();
    }

    start() {
        this.load();
        this._room.on("RoomMember.membership", this.onMembership);
        this._room.on("RoomState.events", this.onRoomState);
        this._started = true;
    }

    stop() {
        this._room.removeListener("RoomMember.membership", this.onMembership);
        this._room.removeListener("RoomState.events", this.onRoomState);
        this._started = false;
    }

    isStarted() {
        return this._started;
    }

    forEvent(eventId) {
        return getPermalinkConstructor().forEvent(this._roomId, eventId, this._serverCandidates);
    }

    forRoom() {
        return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates);
    }

    onRoomState(event) {
        switch (event.getType()) {
            case "m.room.server_acl":
                this._updateAllowedServers();
                this._updateHighestPlUser();
                this._updatePopulationMap();
                this._updateServerCandidates();
                return;
            case "m.room.power_levels":
                this._updateHighestPlUser();
                this._updateServerCandidates();
                return;
        }
    }

    onMembership(evt, member, oldMembership) {
        const userId = member.userId;
        const membership = member.membership;
        const serverName = getServerName(userId);
        const hasJoined = oldMembership !== "join" && membership === "join";
        const hasLeft = oldMembership === "join" && membership !== "join";

        if (hasLeft) {
            this._populationMap[serverName]--;
        } else if (hasJoined) {
            this._populationMap[serverName]++;
        }

        this._updateHighestPlUser();
        this._updateServerCandidates();
    }

    _updateHighestPlUser() {
        const plEvent = this._room.currentState.getStateEvents("m.room.power_levels", "");
        if (plEvent) {
            const content = plEvent.getContent();
            if (content) {
                const users = content.users;
                if (users) {
                    const entries = Object.entries(users);
                    const allowedEntries = entries.filter(([userId]) => {
                        const member = this._room.getMember(userId);
                        if (!member || member.membership !== "join") {
                            return false;
                        }
                        const serverName = getServerName(userId);
                        return !isHostnameIpAddress(serverName) &&
                            !isHostInRegex(serverName, this._bannedHostsRegexps) &&
                            isHostInRegex(serverName, this._allowedHostsRegexps);
                    });
                    const maxEntry = allowedEntries.reduce((max, entry) => {
                        return (entry[1] > max[1]) ? entry : max;
                    }, [null, 0]);
                    const [userId, powerLevel] = maxEntry;
                    // object wasn't empty, and max entry wasn't a demotion from the default
                    if (userId !== null && powerLevel >= 50) {
                        this._highestPlUserId = userId;
                        return;
                    }
                }
            }
        }
        this._highestPlUserId = null;
    }

    _updateAllowedServers() {
        const bannedHostsRegexps = [];
        let allowedHostsRegexps = [new RegExp(".*")]; // default allow everyone
        if (this._room.currentState) {
            const aclEvent = this._room.currentState.getStateEvents("m.room.server_acl", "");
            if (aclEvent && aclEvent.getContent()) {
                const getRegex = (hostname) => new RegExp("^" + utils.globToRegexp(hostname, false) + "$");

                const denied = aclEvent.getContent().deny || [];
                denied.forEach(h => bannedHostsRegexps.push(getRegex(h)));

                const allowed = aclEvent.getContent().allow || [];
                allowedHostsRegexps = []; // we don't want to use the default rule here
                allowed.forEach(h => allowedHostsRegexps.push(getRegex(h)));
            }
        }
        this._bannedHostsRegexps = bannedHostsRegexps;
        this._allowedHostsRegexps = allowedHostsRegexps;
    }

    _updatePopulationMap() {
        const populationMap: { [server: string]: number } = {};
        for (const member of this._room.getJoinedMembers()) {
            const serverName = getServerName(member.userId);
            if (!populationMap[serverName]) {
                populationMap[serverName] = 0;
            }
            populationMap[serverName]++;
        }
        this._populationMap = populationMap;
    }

    _updateServerCandidates() {
        let candidates = [];
        if (this._highestPlUserId) {
            candidates.push(getServerName(this._highestPlUserId));
        }

        const serversByPopulation = Object.keys(this._populationMap)
            .sort((a, b) => this._populationMap[b] - this._populationMap[a])
            .filter(a => {
                return !candidates.includes(a) &&
                    !isHostnameIpAddress(a) &&
                    !isHostInRegex(a, this._bannedHostsRegexps) &&
                    isHostInRegex(a, this._allowedHostsRegexps);
            });

        const remainingServers = serversByPopulation.slice(0, MAX_SERVER_CANDIDATES - candidates.length);
        candidates = candidates.concat(remainingServers);

        this._serverCandidates = candidates;
    }
}

export function makeUserPermalink(userId) {
    return getPermalinkConstructor().forUser(userId);
}

export function makeRoomPermalink(roomId) {
    if (!roomId) {
        throw new Error("can't permalink a falsey roomId");
    }

    // If the roomId isn't actually a room ID, don't try to list the servers.
    // Aliases are already routable, and don't need extra information.
    if (roomId[0] !== '!') return getPermalinkConstructor().forRoom(roomId, []);

    const client = MatrixClientPeg.get();
    const room = client.getRoom(roomId);
    if (!room) {
        return getPermalinkConstructor().forRoom(roomId, []);
    }
    const permalinkCreator = new RoomPermalinkCreator(room);
    permalinkCreator.load();
    return permalinkCreator.forRoom();
}

export function makeGroupPermalink(groupId) {
    return getPermalinkConstructor().forGroup(groupId);
}

export function isPermalinkHost(host: string): boolean {
    // Always check if the permalink is a spec permalink (callers are likely to call
    // parsePermalink after this function).
    if (new SpecPermalinkConstructor().isPermalinkHost(host)) return true;
    return getPermalinkConstructor().isPermalinkHost(host);
}

function getPermalinkConstructor(): PermalinkConstructor {
    if (SdkConfig.get()['permalinkPrefix'] && SdkConfig.get()['permalinkPrefix'] !== matrixtoBaseUrl) {
        return new RiotPermalinkConstructor(SdkConfig.get()['permalinkPrefix']);
    }

    return new SpecPermalinkConstructor();
}

export function parsePermalink(fullUrl: string): PermalinkParts {
    const riotPrefix = SdkConfig.get()['permalinkPrefix'];
    if (fullUrl.startsWith(matrixtoBaseUrl)) {
        return new SpecPermalinkConstructor().parsePermalink(fullUrl);
    } else if (riotPrefix && fullUrl.startsWith(riotPrefix)) {
        return new RiotPermalinkConstructor(riotPrefix).parsePermalink(fullUrl);
    }

    return null; // not a permalink we can handle
}

function getServerName(userId) {
    return userId.split(":").splice(1).join(":");
}

function getHostnameFromMatrixDomain(domain) {
    if (!domain) return null;
    return new URL(`https://${domain}`).hostname;
}

function isHostInRegex(hostname, regexps) {
    hostname = getHostnameFromMatrixDomain(hostname);
    if (!hostname) return true; // assumed
    if (regexps.length > 0 && !regexps[0].test) throw new Error(regexps[0]);

    return regexps.filter(h => h.test(hostname)).length > 0;
}

function isHostnameIpAddress(hostname) {
    hostname = getHostnameFromMatrixDomain(hostname);
    if (!hostname) return false;

    // is-ip doesn't want IPv6 addresses surrounded by brackets, so
    // take them off.
    if (hostname.startsWith("[") && hostname.endsWith("]")) {
        hostname = hostname.substring(1, hostname.length - 1);
    }

    return isIp(hostname);
}
<|endoftext|>


<commit_before>/**
 * Patterns logging - minimal logging framework
 *
 * Copyright 2012 Simplon B.V.
 * Copyright 2012 Florian Friesdorf
 */

define([
        "../compat"  // Needed for function.bind
], function() {
    var root,    // root logger instance
        writer;  // writer instance, used to output log entries

    var Level = {
        DEBUG: 10,
        INFO: 20,
        WARN: 30,
        ERROR: 40,
        FATAL: 50
    };

    function IEConsoleWriter() {
    }

    IEConsoleWriter.prototype = {
            output:  function(level, messages) {
            // console.log will magically appear in IE8 when the user opens the
            // F12 Developer Tools, so we have to test for it every time.
            if (console===undefined || console.log===undefined)
                    return;
            for (i=0; i<messages.length; i++)
                if (typeof messages[i]!=="string")
                    messages[i]=messages[i].toString();

            var message = messages.join(" ");

            if (level<=Level.DEBUG) {
                // console.debug exists but is deprecated
                message="[DEBUG]"+message;
                console.log(messages);
            } else if (level<=Level.INFO)
                console.info(message);
            else if (level<=Level.WARN)
                console.warn(message);
            else
                console.error(message);
        }
    };

    function ConsoleWriter() {
    }

    ConsoleWriter.prototype = {
        output: function(level, messages) {
            if (level<=Level.DEBUG) {
                // console.debug exists but is deprecated
                messages.unshift("[DEBUG]");
                console.log.apply(console, messages);
            } else if (level<=Level.INFO)
                console.info.apply(console, messages);
            else if (level<=Level.WARN)
                console.warn.apply(console, messages);
            else
                console.error.apply(console, messages);
        }
    };

    function Logger(name, parent) {
        this._loggers={};
        this.name=name || "";
        this._parent=parent || null;
        if (name==="root") {
            this._enabled=true;
            this._level=Level.INFO;
        }
    }

    Logger.prototype = {
        getLogger: function(name) {
            if (!(name in this._loggers))
                this._loggers[name] = new Logger(name, this);
            return this._loggers[name];
        },

        _getFlag: function(flag) {
            var context=this;
            flag="_"+flag;
            while (context!==null) {
                if (context[flag]!==undefined)
                    return context[flag];
                context=context._parent;
            }
            return null;
        },

        setEnabled: function(state) {
            this._enabled=!!state;
        },

        isEnabled: function() {
            this._getFlag("enabled");
        },

        setLevel: function(level) {
            if (typeof level==="number")
                this._level=level;
            else if (typeof level==="string") {
                level=level.toUpperCase();
                if (level in Level)
                    this._level=Level[level];
            }
        },

        getLevel: function() {
            return this._getFlag("level");
        },

        getFullname: function() {
            var names = [],
                logger = this;
            while (logger.name !== "root") {
                names.unshift(logger.name);
                logger = logger._parent;
            }
            return names.join(".");
        },

        log: function(level, messages) {
            if (!messages.length || !this._getFlag("enabled") || level<this._getFlag("level"))
                return;
            messages=[this.getFullname() + ":"].concat(messages);
            writer.output(level, messages);
        },

        debug: function() {
            this.log(Level.DEBUG, arguments);
        },

        info: function() {
            this.log(Level.INFO, arguments);
        },

        warn: function() {
            this.log(Level.WARN, arguments);
        },

        error: function() {
            this.log(Level.ERROR, arguments);
        },

        fatal: function() {
            this.log(Level.FATAL, arguments);
        }
    };

    function getWriter() {
        return writer;
    }

    function setWriter(w) {
        writer=w;
    }

    if (window.console && window.console.log && window.console.log.apply!==undefined)
        setWriter(new ConsoleWriter());
    else
        setWriter(new IEConsoleWriter());

    root=new Logger("root");

    var logconfig = /pat-loglevel(|-[^=]+)=([^&]+)/g,
        match;

    while ((match=logconfig.exec(window.location.search))!==null) {
        var logger = (match[1]==="") ? root : root.getLogger(match[1].slice(1));
        logger.setLevel(match[2].toUpperCase());
    }

    return {
        Level: Level,
        getLogger: root.getLogger.bind(root),
        setEnabled: root.setEnabled.bind(root),
        isEnabled: root.isEnabled.bind(root),
        setLevel: root.setLevel.bind(root),
        getLevel: root.getLevel.bind(root),
        debug: root.debug.bind(root),
        info: root.info.bind(root),
        warn: root.warn.bind(root),
        error: root.error.bind(root),
        fatal: root.fatal.bind(root),
        getWriter: getWriter,
        setWriter: setWriter
    };
});
<commit_msg>introduce toplevel patterns logger<commit_after>/**
 * Patterns logging - minimal logging framework
 *
 * Copyright 2012 Simplon B.V.
 * Copyright 2012 Florian Friesdorf
 */

define([
        "../compat"  // Needed for function.bind
], function() {
    var root,    // root logger instance
        writer;  // writer instance, used to output log entries

    var Level = {
        DEBUG: 10,
        INFO: 20,
        WARN: 30,
        ERROR: 40,
        FATAL: 50
    };

    function IEConsoleWriter() {
    }

    IEConsoleWriter.prototype = {
            output:  function(level, messages) {
            // console.log will magically appear in IE8 when the user opens the
            // F12 Developer Tools, so we have to test for it every time.
            if (console===undefined || console.log===undefined)
                    return;
            for (i=0; i<messages.length; i++)
                if (typeof messages[i]!=="string")
                    messages[i]=messages[i].toString();

            var message = messages.join(" ");

            if (level<=Level.DEBUG) {
                // console.debug exists but is deprecated
                message="[DEBUG]"+message;
                console.log(messages);
            } else if (level<=Level.INFO)
                console.info(message);
            else if (level<=Level.WARN)
                console.warn(message);
            else
                console.error(message);
        }
    };

    function ConsoleWriter() {
    }

    ConsoleWriter.prototype = {
        output: function(level, messages) {
            if (level<=Level.DEBUG) {
                // console.debug exists but is deprecated
                messages.unshift("[DEBUG]");
                console.log.apply(console, messages);
            } else if (level<=Level.INFO)
                console.info.apply(console, messages);
            else if (level<=Level.WARN)
                console.warn.apply(console, messages);
            else
                console.error.apply(console, messages);
        }
    };

    function Logger(name, parent) {
        this._loggers={};
        this.name=name || "";
        this._parent=parent || null;
        if (name==="root") {
            this._enabled=true;
            this._level=Level.INFO;
        }
    }

    Logger.prototype = {
        getLogger: function(name) {
            if (!(name in this._loggers))
                this._loggers[name] = new Logger(name, this);
            return this._loggers[name];
        },

        _getFlag: function(flag) {
            var context=this;
            flag="_"+flag;
            while (context!==null) {
                if (context[flag]!==undefined)
                    return context[flag];
                context=context._parent;
            }
            return null;
        },

        setEnabled: function(state) {
            this._enabled=!!state;
        },

        isEnabled: function() {
            this._getFlag("enabled");
        },

        setLevel: function(level) {
            if (typeof level==="number")
                this._level=level;
            else if (typeof level==="string") {
                level=level.toUpperCase();
                if (level in Level)
                    this._level=Level[level];
            }
        },

        getLevel: function() {
            return this._getFlag("level");
        },

        getFullname: function() {
            var names = [],
                logger = this;
            while (logger.name !== "root") {
                names.unshift(logger.name);
                logger = logger._parent;
            }
            return names.join(".");
        },

        log: function(level, messages) {
            if (!messages.length || !this._getFlag("enabled") || level<this._getFlag("level"))
                return;
            messages=[this.getFullname() + ":"].concat(messages);
            writer.output(level, messages);
        },

        debug: function() {
            this.log(Level.DEBUG, arguments);
        },

        info: function() {
            this.log(Level.INFO, arguments);
        },

        warn: function() {
            this.log(Level.WARN, arguments);
        },

        error: function() {
            this.log(Level.ERROR, arguments);
        },

        fatal: function() {
            this.log(Level.FATAL, arguments);
        }
    };

    function getWriter() {
        return writer;
    }

    function setWriter(w) {
        writer=w;
    }

    if (window.console && window.console.log && window.console.log.apply!==undefined)
        setWriter(new ConsoleWriter());
    else
        setWriter(new IEConsoleWriter());

    root=new Logger("root");
    root = root.getLogger("patterns");

    var logconfig = /pat-loglevel(|-[^=]+)=([^&]+)/g,
        match;

    while ((match=logconfig.exec(window.location.search))!==null) {
        var logger = (match[1]==="") ? root : root.getLogger(match[1].slice(1));
        logger.setLevel(match[2].toUpperCase());
    }

    return {
        Level: Level,
        getLogger: root.getLogger.bind(root),
        setEnabled: root.setEnabled.bind(root),
        isEnabled: root.isEnabled.bind(root),
        setLevel: root.setLevel.bind(root),
        getLevel: root.getLevel.bind(root),
        debug: root.debug.bind(root),
        info: root.info.bind(root),
        warn: root.warn.bind(root),
        error: root.error.bind(root),
        fatal: root.fatal.bind(root),
        getWriter: getWriter,
        setWriter: setWriter
    };
});
<|endoftext|>


<commit_before>
function post_to_url(path, params, method) {
    method = method || "post"; // Set method to post by default, if not specified.

    // The rest of this code assumes you are not using a library.
    // It can be made less wordy if you use one.
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", path);

    for(var key in params) {
        if(params.hasOwnProperty(key)) {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", params[key]);

            form.appendChild(hiddenField);
         }
    }

    document.body.appendChild(form);
    form.submit();
    return false;
}

function toggle(divid, aid, user, comment, scrollid) {
	var ele = document.getElementById(divid);
	var text = document.getElementById(aid);
	if(ele.style.display == "block") {
    		ele.style.display = "none";
		text.innerHTML = "show";
  	}
	else {
		ele.style.display = "block";
		text.innerHTML = "hide";
	}
//	post_to_url("includes/php/update_discussion.php", { user: user, comment: comment }, 'post');
//	alert("user "+user+" comment "+comment);
  $.ajax({
    type: "POST",
    url: "includes/php/update_discussion.php",
    data: { user: user, comment: comment },
    success: function() {
    }
  });

	document.getElementById(scrollid).scrollIntoView();
}

function simpleToggle(divid, scrollid) {
	var ele = document.getElementById(divid);
	if(ele.style.display == "block") {
    		ele.style.display = "none";
  	}
	else {
		ele.style.display = "block";
	}

	document.getElementById(scrollid).scrollIntoView();
}

function screenWidth(divid) {
	var ele = document.getElementById(divid);
	alert("el width is "+ele);
	ele.style.width = window.innerWidth;
}<commit_msg>no idea<commit_after>
function post_to_url(path, params, method) {
    method = method || "post"; // Set method to post by default, if not specified.

    // The rest of this code assumes you are not using a library.
    // It can be made less wordy if you use one.
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", path);

    for(var key in params) {
        if(params.hasOwnProperty(key)) {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", params[key]);

            form.appendChild(hiddenField);
         }
    }

    document.body.appendChild(form);
    form.submit();
    return false;
}

function toggle(divid, aid, user, comment, scrollid) {
	var ele = document.getElementById(divid);
	var text = document.getElementById(aid);
	if(ele.style.display == "block") {
    		ele.style.display = "none";
		text.innerHTML = "show";
  	}
	else {
		ele.style.display = "block";
		text.innerHTML = "hide";
	}
//	post_to_url("includes/php/update_discussion.php", { user: user, comment: comment }, 'post');
//	alert("user "+user+" comment "+comment);
  $.ajax({
    type: "POST",
    url: "includes/php/update_discussion.php",
    data: { user: user, comment: comment },
    success: function() {
    }
  });

	document.getElementById(scrollid).scrollIntoView();
}

function simpleToggle(divid, scrollid) {
	var ele = document.getElementById(divid);
	if(ele.style.display == "block") {
    		ele.style.display = "none";
  	}
	else {
		ele.style.display = "block";
	}

	document.getElementById(scrollid).scrollIntoView();
}

function screenWidth(divid) {
	var ele = document.getElementById(divid);
	alert("el width is "+ele.id);
	ele.style.width = window.innerWidth;
}<|endoftext|>


<commit_before>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      27.10.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('logger.js');

/**
 * @class
 *
 * Object for dispatching all incoming events.
 *
 */
M.EventDispatcher = M.Object.create({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.EventDispatcher',

    /**
     * This method is called whenever an event is triggered within the app.
     *
     * @param {Object} evt The event.
     */
    eventDidHappen: function(evt) {
        this.delegateEvent(evt.type, evt.currentTarget.id, evt.keyCode);
    },

    /**
     * This method looks for a corresponding event inside the view manager and
     * delegates the call directly to the responsible controller defined by the
     * target and action properties of the view.
     *
     * @param {String} type The type of event that occured, e.g. 'click'.
     * @param {String} id The id of the element that triggered the event.
     * @param {Number} keyCode The keyCode property of the event, necessary for keypress event, e.g. keyCode is 13 when enter is pressed.
     */
    delegateEvent: function(type, id, keyCode) {
        var view = M.ViewManager.getViewById(id);

        switch(type) {
            case 'click':
                if(view && view.target && view.action) {
                    eval(view.target)[view.action](id);
                }
                break;
            case 'change':
                view.setValueFromDOM();
                break;
            case 'keyup':
                view.setValueFromDOM();
                if(view && view.evt && view.evt.keyUp.target && view.evt.keyUp.action) {
                    eval(view.evt.keyUp.target)[view.evt.keyUp.action](view.value);
                }
                break;
            case 'focus':
                view.hasFocus();
                break;
            case 'blur':
                view.lostFocus();
                break;
        }
    }

});

$(document).ready(function() {
    var eventList = 'click change keyup focus blur';
    $('*[id]').bind(eventList, function(evt) { M.EventDispatcher.eventDidHappen(evt);});
});
<commit_msg>Added support for Enter button for TextFieldViews<commit_after>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      27.10.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('logger.js');

/**
 * @class
 *
 * Object for dispatching all incoming events.
 *
 */
M.EventDispatcher = M.Object.create({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.EventDispatcher',

    /**
     * This method is called whenever an event is triggered within the app.
     *
     * @param {Object} evt The event.
     */
    eventDidHappen: function(evt) {
        this.delegateEvent(evt.type, evt.currentTarget.id, evt.keyCode);
    },

    /**
     * This method looks for a corresponding event inside the view manager and
     * delegates the call directly to the responsible controller defined by the
     * target and action properties of the view.
     *
     * @param {String} type The type of event that occured, e.g. 'click'.
     * @param {String} id The id of the element that triggered the event.
     * @param {Number} keyCode The keyCode property of the event, necessary for keypress event, e.g. keyCode is 13 when enter is pressed.
     */
    delegateEvent: function(type, id, keyCode) {
        var view = M.ViewManager.getViewById(id);

        switch(type) {
            case 'click':
                if(view && view.target && view.action && view.type !== 'M.TextFieldView') {
                    eval(view.target)[view.action](id);
                }
                break;
            case 'change':
                view.setValueFromDOM();
                break;
            case 'keyup':
                if(keyCode === 13 && view.type === 'M.TextFieldView') {
                    console.log(view);
                    console.log(view.target);
                    console.log(view.action);
                    if(view && view.target && view.action) {
                        console.log('key ' + keyCode + ' pressed...');
                        eval(view.target)[view.action](id);
                    }
                }
                break;
            case 'focus':
                view.gotFocus();
                break;
            case 'blur':
                view.lostFocus();
                break;
            case 'orientationchange':
                view.orientationDidChange(evt);
                break;
        }
    }

});

$(document).ready(function() {
    var eventList = 'click change keyup focus blur';
    $('*[id]').bind(eventList, function(evt) { M.EventDispatcher.eventDidHappen(evt);});
});
<|endoftext|>


<commit_before>const path = require('path');
const fs = require('fs');
const S3 = require('aws-sdk/clients/s3');

const {
  reports_s3_akid,
  reports_s3_sak,
  DRONE,
  DRONE_BUILD_NUMBER,
  DRONE_STAGE_NAME,
} = process.env;

if (!DRONE) {
  console.log('Not running in drone, exiting...');
  process.exit(0);
}

const s3 = new S3({
  accessKeyId: reports_s3_akid,
  secretAccessKey: reports_s3_sak,
  signatureVersion: 'v4',
});

const ROOTDIR = path.dirname(path.dirname(require.main.filename));
const MODULE = path.basename(ROOTDIR);
const OBJECT_NAME = `${DRONE_BUILD_NUMBER}/${MODULE}/${DRONE_STAGE_NAME}.html`;
const REPORT_FILE = `${ROOTDIR}/mochawesome-report/mochawesome.html`;

const uploadParams = {
  Body: fs.readFileSync(REPORT_FILE),
  Bucket: 'bitgo-sdk-test-reports',
  Key: OBJECT_NAME,
  ACL: 'public-read',
  ContentType: 'text/html',
};

s3.putObject(uploadParams, (err, data) => {
  if (err) {
    console.error(`S3 error: ${err}\n${err.stack}`);
  } else {
    console.log(`=== TEST REPORT UPLOADED SUCCESSFULLY ===`);
    console.log(`https://bitgo-sdk-test-reports.s3.amazonaws.com/${OBJECT_NAME}`);
    console.log();
  }
});

<commit_msg>Make sure test report exists before attempting to upload<commit_after>const path = require('path');
const fs = require('fs');
const S3 = require('aws-sdk/clients/s3');

const {
  reports_s3_akid,
  reports_s3_sak,
  DRONE,
  DRONE_BUILD_NUMBER,
  DRONE_STAGE_NAME,
} = process.env;

if (!DRONE) {
  console.log('Not running in drone, exiting...');
  process.exit(0);
}

const s3 = new S3({
  accessKeyId: reports_s3_akid,
  secretAccessKey: reports_s3_sak,
  signatureVersion: 'v4',
});

const ROOTDIR = path.dirname(path.dirname(require.main.filename));
const MODULE = path.basename(ROOTDIR);
const OBJECT_NAME = `${DRONE_BUILD_NUMBER}/${MODULE}/${DRONE_STAGE_NAME}.html`;
const REPORT_FILE = `${ROOTDIR}/mochawesome-report/mochawesome.html`;

if (!fs.existsSync(REPORT_FILE)) {
  console.warn(`Report file '${REPORT_FILE}' not found. Skipping test report upload...`);
} else {
  const uploadParams = {
    Body: fs.readFileSync(REPORT_FILE),
    Bucket: 'bitgo-sdk-test-reports',
    Key: OBJECT_NAME,
    ACL: 'public-read',
    ContentType: 'text/html',
  };

  s3.putObject(uploadParams, (err, data) => {
    if (err) {
      console.error(`S3 error: ${err}\n${err.stack}`);
    } else {
      console.log(`=== TEST REPORT UPLOADED SUCCESSFULLY ===`);
      console.log(`https://bitgo-sdk-test-reports.s3.amazonaws.com/${OBJECT_NAME}`);
      console.log();
    }
  });
}
<|endoftext|>