<commit_before>function _get(q) {
	if (q[0] === '*') return document.querySelectorAll(q.substr(1));
	else return document.querySelector(q);
}

Object.defineProperties(Number.prototype, {
	'between': {
		value: function(a, b) {
			if (this >= a && this <= b) return true;
			return false;
		}
	},

	'outside': {
		value: function(a, b) {
			if (this <= a || this >= b) return true;
			return false;
		}
	}
});

function Game() {
	var CANVAS = _get('#game-canvas'),
		gameW = _get('#container').getBoundingClientRect().width,
		gameH = _get('#container').getBoundingClientRect().height,
		bricks = new Array,
		t = 0,
		pi = Math.PI,
		animationID, pad, c;

	function fastLine(x1, y1, x2, y2, color, width) {
		c.beginPath();
		c.strokeStyle = color;
		c.lineWidth = width;
		c.moveTo(x1, y1);
		c.lineTo(x2, y2);
		c.closePath();
		c.stroke();
	}

	function Brick(x, y, w, h, color, lives) {
		var borderW = w/15,
			x2 = x+w, y2 = y+h;

		this.dead = false;
		this.newTeta = 0;

		this.draw = function() {
			c.fillStyle = color;
			c.lineWidth = borderW;
			if (color === 'red') c.strokeStyle = '#B70000';
			if (color === 'gold') c.strokeStyle = '#A28000';
			if (color === 'green') c.strokeStyle = '#004200';

			c.fillRect(x, y, w, h);
			c.strokeRect(x+borderW/2, y+borderW/2, w-borderW, h-borderW);
		}

		this.collision = function(ballX, ballY, ballR, ballTeta) {
			// returns true if the brick gets hit by the ball
			var distX = Math.abs(ballX - x - w/2),
				distY = Math.abs(ballY - y - h/2),
				dX = distX - w/2,
				dY = distY - h/2,
				collided = false;

			if (distX > w/2+ballR || distY > h/2+ballR) return collided;
			else if (distX <= w/2 || distY <= h/2 || dX*dX + dY*dY <= ballR*ballR) collided = true;

			if (collided) {
				this.newTeta = ballTeta;
			}

			if (collided && --lives === 0) this.dead = true;
			return collided;
		}
	}

	function Pad() {
		var r = 7/100*gameW,
			h = 1/100*gameH,
			x = gameW/2,
			y = gameH-h,
			hitbox = false; // hitbox prevents the ball from hitting the pad more than once

		function draw() {
			c.fillStyle = 'white';
			c.fillRect(x-r, y, 2*r, h);

			// DEBUG
			fastLine(x, y, x, 0, 'white', 2);
		}

		this.direction = false;
		this.speed = 15;
		this.newTeta = 0;

		this.collision = function(ballX, ballY, ballR, ballTeta) {
			var dX = ballX - x;

			// DEBUG
			c.fillText('ballTeta = ' + ballTeta/pi*180, 50, gameH - 200);
			fastLine(ballX, ballY, ballX + 1000*Math.cos(-ballTeta), ballY + 1000*Math.sin(-ballTeta), 'pink', 2);

			if (!(Math.abs(dX) <= r || ballY+ballR >= y)) hitbox = false;

			if (!hitbox && Math.abs(dX) <= r && ballY+ballR >= y) {
				hitbox = true;
				this.newTeta = 2*pi - ballTeta;

				// deviation of the ball
				if (dX > r/5) this.newTeta -= (this.newTeta-pi/36)*dX/r;
				else if (dX < r/5) this.newTeta -= (pi-this.newTeta-pi/36)*dX/r

				return true;
			}
			return false;
		}

		this.update = function() {
			if (this.direction === 'left') {
				x -= this.speed;

				if (x-r < 0) {
					this.direction = false;
					x = r;
				}
			}

			if (this.direction === 'right') {
				x += this.speed;

				if (x+r > gameW) {
					this.direction = false;
					x = gameW-r;
				}
			}

			draw();
		}

		draw();
	}

	function Ball() {
		var r = 1.5/100*gameW,
			x = gameW/2,
			y = gameH - gameH/100*5,
			teta = Math.random()*4*pi/6 + pi/6,
			speed = 8;

		function draw() {
			c.fillStyle = 'lightgrey';
			c.beginPath();
			c.arc(x, y, r, 0, pi*2);
			c.closePath();
			c.fill();

			// DEBUG
			fastLine(x, 0, x, gameH, 'lightgrey', 2);
			fastLine(0, y, gameW, y, 'lightgrey', 2);
		}

		this.update = function() {
			// Walls
			if (x+r >= gameW || x-r <= 0) teta = teta <= pi ? pi - teta : 3*pi - teta;	// right-left
			if (y-r <= 0) teta = 2*pi - teta;											// top
			if (pad.collision(x, y, r, teta)) teta = pad.newTeta;						// pad

			// Bricks
			for (var i=0, b; b = bricks[i]; i++) if (b.collision(x, y, r, teta)) {
				teta = b.newTeta;
				if (b.dead) bricks.splice(i--, 1);
			}

			x += speed*Math.cos(teta);
			y -= speed*Math.sin(teta);

			draw();
		}

		draw();
	}

	function spawnBricks(rows, columns, height) {
		var width = gameW/columns,
			height = height/100*gameH,
			color = 'red',
			b;

		for (var i=0; i < rows; i++) {
			for (var j=0; j < columns; j++) {
				if (i > 1) color = 'gold';
				if (i > 4) color = 'green';
				b = new Brick(j*width, i*height, width, height, color, 1);
				b.draw();
				bricks.push(b);
			}
		}
	}

	function update() {
		c.clearRect(0, 0, gameW, gameH);
		for (var i=0, b; b = bricks[i]; i++) b.draw();

		pad.update();
		ball.update();

		t++;
		animationID = requestAnimationFrame(update);
	}

	this.start = function() {
		CANVAS.width = CANVAS.height = gameW > gameH ? gameW = gameH : gameH = gameW;
		c = CANVAS.getContext('2d');

		spawnBricks(10, 10, 5);
		pad = new Pad();
		ball = new Ball();

		document.addEventListener('keydown', function(e) {
			if (e.keyCode === 37) pad.direction = 'left';
			if (e.keyCode === 39) pad.direction = 'right';
		});

		document.addEventListener('keyup', function(e) {
			if ((e.keyCode === 37 && pad.direction === 'left') || (e.keyCode === 39 && pad.direction === 'right')) pad.direction = false;
		});

		animationID = requestAnimationFrame(update);
	}
}

var game = new Game();
game.start();<commit_msg>Ball-brick border collision implemented<commit_after>function _get(q) {
	if (q[0] === '*') return document.querySelectorAll(q.substr(1));
	else return document.querySelector(q);
}

Object.defineProperties(Number.prototype, {
	'between': {
		value: function(a, b) {
			if (this >= a && this <= b) return true;
			return false;
		}
	},

	'outside': {
		value: function(a, b) {
			if (this <= a || this >= b) return true;
			return false;
		}
	}
});

function Game() {
	var CANVAS = _get('#game-canvas'),
		gameW = _get('#container').getBoundingClientRect().width,
		gameH = _get('#container').getBoundingClientRect().height,
		bricks = new Array,
		t = 0,
		pi = Math.PI,
		animationID, pad, c;

	function fastLine(x1, y1, x2, y2, color, width) {
		c.beginPath();
		c.strokeStyle = color;
		c.lineWidth = width;
		c.moveTo(x1, y1);
		c.lineTo(x2, y2);
		c.closePath();
		c.stroke();
	}

	function Brick(x, y, w, h, color, lives) {
		var borderW = w/15,
			x2 = x+w, y2 = y+h;

		this.dead = false;
		this.newTeta = 0;

		this.draw = function() {
			c.fillStyle = color;
			c.lineWidth = borderW;
			if (color === 'red') c.strokeStyle = '#B70000';
			if (color === 'gold') c.strokeStyle = '#A28000';
			if (color === 'green') c.strokeStyle = '#004200';

			c.fillRect(x, y, w, h);
			c.strokeRect(x+borderW/2, y+borderW/2, w-borderW, h-borderW);
		}

		this.collision = function(ballX, ballY, ballR, ballTeta) {
			// returns true if the brick gets hit by the ball
			var distX = Math.abs(ballX - x - w/2),
				distY = Math.abs(ballY - y - h/2),
				dX = distX - w/2,
				dY = distY - h/2,
				collided = false;

			if (distX > w/2+ballR || distY > h/2+ballR) return collided;
			else if (distX <= w/2 || distY <= h/2 || dX*dX + dY*dY <= ballR*ballR) collided = true;

			if (collided) {
				// vertical borders
				if (ballX.between(x-ballR, x2+ballR) && ballY.between(y, y2)) this.newTeta = ballTeta <= pi ? pi - ballTeta : 3*pi - ballTeta;
				// horizontal borders
				else if (ballY.between(y-ballR, y2+ballR) && ballX.between(x, x2)) this.newTeta =  2*pi - ballTeta;

				else this.newTeta = ballTeta;
			}

			if (collided && --lives === 0) this.dead = true;
			return collided;
		}
	}

	function Pad() {
		var r = 7/100*gameW,
			h = 1/100*gameH,
			x = gameW/2,
			y = gameH-h,
			hitbox = false; // hitbox prevents the ball from hitting the pad more than once

		function draw() {
			c.fillStyle = 'white';
			c.fillRect(x-r, y, 2*r, h);

			// DEBUG
			fastLine(x, y, x, 0, 'white', 2);
		}

		this.direction = false;
		this.speed = 15;
		this.newTeta = 0;

		this.collision = function(ballX, ballY, ballR, ballTeta) {
			var dX = ballX - x;

			// DEBUG
			c.fillText('ballTeta = ' + ballTeta/pi*180, 50, gameH - 200);
			fastLine(ballX, ballY, ballX + 1000*Math.cos(-ballTeta), ballY + 1000*Math.sin(-ballTeta), 'pink', 2);

			if (!(Math.abs(dX) <= r || ballY+ballR >= y)) hitbox = false;

			if (!hitbox && Math.abs(dX) <= r && ballY+ballR >= y) {
				hitbox = true;
				this.newTeta = 2*pi - ballTeta;

				// deviation of the ball
				if (dX > r/5) this.newTeta -= (this.newTeta-pi/36)*dX/r;
				else if (dX < r/5) this.newTeta -= (pi-this.newTeta-pi/36)*dX/r

				return true;
			}
			return false;
		}

		this.update = function() {
			if (this.direction === 'left') {
				x -= this.speed;

				if (x-r < 0) {
					this.direction = false;
					x = r;
				}
			}

			if (this.direction === 'right') {
				x += this.speed;

				if (x+r > gameW) {
					this.direction = false;
					x = gameW-r;
				}
			}

			draw();
		}

		draw();
	}

	function Ball() {
		var r = 1.5/100*gameW,
			x = gameW/2,
			y = gameH - gameH/100*5,
			teta = Math.random()*4*pi/6 + pi/6,
			speed = 10;

		function draw() {
			c.fillStyle = 'lightgrey';
			c.beginPath();
			c.arc(x, y, r, 0, pi*2);
			c.closePath();
			c.fill();

			// DEBUG
			fastLine(x, 0, x, gameH, 'lightgrey', 2);
			fastLine(0, y, gameW, y, 'lightgrey', 2);
		}

		this.update = function() {
			// Walls
			if (x+r >= gameW || x-r <= 0) teta = teta <= pi ? pi - teta : 3*pi - teta;	// right-left
			if (y-r <= 0) teta = 2*pi - teta;											// top
			if (pad.collision(x, y, r, teta)) teta = pad.newTeta;						// pad

			// Bricks
			for (var i=0, b; b = bricks[i]; i++) if (b.collision(x, y, r, teta)) {
				teta = b.newTeta;
				if (b.dead) bricks.splice(i--, 1);
			}

			x += speed*Math.cos(teta);
			y -= speed*Math.sin(teta);

			draw();
		}

		draw();
	}

	function spawnBricks(rows, columns, height) {
		var width = gameW/columns,
			height = height/100*gameH,
			color = 'red',
			b;

		for (var i=0; i < rows; i++) {
			for (var j=0; j < columns; j++) {
				if (i > 1) color = 'gold';
				if (i > 4) color = 'green';
				b = new Brick(j*width, i*height, width, height, color, 1);
				b.draw();
				bricks.push(b);
			}
		}
	}

	function update() {
		c.clearRect(0, 0, gameW, gameH);
		for (var i=0, b; b = bricks[i]; i++) b.draw();

		pad.update();
		ball.update();

		t++;
		animationID = requestAnimationFrame(update);
	}

	this.start = function() {
		CANVAS.width = CANVAS.height = gameW > gameH ? gameW = gameH : gameH = gameW;
		c = CANVAS.getContext('2d');

		spawnBricks(10, 10, 5);
		pad = new Pad();
		ball = new Ball();

		document.addEventListener('keydown', function(e) {
			if (e.keyCode === 37) pad.direction = 'left';
			if (e.keyCode === 39) pad.direction = 'right';
		});

		document.addEventListener('keyup', function(e) {
			if ((e.keyCode === 37 && pad.direction === 'left') || (e.keyCode === 39 && pad.direction === 'right')) pad.direction = false;
		});

		animationID = requestAnimationFrame(update);
	}
}

var game = new Game();
game.start();<|endoftext|>


<commit_before>
goog.require('goog.debug.Console');
goog.require('goog.testing.jsunit');
goog.require('ydn.db.core.Storage');

var reachedFinalContinuation, schema, debug_console, db, objs, arr_objs;

var db_name = 'test_keys_3';
var store_name = 'st';
var string_key_store = 'st3';
var arr_store_name = 'st2';

var setUp = function () {
  if (!debug_console) {
    debug_console = new goog.debug.Console();
    debug_console.setCapturing(true);
    goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    //goog.debug.Logger.getLogger('ydn.gdata.MockServer').setLevel(goog.debug.Logger.Level.FINEST);
    goog.debug.Logger.getLogger('ydn.db').setLevel(goog.debug.Logger.Level.FINE);
    //goog.debug.Logger.getLogger('ydn.db.con').setLevel(goog.debug.Logger.Level.FINEST);
    //goog.debug.Logger.getLogger('ydn.db.req').setLevel(goog.debug.Logger.Level.FINEST);
  }

  var value_index = new ydn.db.schema.Index('value', ydn.db.schema.DataType.TEXT, true);
  var tag_index = new ydn.db.schema.Index('type', ydn.db.schema.DataType.TEXT, false, true);
  var x_index = new ydn.db.schema.Index('x', ydn.db.schema.DataType.NUMERIC, false);
  var store_schema = new ydn.db.schema.Store(store_name, 'id', false,
    ydn.db.schema.DataType.INTEGER, [x_index, value_index, tag_index]);
  var arr_store_schema = new ydn.db.schema.Store(arr_store_name, 'id', false,
    ydn.db.schema.DataType.ARRAY);
  var string_store = new ydn.db.schema.Store(string_key_store, 'value');
  schema = new ydn.db.schema.Database(undefined, [arr_store_schema, store_schema, string_store]);
  db = new ydn.db.core.Storage(db_name, schema, options);

  objs = [
    {id: -3, value: 'a0', x: 1, type: ['a', 'b'], remark: 'test ' + Math.random()},
    {id: 0, value: 'a2', x: 3,type: ['a'], remark: 'test ' + Math.random()},
    {id: 1, value: 'ba', x: 2,type: ['b'], remark: 'test ' + Math.random()},
    {id: 3, value: 'bc', x: 2,type: ['b', 'c'], remark: 'test ' + Math.random()},
    {id: 10, value: 'c', type: ['c'], remark: 'test ' + Math.random()},
    {id: 11, value: 'c1', type: ['c', 'a', 'b'], remark: 'test ' + Math.random()},
    {id: 20, value: 'ca', x: 2,remark: 'test ' + Math.random()}
  ];

  arr_objs = [
    {id:['a', 'qs0'], value:0, type:'a'},
    {id:['a', 'qs1'], value:1, type:'a'},
    {id:['b', 'at2'], value:2, type:'b'},
    {id:['b', 'bs1'], value:3, type:'b'},
    {id:['c', 'bs2'], value:4, type:'c'},
    {id:['c', 'bs3'], value:5, type:'c'},
    {id:['c', 'st3'], value:6, type:'c'}
  ];

  db.put(arr_store_name, arr_objs);
  db.put(string_key_store, objs);
  db.put(store_name, objs).addCallback(function (value) {
    console.log(db + ' ready.');
  });

};

var tearDown = function() {
  assertTrue('The final continuation was not reached', reachedFinalContinuation);
};

var test_store = function () {

  var get_done;
  var result;
  var keys = objs.map(function(x) {return x.id});

  waitForCondition(
    // Condition
    function () {
      return get_done;
    },
    // Continuation
    function () {
      assertArrayEquals(keys, result);
      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  goog.net.XhrIo

  db.keys(store_name).addCallback(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    get_done = true;
  });

};

var test_primary_key_range = function () {

  var get_done;
  var result;
  var keys = objs.slice(2, 5).map(function(x) {return x.id});

  waitForCondition(
    // Condition
    function () {
      return get_done;
    },
    // Continuation
    function () {
      assertArrayEquals(keys, result);
      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.bound(1, 10);
  db.keys(store_name, range).addCallback(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    get_done = true;
  });

};

var test_by_index_key_range = function () {

  var get_done;
  var result;
  var keys = objs.slice(2, 5).map(function(x) {return x.id});

  waitForCondition(
    // Condition
    function () {
      return get_done;
    },
    // Continuation
    function () {
      assertArrayEquals(keys, result);
      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.bound('ba', 'c');
  db.keys(store_name, 'value', range).addCallback(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    get_done = true;
  });

};

var test_array_key = function () {

  var keys = arr_objs.map(function(x) {return x.id});
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  db.keys(arr_store_name).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var test_keyrange_starts = function () {

  var keys = [];
  for (var i = 0; i < objs.length; i++) {
    if (goog.string.startsWith(objs[i].value, 'b')) {
      keys.push(objs[i].id);
    }
  }
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.starts('b');
  db.keys(store_name, 'value', range).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var test_string_key_starts = function () {

  var keys = [];
  for (var i = 0; i < objs.length; i++) {
    if (goog.string.startsWith(objs[i].value, 'b')) {
      keys.push(objs[i].value);
    }
  }
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.starts('b');
  db.keys(string_key_store, range).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var test_array_key_key_range = function () {

  var keys = arr_objs.slice(2, 4).map(function(x) {return x.id});
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.starts(['b']);
  db.keys(arr_store_name, range).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var testCase = new goog.testing.ContinuationTestCase();
testCase.autoDiscoverTests();
G_testRunner.initialize(testCase);

<commit_msg>fix for websql<commit_after>
goog.require('goog.debug.Console');
goog.require('goog.testing.jsunit');
goog.require('ydn.db.core.Storage');

var reachedFinalContinuation, schema, debug_console, db, objs, arr_objs;

var db_name = 'test_keys_3';
var store_name = 'st';
var string_key_store = 'st3';
var arr_store_name = 'st2';

var setUp = function () {
  if (!debug_console) {
    debug_console = new goog.debug.Console();
    debug_console.setCapturing(true);
    goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    //goog.debug.Logger.getLogger('ydn.gdata.MockServer').setLevel(goog.debug.Logger.Level.FINEST);
    goog.debug.Logger.getLogger('ydn.db').setLevel(goog.debug.Logger.Level.FINE);
    //goog.debug.Logger.getLogger('ydn.db.con').setLevel(goog.debug.Logger.Level.FINEST);
    //goog.debug.Logger.getLogger('ydn.db.req').setLevel(goog.debug.Logger.Level.FINEST);
  }

  var value_index = new ydn.db.schema.Index('value', ydn.db.schema.DataType.TEXT, true);
  var tag_index = new ydn.db.schema.Index('type', ydn.db.schema.DataType.TEXT, false, true);
  var x_index = new ydn.db.schema.Index('x', ydn.db.schema.DataType.NUMERIC, false);
  var store_schema = new ydn.db.schema.Store(store_name, 'id', false,
    ydn.db.schema.DataType.INTEGER, [x_index, value_index, tag_index]);
  var arr_store_schema = new ydn.db.schema.Store(arr_store_name, 'id', false,
    ydn.db.schema.DataType.ARRAY);
  var string_store = new ydn.db.schema.Store(string_key_store, 'value');
  schema = new ydn.db.schema.Database(undefined, [arr_store_schema, store_schema, string_store]);
  db = new ydn.db.core.Storage(db_name, schema, options);

  objs = [
    {id: -3, value: 'a0', x: 1, type: ['a', 'b'], remark: 'test ' + Math.random()},
    {id: 0, value: 'a2', x: 3,type: ['a'], remark: 'test ' + Math.random()},
    {id: 1, value: 'ba', x: 2,type: ['b'], remark: 'test ' + Math.random()},
    {id: 3, value: 'bc', x: 2,type: ['b', 'c'], remark: 'test ' + Math.random()},
    {id: 10, value: 'c', type: ['c'], remark: 'test ' + Math.random()},
    {id: 11, value: 'c1', type: ['c', 'a', 'b'], remark: 'test ' + Math.random()},
    {id: 20, value: 'ca', x: 2,remark: 'test ' + Math.random()}
  ];

  arr_objs = [
    {id:['a', 'qs0'], value:0, type:'a'},
    {id:['a', 'qs1'], value:1, type:'a'},
    {id:['b', 'at2'], value:2, type:'b'},
    {id:['b', 'bs1'], value:3, type:'b'},
    {id:['c', 'bs2'], value:4, type:'c'},
    {id:['c', 'bs3'], value:5, type:'c'},
    {id:['c', 'st3'], value:6, type:'c'}
  ];

  db.put(arr_store_name, arr_objs);
  db.put(string_key_store, objs);
  db.put(store_name, objs).addCallback(function (value) {
    console.log(db + ' ready.');
  });

};

var tearDown = function() {
  assertTrue('The final continuation was not reached', reachedFinalContinuation);
};

var test_store = function () {

  var get_done;
  var result;
  var keys = objs.map(function(x) {return x.id});

  waitForCondition(
    // Condition
    function () {
      return get_done;
    },
    // Continuation
    function () {
      assertArrayEquals(keys, result);
      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  db.keys(store_name).addCallback(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    get_done = true;
  });

};

var test_primary_key_range = function () {

  var get_done;
  var result;
  var keys = objs.slice(2, 5).map(function(x) {return x.id});

  waitForCondition(
    // Condition
    function () {
      return get_done;
    },
    // Continuation
    function () {
      assertArrayEquals(keys, result);
      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.bound(1, 10);
  db.keys(store_name, range).addCallback(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    get_done = true;
  });

};

var test_by_index_key_range = function () {

  var get_done;
  var result;
  var keys = objs.slice(2, 5).map(function(x) {return x.id});

  waitForCondition(
    // Condition
    function () {
      return get_done;
    },
    // Continuation
    function () {
      assertArrayEquals(keys, result);
      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.bound('ba', 'c');
  db.keys(store_name, 'value', range).addCallback(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    get_done = true;
  });

};

var test_array_key = function () {

  var keys = arr_objs.map(function(x) {return x.id});
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  db.keys(arr_store_name).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var test_keyrange_starts = function () {

  var keys = [];
  for (var i = 0; i < objs.length; i++) {
    if (goog.string.startsWith(objs[i].value, 'b')) {
      keys.push(objs[i].id);
    }
  }
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.starts('b');
  db.keys(store_name, 'value', range).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var test_string_key_starts = function () {

  var keys = [];
  for (var i = 0; i < objs.length; i++) {
    if (goog.string.startsWith(objs[i].value, 'b')) {
      keys.push(objs[i].value);
    }
  }
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.starts('b');
  db.keys(string_key_store, range).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var test_array_key_key_range = function () {

  var keys = arr_objs.slice(2, 4).map(function(x) {return x.id});
  var done, result;

  waitForCondition(
    // Condition
    function () {
      return done;
    },
    // Continuation
    function () {
      assertArrayEquals('result', keys, result);

      reachedFinalContinuation = true;
    },
    100, // interval
    1000); // maxTimeout

  var range = ydn.db.KeyRange.starts(['b']);
  db.keys(arr_store_name, range).addBoth(function (value) {
    //console.log('fetch value: ' + JSON.stringify(value));
    result = value;
    done = true;
  });

};

var testCase = new goog.testing.ContinuationTestCase();
testCase.autoDiscoverTests();
G_testRunner.initialize(testCase);

<|endoftext|>


<commit_before>/** @jsx jsx */

import React from 'react' //eslint-disable-line
import { jsx, Styled, Grid } from 'theme-ui'
import { graphql, Link } from 'gatsby'
import styled from '@emotion/styled'
import Layout from '../components/Layout'
import Container from '../components/Container'
import Img from 'gatsby-image'

import { Tooltip } from 'react-tippy'

const ContentGrid = styled.div`
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  grid-gap: 1em;
  @media screen and (min-width: ${props => props.theme.responsive.small}) {
    grid-template-columns: 6fr 3fr;
    grid-template-rows: 1fr;
  }
  @media screen and (min-width: 1801px) {
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr;
  }
`

const List = styled.ul`
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  margin: 2em auto;
  @media screen and (min-width: ${props => props.theme.responsive.small}) {
    margin: 0 auto;
  }
`

const Posts = ({ data, pageContext, location }) => {
  const photography = data.allContentfulPhotography.edges
  const posts = data.allContentfulPost.edges

  return (
    <Layout location={location.pathname}>
      <Container>
        <ContentGrid>

            <Grid gap={3} rows={'auto'}>
              {photography.map(({ node: photoSet }) => (
                <Link
                  key={photoSet.id}
                  to={`/photography/${photoSet.slug}/`}
                  sx={{
                    color: 'text',
                    textDecoration: 'none',
                    border: '1px solid',
                    bordercolor: 'inherit',
                    padding: [2, 3],
                    borderRadius: '4px',
                  }}
                >
                  <Tooltip
                    // options
                    position="bottom"
                    followCursor="true"
                    html={

                    }
                  >

                  </Tooltip>
                </Link>
              ))}
              <Link
                to={`/photography/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  py: [2, 3],
                }}
              >

              </Link>
            </Grid>

            <Grid gap={3} rows={'auto'}>
              {posts.map(({ node: post }) => (
                <Link
                  key={post.id}
                  to={`/blog/${post.slug}/`}
                  sx={{
                    color: 'text',
                    textDecoration: 'none',
                    border: '1px solid',
                    bordercolor: 'inherit',
                    padding: [2, 3],
                    borderRadius: '4px',
                  }}
                >
                  <Tooltip
                    // options
                    position="bottom"
                    followCursor="true"
                    html={

                    }
                  >

                  </Tooltip>
                </Link>
              ))}
              <Link
                to={`/blog/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',

                  py: [2, 3],
                }}
              >

              </Link>
            </Grid>

            <Grid gap={3} rows={'auto'}>
              {/* <Link
              to={`/resume/`}
              sx={{
                color: 'text',
                textDecoration: 'none',
                border: '1px solid',
                bordercolor: 'inherit',
                padding: [2, 3],
              }}
            >

            </Link> */}
              <Link
                to={`/etc/generative/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  border: '1px solid',
                  bordercolor: 'inherit',
                  padding: [2, 3],
                  borderRadius: '4px',
                }}
              >

              </Link>
              <Link
                to={`/etc/time/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  border: '1px solid',
                  bordercolor: 'inherit',
                  padding: [2, 3],
                  borderRadius: '4px',
                }}
              >

              </Link>
              <Link
                to={`/resources/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  border: '1px solid',
                  bordercolor: 'inherit',
                  padding: [2, 3],
                  borderRadius: '4px',
                }}
              >

              </Link>
            </Grid>

        </ContentGrid>
      </Container>
    </Layout>
  )
}

export const query = graphql`
  query {
    allContentfulPost(sort: { fields: publishDate, order: DESC }, limit: 4) {
      edges {
        node {
          title
          id
          slug
          publishDate(formatString: "MMMM DD, YYYY")
          updatedAt(formatString: "MMMM DD, YYYY")
          heroImage {
            thumbnail: fluid(maxWidth: 400, quality: 50) {
              ...GatsbyContentfulFluid_withWebp
              src
              aspectRatio
            }
          }
          body {
            childMarkdownRemark {
              timeToRead
              html
              excerpt(pruneLength: 80)
            }
          }
        }
      }
    }
    allContentfulPhotography(
      sort: { fields: updatedAt, order: DESC }
      limit: 4
    ) {
      edges {
        node {
          title
          id
          slug
          publishDate(formatString: "MMMM DD, YYYY")
          updatedAt(formatString: "MMMM DD, YYYY")
          heroImage {
            thumbnail: fluid(maxWidth: 400, quality: 50) {
              ...GatsbyContentfulFluid_withWebp
              src
              aspectRatio
            }
          }
        }
      }
    }
  }
`

export default Posts
<commit_msg>grid again<commit_after>/** @jsx jsx */

import React from 'react' //eslint-disable-line
import { jsx, Styled, Grid } from 'theme-ui'
import { graphql, Link } from 'gatsby'
import styled from '@emotion/styled'
import Layout from '../components/Layout'
import Container from '../components/Container'
import Img from 'gatsby-image'

import { Tooltip } from 'react-tippy'

const ContentGrid = styled.div`
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  grid-gap: 1em;
  @media screen and (min-width: ${props => props.theme.responsive.small}) {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
  }
  @media screen and (min-width: 1801px) {
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr;
  }
`

const Posts = ({ data, pageContext, location }) => {
  const photography = data.allContentfulPhotography.edges
  const posts = data.allContentfulPost.edges

  return (
    <Layout location={location.pathname}>
      <Container>
        <ContentGrid>

            <Grid gap={3} rows={'auto'}>
              {photography.map(({ node: photoSet }) => (
                <Link
                  key={photoSet.id}
                  to={`/photography/${photoSet.slug}/`}
                  sx={{
                    color: 'text',
                    textDecoration: 'none',
                    border: '1px solid',
                    bordercolor: 'inherit',
                    padding: [2, 3],
                    borderRadius: '4px',
                  }}
                >
                  <Tooltip
                    // options
                    position="bottom"
                    followCursor="true"
                    html={

                    }
                  >

                  </Tooltip>
                </Link>
              ))}
              <Link
                to={`/photography/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  py: [2, 3],
                }}
              >

              </Link>
            </Grid>

            <Grid gap={3} rows={'auto'}>
              {posts.map(({ node: post }) => (
                <Link
                  key={post.id}
                  to={`/blog/${post.slug}/`}
                  sx={{
                    color: 'text',
                    textDecoration: 'none',
                    border: '1px solid',
                    bordercolor: 'inherit',
                    padding: [2, 3],
                    borderRadius: '4px',
                  }}
                >
                  <Tooltip
                    // options
                    position="bottom"
                    followCursor="true"
                    html={

                    }
                  >

                  </Tooltip>
                </Link>
              ))}
              <Link
                to={`/blog/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',

                  py: [2, 3],
                }}
              >

              </Link>
            </Grid>

            <Grid gap={3} rows={'auto'}>
              {/* <Link
              to={`/resume/`}
              sx={{
                color: 'text',
                textDecoration: 'none',
                border: '1px solid',
                bordercolor: 'inherit',
                padding: [2, 3],
              }}
            >

            </Link> */}
              <Link
                to={`/etc/generative/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  border: '1px solid',
                  bordercolor: 'inherit',
                  padding: [2, 3],
                  borderRadius: '4px',
                }}
              >

              </Link>
              <Link
                to={`/etc/time/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  border: '1px solid',
                  bordercolor: 'inherit',
                  padding: [2, 3],
                  borderRadius: '4px',
                }}
              >

              </Link>
              <Link
                to={`/resources/`}
                sx={{
                  color: 'text',
                  textDecoration: 'none',
                  border: '1px solid',
                  bordercolor: 'inherit',
                  padding: [2, 3],
                  borderRadius: '4px',
                }}
              >

              </Link>
            </Grid>

        </ContentGrid>
      </Container>
    </Layout>
  )
}

export const query = graphql`
  query {
    allContentfulPost(sort: { fields: publishDate, order: DESC }, limit: 4) {
      edges {
        node {
          title
          id
          slug
          publishDate(formatString: "MMMM DD, YYYY")
          updatedAt(formatString: "MMMM DD, YYYY")
          heroImage {
            thumbnail: fluid(maxWidth: 400, quality: 50) {
              ...GatsbyContentfulFluid_withWebp
              src
              aspectRatio
            }
          }
          body {
            childMarkdownRemark {
              timeToRead
              html
              excerpt(pruneLength: 80)
            }
          }
        }
      }
    }
    allContentfulPhotography(
      sort: { fields: updatedAt, order: DESC }
      limit: 4
    ) {
      edges {
        node {
          title
          id
          slug
          publishDate(formatString: "MMMM DD, YYYY")
          updatedAt(formatString: "MMMM DD, YYYY")
          heroImage {
            thumbnail: fluid(maxWidth: 400, quality: 50) {
              ...GatsbyContentfulFluid_withWebp
              src
              aspectRatio
            }
          }
        }
      }
    }
  }
`

export default Posts
<|endoftext|>


<commit_before>'use strict';

var app = angular.module('app', [
  'app.constants',
  'ngWebSocket',
  'LocalStorageModule',
  'app.helpers',
  'pascalprecht.translate',
  'app.filters',
  'app.services',
  'app.directives',
  'app.vis',
  'ngSanitize',
  'ngResource',
  'ui.utils',
  'ui.showhide',
  'ui.validate',
  'ui.bootstrap',
  'ui.bootstrap.tpls'
  ])
  .directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      link: function (scope, ele, attrs) {
        scope.$watch(attrs.dynamic, function(html) {
          ele.html(html);
          $compile(ele.contents())(scope);
        });
      }
    };
  })
  .config(function($tooltipProvider, $httpProvider,
                   $resourceProvider, $translateProvider, DEFAULT_LANG) {

      $translateProvider.preferredLanguage(DEFAULT_LANG);

      $translateProvider.useStaticFilesLoader({
          prefix: './locale/',
          suffix: '.json'
      });
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common["X-Requested-With"];
    //$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
    $tooltipProvider.options({
      appendToBody: true
    });
  })
  // angular-ui config
  .value('ui.config', {
    animate: 'ui-hide',
  })
  // split array displays separates an array inside a textarea with newlines
  .directive('splitArray', function() {
      return {
          restrict: 'A',
          require: 'ngModel',
          link: function(scope, element, attr, ngModel) {

              function fromUser(text) {
                  return text.split("\n");
              }

              function toUser(array) {
                  if (array) {
                    return array.join("\n");
                  }
              }

              ngModel.$parsers.push(fromUser);
              ngModel.$formatters.push(toUser);
          }
      };
  })
  .factory('DataStream', [
    '$websocket',
    '$rootScope',
    '$interval',
    '$window',
    'Messages',
    function($websocket, $rootScope, $interval, $window, Messages) {

      var WS_RECONNECT_INTERVAL = 5000;
      var WS_RETRY_COUNT        = 0;

      var ds = $websocket('ws://' + document.location.host + '/data');

      // Register if the user navigated away, so we don't try to connect to the UI.
      // Also, force closing the websocket
      var userDidLeave = false;
      $window.onbeforeunload = function() {
        ds.close(true);
        userDidLeave = true;
      };

      ds.onMessage(function(raw) {
        var envelope = JSON.parse(raw.data);
        if (typeof Messages[envelope.Type] != 'undefined') {
          Messages[envelope.Type].call(this, envelope.Message);
        } else {
          console.log('Got unknown message type: ' + envelope.Type);
        };
      });

      ds.onOpen(function(msg) {
        $rootScope.wsConnected = true;
        WS_RETRY_COUNT = 0;
        $rootScope.backendIsGone = false;
        $rootScope.wsLastConnectedAt = new Date();
        console.log("New websocket instance created " + msg);
      });

      ds.onClose(function(msg) {
        $rootScope.wsConnected = false;

        console.log("This websocket instance closed " + msg);

        // If the user left, then don't try to reconnect. Causes a known bug lantern-#2721
        // where some browsers will reconnect when navigating away, returning to Lantern
        // home page
        if (userDidLeave) {
          return;
        }

        // try to reconnect indefinitely
        // when the websocket closes
        $interval(function() {
          console.log("Trying to reconnect to disconnected websocket");
          ds = $websocket('ws://' + document.location.host + '/data');
          ds.onOpen(function(msg) {
            $window.location.reload();
          });
          if (ds.readyState == 0) {
            ds.close(true);
          }
        }, WS_RECONNECT_INTERVAL);
      });

      ds.onError(function(msg) {
        console.log("Error on this websocket instance " + msg);
        //ds.close(true);
        //ds.flush();
      });

      var methods = {
        'send': function(messageType, data) {
          console.log('request to send.');
          ds.send(JSON.stringify({'Type': messageType, 'Message': data}))
        }
      };

      return methods;
    }
  ])
  .factory('ProxiedSites', ['$window', '$rootScope', 'DataStream', function($window, $rootScope, DataStream) {

      var methods = {
        update: function() {
          console.log('UPDATE');
          // dataStream.send(JSON.stringify($rootScope.updates));
          DataStream.send('ProxiedSites', $rootScope.updates)
        },
        get: function() {
          console.log('GET');
          // dataStream.send(JSON.stringify({ action: 'get' }));
          DataStream.send('ProxiedSites', {'action': 'get'});
        }
      };

      return methods;
  }])
  .run(function ($filter, $log, $rootScope, $timeout, $window, $websocket,
                 $translate, $http, apiSrvc, gaMgr, modelSrvc, ENUMS, EXTERNAL_URL, MODAL, CONTACT_FORM_MAXLEN) {

    var CONNECTIVITY = ENUMS.CONNECTIVITY,
        MODE = ENUMS.MODE,
        jsonFltr = $filter('json'),
        model = modelSrvc.model,
        prettyUserFltr = $filter('prettyUser'),
        reportedStateFltr = $filter('reportedState');

    // for easier inspection in the JavaScript console
    $window.rootScope = $rootScope;
    $window.model = model;

    $rootScope.EXTERNAL_URL = EXTERNAL_URL;

    $rootScope.model = model;
    $rootScope.DEFAULT_AVATAR_URL = 'img/default-avatar.png';
    $rootScope.CONTACT_FORM_MAXLEN = CONTACT_FORM_MAXLEN;

    angular.forEach(ENUMS, function(val, key) {
      $rootScope[key] = val;
    });

    $rootScope.reload = function () {
      location.reload(true); // true to bypass cache and force request to server
    };

    $rootScope.switchLang = function (lang) {
        $rootScope.lang = lang;
        $translate.use(lang);
    };

    $rootScope.trackPageView = function() {
        gaMgr.trackPageView('start');
    };

    $rootScope.valByLang = function(name) {
        // use language-specific forums URL
        if (name && $rootScope.lang &&
            name.hasOwnProperty($rootScope.lang)) {
            return name[$rootScope.lang];
        }
        // default to English language forum
        return name['en_US'];
    };

    $rootScope.changeLang = function(lang) {
      return $rootScope.interaction(INTERACTION.changeLang, {lang: lang});
    };

    $rootScope.openRouterConfig = function() {
      return $rootScope.interaction(INTERACTION.routerConfig);
    };

    $rootScope.openExternal = function(url) {
      return $window.open(url);
    };

    $rootScope.resetContactForm = function (scope) {
      if (scope.show) {
        var reportedState = jsonFltr(reportedStateFltr(model));
        scope.diagnosticInfo = reportedState;
      }
    };

    $rootScope.interactionWithNotify = function (interactionid, scope, reloadAfter) {
      var extra;
      if (scope.notify) {
        var diagnosticInfo = scope.diagnosticInfo;
        if (diagnosticInfo) {
          try {
            diagnosticInfo = angular.fromJson(diagnosticInfo);
          } catch (e) {
            $log.debug('JSON decode diagnosticInfo', diagnosticInfo, 'failed, passing as-is');
          }
        }
        extra = {
          context: model.modal,
          message: scope.message,
          diagnosticInfo: diagnosticInfo
        };
      }
      $rootScope.interaction(interactionid, extra).then(function () {
        if (reloadAfter) $rootScope.reload();
      });
    };

    $rootScope.backendIsGone = false;
    $rootScope.$watch("wsConnected", function(wsConnected) {
      var MILLIS_UNTIL_BACKEND_CONSIDERED_GONE = 10000;
      if (!wsConnected) {
        // In 11 seconds, check if we're still not connected
        $timeout(function() {
          var lastConnectedAt = $rootScope.wsLastConnectedAt;
          if (lastConnectedAt) {
            var timeSinceLastConnected = new Date().getTime() - lastConnectedAt.getTime();
            $log.debug("Time since last connect", timeSinceLastConnected);
            if (timeSinceLastConnected > MILLIS_UNTIL_BACKEND_CONSIDERED_GONE) {
              // If it's been more than 10 seconds since we last connect,
              // treat the backend as gone
              console.log("Backend is gone");
              $rootScope.backendIsGone = true;
            } else {
              $rootScope.backendIsGone = false;
            }
          }
        }, MILLIS_UNTIL_BACKEND_CONSIDERED_GONE + 1);
      }
    });
  });
<commit_msg>Possible workaround for #2799<commit_after>'use strict';

var app = angular.module('app', [
  'app.constants',
  'ngWebSocket',
  'LocalStorageModule',
  'app.helpers',
  'pascalprecht.translate',
  'app.filters',
  'app.services',
  'app.directives',
  'app.vis',
  'ngSanitize',
  'ngResource',
  'ui.utils',
  'ui.showhide',
  'ui.validate',
  'ui.bootstrap',
  'ui.bootstrap.tpls'
  ])
  .directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      link: function (scope, ele, attrs) {
        scope.$watch(attrs.dynamic, function(html) {
          ele.html(html);
          $compile(ele.contents())(scope);
        });
      }
    };
  })
  .config(function($tooltipProvider, $httpProvider,
                   $resourceProvider, $translateProvider, DEFAULT_LANG) {

      $translateProvider.preferredLanguage(DEFAULT_LANG);

      $translateProvider.useStaticFilesLoader({
          prefix: './locale/',
          suffix: '.json'
      });
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common["X-Requested-With"];
    //$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
    $tooltipProvider.options({
      appendToBody: true
    });
  })
  // angular-ui config
  .value('ui.config', {
    animate: 'ui-hide',
  })
  // split array displays separates an array inside a textarea with newlines
  .directive('splitArray', function() {
      return {
          restrict: 'A',
          require: 'ngModel',
          link: function(scope, element, attr, ngModel) {

              function fromUser(text) {
                  return text.split("\n");
              }

              function toUser(array) {
                  if (array) {
                    return array.join("\n");
                  }
              }

              ngModel.$parsers.push(fromUser);
              ngModel.$formatters.push(toUser);
          }
      };
  })
  .factory('DataStream', [
    '$websocket',
    '$rootScope',
    '$interval',
    '$window',
    'Messages',
    function($websocket, $rootScope, $interval, $window, Messages) {

      var WS_RECONNECT_INTERVAL = 5000;
      var WS_RETRY_COUNT        = 0;

      var ds = $websocket('ws://' + document.location.host + '/data');

      // Register if the user navigated away, so we don't try to connect to the UI.
      // Also, force closing the websocket
      var userDidLeave = false;
      $window.onbeforeunload = function() {
        ds.close(true);
        userDidLeave = true;
      };

      ds.onMessage(function(raw) {
        var envelope = JSON.parse(raw.data);
        if (typeof Messages[envelope.Type] != 'undefined') {
          Messages[envelope.Type].call(this, envelope.Message);
        } else {
          console.log('Got unknown message type: ' + envelope.Type);
        };
      });

      ds.onOpen(function(msg) {
        $rootScope.wsConnected = true;
        WS_RETRY_COUNT = 0;
        $rootScope.backendIsGone = false;
        $rootScope.wsLastConnectedAt = new Date();
        console.log("New websocket instance created " + msg);
      });

      ds.onClose(function(msg) {
        $rootScope.wsConnected = false;

        console.log("This websocket instance closed " + msg);

        // If the user left, then don't try to reconnect. Causes a known bug lantern-#2721
        // where some browsers will reconnect when navigating away, returning to Lantern
        // home page
        if (userDidLeave) {
          return;
        }

        // try to reconnect indefinitely
        // when the websocket closes
        $interval(function() {
          console.log("Trying to reconnect to disconnected websocket");
          ds = $websocket('ws://' + document.location.host + '/data');
          ds.onOpen(function(msg) {
            $window.location.reload();
          });
          setTimeout(function() {
            if (ds.readyState != 1) {
              ds.close(true);
            }
          }, 100)
        }, WS_RECONNECT_INTERVAL);
      });

      ds.onError(function(msg) {
        console.log("Error on this websocket instance " + msg);
        ds.close(true);
      });

      var methods = {
        'send': function(messageType, data) {
          console.log('request to send.');
          ds.send(JSON.stringify({'Type': messageType, 'Message': data}))
        }
      };

      return methods;
    }
  ])
  .factory('ProxiedSites', ['$window', '$rootScope', 'DataStream', function($window, $rootScope, DataStream) {

      var methods = {
        update: function() {
          console.log('UPDATE');
          // dataStream.send(JSON.stringify($rootScope.updates));
          DataStream.send('ProxiedSites', $rootScope.updates)
        },
        get: function() {
          console.log('GET');
          // dataStream.send(JSON.stringify({ action: 'get' }));
          DataStream.send('ProxiedSites', {'action': 'get'});
        }
      };

      return methods;
  }])
  .run(function ($filter, $log, $rootScope, $timeout, $window, $websocket,
                 $translate, $http, apiSrvc, gaMgr, modelSrvc, ENUMS, EXTERNAL_URL, MODAL, CONTACT_FORM_MAXLEN) {

    var CONNECTIVITY = ENUMS.CONNECTIVITY,
        MODE = ENUMS.MODE,
        jsonFltr = $filter('json'),
        model = modelSrvc.model,
        prettyUserFltr = $filter('prettyUser'),
        reportedStateFltr = $filter('reportedState');

    // for easier inspection in the JavaScript console
    $window.rootScope = $rootScope;
    $window.model = model;

    $rootScope.EXTERNAL_URL = EXTERNAL_URL;

    $rootScope.model = model;
    $rootScope.DEFAULT_AVATAR_URL = 'img/default-avatar.png';
    $rootScope.CONTACT_FORM_MAXLEN = CONTACT_FORM_MAXLEN;

    angular.forEach(ENUMS, function(val, key) {
      $rootScope[key] = val;
    });

    $rootScope.reload = function () {
      location.reload(true); // true to bypass cache and force request to server
    };

    $rootScope.switchLang = function (lang) {
        $rootScope.lang = lang;
        $translate.use(lang);
    };

    $rootScope.trackPageView = function() {
        gaMgr.trackPageView('start');
    };

    $rootScope.valByLang = function(name) {
        // use language-specific forums URL
        if (name && $rootScope.lang &&
            name.hasOwnProperty($rootScope.lang)) {
            return name[$rootScope.lang];
        }
        // default to English language forum
        return name['en_US'];
    };

    $rootScope.changeLang = function(lang) {
      return $rootScope.interaction(INTERACTION.changeLang, {lang: lang});
    };

    $rootScope.openRouterConfig = function() {
      return $rootScope.interaction(INTERACTION.routerConfig);
    };

    $rootScope.openExternal = function(url) {
      return $window.open(url);
    };

    $rootScope.resetContactForm = function (scope) {
      if (scope.show) {
        var reportedState = jsonFltr(reportedStateFltr(model));
        scope.diagnosticInfo = reportedState;
      }
    };

    $rootScope.interactionWithNotify = function (interactionid, scope, reloadAfter) {
      var extra;
      if (scope.notify) {
        var diagnosticInfo = scope.diagnosticInfo;
        if (diagnosticInfo) {
          try {
            diagnosticInfo = angular.fromJson(diagnosticInfo);
          } catch (e) {
            $log.debug('JSON decode diagnosticInfo', diagnosticInfo, 'failed, passing as-is');
          }
        }
        extra = {
          context: model.modal,
          message: scope.message,
          diagnosticInfo: diagnosticInfo
        };
      }
      $rootScope.interaction(interactionid, extra).then(function () {
        if (reloadAfter) $rootScope.reload();
      });
    };

    $rootScope.backendIsGone = false;
    $rootScope.$watch("wsConnected", function(wsConnected) {
      var MILLIS_UNTIL_BACKEND_CONSIDERED_GONE = 10000;
      if (!wsConnected) {
        // In 11 seconds, check if we're still not connected
        $timeout(function() {
          var lastConnectedAt = $rootScope.wsLastConnectedAt;
          if (lastConnectedAt) {
            var timeSinceLastConnected = new Date().getTime() - lastConnectedAt.getTime();
            $log.debug("Time since last connect", timeSinceLastConnected);
            if (timeSinceLastConnected > MILLIS_UNTIL_BACKEND_CONSIDERED_GONE) {
              // If it's been more than 10 seconds since we last connect,
              // treat the backend as gone
              console.log("Backend is gone");
              $rootScope.backendIsGone = true;
            } else {
              $rootScope.backendIsGone = false;
            }
          }
        }, MILLIS_UNTIL_BACKEND_CONSIDERED_GONE + 1);
      }
    });
  });
<|endoftext|>


<commit_before>// @flow

import ContactHub from '../src/ContactHub';
import Customer from '../src/Customer';
import nock from 'nock';

const auth = {
  token: 'token',
  workspaceId: 'wid',
  nodeId: 'nid'
};

const ch = new ContactHub(auth);

const apiUrl = 'https://api.contactlab.it/hub/v1';

describe('ContactHub', () => {
  describe('constructor', () => {
    it('throws if required params are missing', () => {
      const wrongCall = () => {
        // $ExpectError
        new ContactHub(); // eslint-disable-line
      };
      expect(wrongCall).toThrow();
    });
  });

  describe('getCustomer', () => {
    const customer = {
      id: 'foo'
    };

    it('finds an existing Customer', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers/${customer.id}`)
        .query({ nodeId: auth.nodeId })
        .reply(200, customer);

      const res = await ch.getCustomer(customer.id);
      expect(res.id).toEqual('foo');
    });
  });

  describe('getCustomers', () => {
    const exampleQuery = {
      name: '',
      query: {
        name: 'mario',
        type: 'simple',
        are: {
          condition: {
            type: 'atomic',
            attribute: 'base.firstName',
            operator: 'EQUALS',
            value: 'Mario'
          }
        }
      }
    };

    it('works with no parameters', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId })
        .reply(200, {
          elements: [{ id: 'no-filters' }]
        });

      const res = await ch.getCustomers();
      expect(res[0].id).toBe('no-filters');
    });

    it('takes an externalId as a filter', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, externalId: 'ext123' })
        .reply(200, {
          elements: [{ id: 'by-extid' }]
        });

      const res = await ch.getCustomers({ externalId: 'ext123' });
      expect(res[0].id).toBe('by-extid');
    });

    it('takes a list of fields to retrieve', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, fields: 'base.firstName,base.lastName' })
        .reply(200, {
          elements: [{ id: 'with-fields' }]
        });

      const res = await ch.getCustomers({
        fields: ['base.firstName', 'base.lastName']
      });
      expect(res[0].id).toBe('with-fields');
    });

    it('takes a query object as a filter', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, query: JSON.stringify(exampleQuery) })
        .reply(200, {
          elements: [{ id: 'by-query' }]
        });

      const res = await ch.getCustomers({ query: exampleQuery });
      expect(res[0].id).toBe('by-query');
    });

    it('takes a field to sort by', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, sort: 'base.firstName' })
        .reply(200, {
          elements: [{ id: 'sorted' }]
        });

      const res = await ch.getCustomers({ sort: 'base.firstName' });
      expect(res[0].id).toBe('sorted');
    });

    it('takes a sort field and a sort direction', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, sort: 'base.firstName,desc' })
        .reply(200, {
          elements: [{ id: 'sorted-desc' }]
        });

      const res = await ch.getCustomers({
        sort: 'base.firstName',
        direction: 'desc'
      });
      expect(res[0].id).toBe('sorted-desc');
    });

    it('takes all params together', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({
          nodeId: auth.nodeId,
          externalId: 'ext123',
          fields: 'base.firstName',
          query: JSON.stringify(exampleQuery),
          sort: 'base.firstName,asc'
        })
        .reply(200, {
          elements: [{ id: 'all-params' }]
        });

      const res = await ch.getCustomers({
        externalId: 'ext123',
        fields: ['base.firstName'],
        query: exampleQuery,
        sort: 'base.firstName',
        direction: 'asc'
      });
      expect(res[0].id).toBe('all-params');
    });
  });

  describe('addCustomer', () => {
    it('creates a new Customer', () => {
      const customer = new Customer({
        base: {
          firstName: 'Mario'
        }
      });

      nock(apiUrl)
        .post(`/workspaces/${auth.workspaceId}/customers`)
        .reply(200, { id: 'new-cid' });

      return ch.addCustomer(customer).then(res => {
        expect(res.id).toBe('new-cid');
      });
    });
  });

  describe('updateCustomer', () => {
    it('updates an existing Customer', async () => {
      const customer = new Customer({
        id: 'existing-id',
        base: {
          lastName: 'Rossi'
        }
      });

      nock(apiUrl)
        .put(`/workspaces/${auth.workspaceId}/customers/${customer.id}`)
        .reply(200, customer);

      const res = await ch.updateCustomer(customer);
      expect(res.base.lastName).toBe('Rossi');
    });
  });

  describe('patchCustomer', () => {
    it('updates an existing Customer', async () => {
      const customerId = 'existing-id';
      const customer = new Customer({
        base: { lastName: 'Rossi' }
      });

      nock(apiUrl)
        .patch(`/workspaces/${auth.workspaceId}/customers/${customerId}`)
        .reply(200, customer);

      const res = await ch.patchCustomer(customerId, customer);
      expect(res.base.lastName).toBe('Rossi');
    });
  });

  describe('deleteCustomer', () => {
    it('deletes an existing Customer', async () => {
      const customerId = 'existing-cid';

      nock(apiUrl)
        .delete(`/workspaces/${auth.workspaceId}/customers/${customerId}`)
        .query({ nodeId: auth.nodeId })
        .reply(200);

      const res = await ch.deleteCustomer('existing-cid');
      expect(res).toEqual({ deleted: true });
    });
  });
});
<commit_msg>More specific eslint override<commit_after>// @flow

import ContactHub from '../src/ContactHub';
import Customer from '../src/Customer';
import nock from 'nock';

const auth = {
  token: 'token',
  workspaceId: 'wid',
  nodeId: 'nid'
};

const ch = new ContactHub(auth);

const apiUrl = 'https://api.contactlab.it/hub/v1';

describe('ContactHub', () => {
  describe('constructor', () => {
    it('throws if required params are missing', () => {
      const wrongCall = () => {
        // $ExpectError
        new ContactHub(); // eslint-disable-line no-new
      };
      expect(wrongCall).toThrow();
    });
  });

  describe('getCustomer', () => {
    const customer = {
      id: 'foo'
    };

    it('finds an existing Customer', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers/${customer.id}`)
        .query({ nodeId: auth.nodeId })
        .reply(200, customer);

      const res = await ch.getCustomer(customer.id);
      expect(res.id).toEqual('foo');
    });
  });

  describe('getCustomers', () => {
    const exampleQuery = {
      name: '',
      query: {
        name: 'mario',
        type: 'simple',
        are: {
          condition: {
            type: 'atomic',
            attribute: 'base.firstName',
            operator: 'EQUALS',
            value: 'Mario'
          }
        }
      }
    };

    it('works with no parameters', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId })
        .reply(200, {
          elements: [{ id: 'no-filters' }]
        });

      const res = await ch.getCustomers();
      expect(res[0].id).toBe('no-filters');
    });

    it('takes an externalId as a filter', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, externalId: 'ext123' })
        .reply(200, {
          elements: [{ id: 'by-extid' }]
        });

      const res = await ch.getCustomers({ externalId: 'ext123' });
      expect(res[0].id).toBe('by-extid');
    });

    it('takes a list of fields to retrieve', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, fields: 'base.firstName,base.lastName' })
        .reply(200, {
          elements: [{ id: 'with-fields' }]
        });

      const res = await ch.getCustomers({
        fields: ['base.firstName', 'base.lastName']
      });
      expect(res[0].id).toBe('with-fields');
    });

    it('takes a query object as a filter', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, query: JSON.stringify(exampleQuery) })
        .reply(200, {
          elements: [{ id: 'by-query' }]
        });

      const res = await ch.getCustomers({ query: exampleQuery });
      expect(res[0].id).toBe('by-query');
    });

    it('takes a field to sort by', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, sort: 'base.firstName' })
        .reply(200, {
          elements: [{ id: 'sorted' }]
        });

      const res = await ch.getCustomers({ sort: 'base.firstName' });
      expect(res[0].id).toBe('sorted');
    });

    it('takes a sort field and a sort direction', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({ nodeId: auth.nodeId, sort: 'base.firstName,desc' })
        .reply(200, {
          elements: [{ id: 'sorted-desc' }]
        });

      const res = await ch.getCustomers({
        sort: 'base.firstName',
        direction: 'desc'
      });
      expect(res[0].id).toBe('sorted-desc');
    });

    it('takes all params together', async () => {
      nock(apiUrl)
        .get(`/workspaces/${auth.workspaceId}/customers`)
        .query({
          nodeId: auth.nodeId,
          externalId: 'ext123',
          fields: 'base.firstName',
          query: JSON.stringify(exampleQuery),
          sort: 'base.firstName,asc'
        })
        .reply(200, {
          elements: [{ id: 'all-params' }]
        });

      const res = await ch.getCustomers({
        externalId: 'ext123',
        fields: ['base.firstName'],
        query: exampleQuery,
        sort: 'base.firstName',
        direction: 'asc'
      });
      expect(res[0].id).toBe('all-params');
    });
  });

  describe('addCustomer', () => {
    it('creates a new Customer', () => {
      const customer = new Customer({
        base: {
          firstName: 'Mario'
        }
      });

      nock(apiUrl)
        .post(`/workspaces/${auth.workspaceId}/customers`)
        .reply(200, { id: 'new-cid' });

      return ch.addCustomer(customer).then(res => {
        expect(res.id).toBe('new-cid');
      });
    });
  });

  describe('updateCustomer', () => {
    it('updates an existing Customer', async () => {
      const customer = new Customer({
        id: 'existing-id',
        base: {
          lastName: 'Rossi'
        }
      });

      nock(apiUrl)
        .put(`/workspaces/${auth.workspaceId}/customers/${customer.id}`)
        .reply(200, customer);

      const res = await ch.updateCustomer(customer);
      expect(res.base.lastName).toBe('Rossi');
    });
  });

  describe('patchCustomer', () => {
    it('updates an existing Customer', async () => {
      const customerId = 'existing-id';
      const customer = new Customer({
        base: { lastName: 'Rossi' }
      });

      nock(apiUrl)
        .patch(`/workspaces/${auth.workspaceId}/customers/${customerId}`)
        .reply(200, customer);

      const res = await ch.patchCustomer(customerId, customer);
      expect(res.base.lastName).toBe('Rossi');
    });
  });

  describe('deleteCustomer', () => {
    it('deletes an existing Customer', async () => {
      const customerId = 'existing-cid';

      nock(apiUrl)
        .delete(`/workspaces/${auth.workspaceId}/customers/${customerId}`)
        .query({ nodeId: auth.nodeId })
        .reply(200);

      const res = await ch.deleteCustomer('existing-cid');
      expect(res).toEqual({ deleted: true });
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import $C from '../deps/collection';
import Parser from './constructor';
import { ws } from '../helpers/string';
import { parentLink } from '../consts/regs';
import { LEFT_BLOCK, RIGHT_BLOCK, ADV_LEFT_BLOCK } from '../consts/literals';

const
	escapeEqRgxp = /===|==|([\\]+)=/g,
	escapeOrRgxp = /\|\||([\\]+)\|/g;

const
	unEscapeEqRgxp = /__SNAKESKIN_EQ__(\d+)_(\d+)_/g,
	unEscapeOrRgxp = /__SNAKESKIN_OR__(\d+)_(\d+)_/g;

function escapeEq(sstr, $1) {
	if ($1 && $1.length % 2 === 0) {
		return sstr;
	}

	return `__SNAKESKIN_EQ__${sstr.split('=').length}_${$1.length}_`;
}

function escapeOr(sstr, $1) {
	if ($1 && $1.length % 2 === 0) {
		return sstr;
	}

	return `__SNAKESKIN_OR__${sstr.split('|').length}_${$1.length}_`;
}

function unEscapeEq(ignore, $1, $2) {
	return new Array(Number($2)).join('\\') + new Array(Number($1)).join('=');
}

function unEscapeOr(ignore, $1, $2) {
	return new Array(Number($2)).join('\\') + new Array(Number($1)).join('|');
}

/**
 * Returns string declaration of XML attribute
 *
 * @param {string} str - source string
 * @param {?string=} [opt_group] - group name
 * @param {?string=} [opt_separator='-'] - group separator
 * @param {?boolean=} [opt_classLink=false] - if is true, then a value of a class attribute
 *   will be saved to a variable
 *
 * @return {string}
 */
Parser.prototype.returnXMLAttrDecl = function (str, opt_group, opt_separator, opt_classLink) {
	const
		{attr, attrEscape} = this;

	this.attr = true;
	this.attrEscape = true;

	opt_group = opt_group || '';
	opt_separator = opt_separator || '-';

	str = str
		.replace(escapeHTMLRgxp, escapeHTML)
		.replace(escapeOrRgxp, escapeOr);

	const
		parts = str.split('|'),
		ref = this.bemRef;

	const
		s = ADV_LEFT_BLOCK + LEFT_BLOCK,
		e = RIGHT_BLOCK;

	const res = $C(parts).reduce((res, el) => {
		el = el
			.replace(unEscapeOrRgxp, unEscapeOr)
			.replace(escapeEqRgxp, escapeEq);

		const
			arg = el.split('=');

		let
			empty = arg.length !== 2;

		if (empty) {
			if (this.doctype === 'xml') {
				arg[1] = arg[0];
				empty = false;

			} else {
				arg[1] = '';
			}
		}

		arg[0] = arg[0].trim().replace(unEscapeEqRgxp, unEscapeEq);
		arg[1] = arg[1].trim().replace(unEscapeEqRgxp, unEscapeEq);
		res += ws`
			var __ATTR_STR__ = \'\',
				__ATTR_J__ = 0;
		`;

		if (opt_group) {
			arg[0] = opt_group + opt_separator + arg[0];

		} else {
			arg[0] = arg[0][0] === '-' ? `data-${arg[0].slice(1)}` : arg[0];
		}

		res += $C(this.splitBySpace(arg[1])).reduce((val) => {
			val = val.trim();

			if (parentLink.test(val) && ref) {
				val = `${s}'${ref}'${FILTER}${this.bemFilter} '${val.slice('&'.length)}',$0${e}`;
				val = this.pasteDangerBlocks(this.replaceTplVars(val));
			}

			val = `'${this.pasteTplVarBlocks(val)}'`;
			return res += ws`
				if ((${val}) != null && (${val}) !== '') {
					__ATTR_STR__ += __ATTR_J__ ? ' ' + ${val} : ${val};
					__ATTR_J__++;
				}
			`;

		}, 'res');

		arg[0] = `'${this.pasteTplVarBlocks(arg[0])}'`;
		res += `if ((${arg[0]}) != null && (${arg[0]}) != '') {`;

		const tmp = ws`
			if (__NODE__) {
				__NODE__.setAttribute(${arg[0]}, ${empty} ? ${arg[0]} : __ATTR_STR__ );

			} else {
				${this.wrap(`' ' + ${arg[0]} + (${empty} ? '' : '="' + __ATTR_STR__ + '"')`)}
			}
		`;

		if (opt_classLink) {
			res += ws`
				if (__ATTR_TMP__[(${arg[0]})] != null) {
					__ATTR_TMP__[(${arg[0]})] += __ATTR_STR__;

				} else {
					${tmp}
				}
			`;

		} else {
			res += tmp;
		}

		return res += '}';

	}, '');

	this.attr = attr;
	this.attrEscape = attrEscape;

	return res;
};

/**
 * Splits a string of attribute declaration into groups
 *
 * @param {string} str - source string
 * @return {!Array<{attr: string, group: ?string, separator: ?string}>}
 */
Parser.prototype.splitXMLAttrGroup = function (str) {
	const
		rAttr = this.attr,
		rEscape = this.attrEscape;

	this.attr = true;
	this.attrEscape = true;

	str = this.replaceTplVars(str, {replace: true});

	const
		groups = [];

	let
		group = '',
		attr = '',
		sep = '';

	const separator = {
		'-': true,
		':': true,
		'_': true
	};

	let pOpen = 0;
	for (let i = 0; i < str.length; i++) {
		let
			el = str[i],
			next = str[i + 1];

		if (!pOpen) {
			if (separator[el] && next === '(') {
				pOpen++;
				i++;
				sep = el;
				continue;
			}

			if (el === '(') {
				pOpen++;
				sep = '';
				continue;
			}
		}

		if (pOpen) {
			if (el === '(') {
				pOpen++;

			} else if (el === ')') {
				pOpen--;

				if (!pOpen) {
					groups.push({
						attr: attr.trim(),
						group: Snakeskin.Filters.html(group, true).trim(),
						separator: sep
					});

					group = '';
					attr = '';
					sep = '';

					i++;
					continue;
				}
			}
		}

		if (!pOpen) {
			group += el;

		} else {
			attr += el;
		}
	}

	if (group && !attr) {
		groups.push({
			attr: group.trim(),
			group: null,
			separator: null
		});
	}

	this.attr = rAttr;
	this.attrEscape = rEscape;

	return groups;
};
<commit_msg>:wrench:<commit_after>'use strict';

/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import $C from '../deps/collection';
import Parser from './constructor';
import { ws } from '../helpers/string';
import { parentLink } from '../consts/regs';
import { LEFT_BLOCK, RIGHT_BLOCK, ADV_LEFT_BLOCK } from '../consts/literals';
import { escapeHTMLRgxp, escapeHTML } from '../live/filters';

const
	escapeEqRgxp = /===|==|([\\]+)=/g,
	escapeOrRgxp = /\|\||([\\]+)\|/g;

const
	unEscapeEqRgxp = /__SNAKESKIN_EQ__(\d+)_(\d+)_/g,
	unEscapeOrRgxp = /__SNAKESKIN_OR__(\d+)_(\d+)_/g;

function escapeEq(sstr, $1) {
	if ($1 && $1.length % 2 === 0) {
		return sstr;
	}

	return `__SNAKESKIN_EQ__${sstr.split('=').length}_${$1.length}_`;
}

function escapeOr(sstr, $1) {
	if ($1 && $1.length % 2 === 0) {
		return sstr;
	}

	return `__SNAKESKIN_OR__${sstr.split('|').length}_${$1.length}_`;
}

function unEscapeEq(ignore, $1, $2) {
	return new Array(Number($2)).join('\\') + new Array(Number($1)).join('=');
}

function unEscapeOr(ignore, $1, $2) {
	return new Array(Number($2)).join('\\') + new Array(Number($1)).join('|');
}

/**
 * Returns string declaration of XML attribute
 *
 * @param {string} str - source string
 * @param {?string=} [opt_group] - group name
 * @param {?string=} [opt_separator='-'] - group separator
 * @param {?boolean=} [opt_classLink=false] - if is true, then a value of a class attribute
 *   will be saved to a variable
 *
 * @return {string}
 */
Parser.prototype.returnXMLAttrDecl = function (str, opt_group, opt_separator, opt_classLink) {
	const
		{attr, attrEscape} = this;

	this.attr = true;
	this.attrEscape = true;

	opt_group = opt_group || '';
	opt_separator = opt_separator || '-';

	str = str
		.replace(escapeHTMLRgxp, escapeHTML)
		.replace(escapeOrRgxp, escapeOr);

	const
		parts = str.split('|'),
		ref = this.bemRef;

	const
		s = ADV_LEFT_BLOCK + LEFT_BLOCK,
		e = RIGHT_BLOCK;

	const res = $C(parts).reduce((res, el) => {
		el = el
			.replace(unEscapeOrRgxp, unEscapeOr)
			.replace(escapeEqRgxp, escapeEq);

		const
			arg = el.split('=');

		let
			empty = arg.length !== 2;

		if (empty) {
			if (this.doctype === 'xml') {
				arg[1] = arg[0];
				empty = false;

			} else {
				arg[1] = '';
			}
		}

		arg[0] = arg[0].trim().replace(unEscapeEqRgxp, unEscapeEq);
		arg[1] = arg[1].trim().replace(unEscapeEqRgxp, unEscapeEq);
		res += ws`
			var __ATTR_STR__ = \'\',
				__ATTR_J__ = 0;
		`;

		if (opt_group) {
			arg[0] = opt_group + opt_separator + arg[0];

		} else {
			arg[0] = arg[0][0] === '-' ? `data-${arg[0].slice(1)}` : arg[0];
		}

		res += $C(this.splitBySpace(arg[1])).reduce((val) => {
			val = val.trim();

			if (parentLink.test(val) && ref) {
				val = `${s}'${ref}'${FILTER}${this.bemFilter} '${val.slice('&'.length)}',$0${e}`;
				val = this.pasteDangerBlocks(this.replaceTplVars(val));
			}

			val = `'${this.pasteTplVarBlocks(val)}'`;
			return res += ws`
				if ((${val}) != null && (${val}) !== '') {
					__ATTR_STR__ += __ATTR_J__ ? ' ' + ${val} : ${val};
					__ATTR_J__++;
				}
			`;

		}, 'res');

		arg[0] = `'${this.pasteTplVarBlocks(arg[0])}'`;
		res += `if ((${arg[0]}) != null && (${arg[0]}) != '') {`;

		const tmp = ws`
			if (__NODE__) {
				__NODE__.setAttribute(${arg[0]}, ${empty} ? ${arg[0]} : __ATTR_STR__ );

			} else {
				${this.wrap(`' ' + ${arg[0]} + (${empty} ? '' : '="' + __ATTR_STR__ + '"')`)}
			}
		`;

		if (opt_classLink) {
			res += ws`
				if (__ATTR_TMP__[(${arg[0]})] != null) {
					__ATTR_TMP__[(${arg[0]})] += __ATTR_STR__;

				} else {
					${tmp}
				}
			`;

		} else {
			res += tmp;
		}

		return res += '}';

	}, '');

	this.attr = attr;
	this.attrEscape = attrEscape;

	return res;
};

/**
 * Splits a string of attribute declaration into groups
 *
 * @param {string} str - source string
 * @return {!Array<{attr: string, group: ?string, separator: ?string}>}
 */
Parser.prototype.splitXMLAttrGroup = function (str) {
	const
		rAttr = this.attr,
		rEscape = this.attrEscape;

	this.attr = true;
	this.attrEscape = true;

	str = this.replaceTplVars(str, {replace: true});

	const
		groups = [];

	let
		group = '',
		attr = '',
		sep = '';

	const separator = {
		'-': true,
		':': true,
		'_': true
	};

	let pOpen = 0;
	for (let i = 0; i < str.length; i++) {
		let
			el = str[i],
			next = str[i + 1];

		if (!pOpen) {
			if (separator[el] && next === '(') {
				pOpen++;
				i++;
				sep = el;
				continue;
			}

			if (el === '(') {
				pOpen++;
				sep = '';
				continue;
			}
		}

		if (pOpen) {
			if (el === '(') {
				pOpen++;

			} else if (el === ')') {
				pOpen--;

				if (!pOpen) {
					groups.push({
						attr: attr.trim(),
						group: Snakeskin.Filters.html(group, true).trim(),
						separator: sep
					});

					group = '';
					attr = '';
					sep = '';

					i++;
					continue;
				}
			}
		}

		if (!pOpen) {
			group += el;

		} else {
			attr += el;
		}
	}

	if (group && !attr) {
		groups.push({
			attr: group.trim(),
			group: null,
			separator: null
		});
	}

	this.attr = rAttr;
	this.attrEscape = rEscape;

	return groups;
};
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var parse = require('../src/commands/parse');

describe('YAML-to-commands parser', () => {
  var filename = 'test/test.usher.yml';
  var result = parse(filename);
  var tests = [
    {
      key: 'basic',
      expected: 'docker build -t usher .'
    },
    {
      key: 'custom',
      expected: 'do thing'
    },
    {
      key: 'sequence',
      expected: [
        'docker build -t usher .',
        'do thing'
      ]
    },
    {
      key: 'bogus',
      expected: false
    },
    {
      key: 'null',
      expected: false
    }
  ];

  tests.forEach( test =>
    it(`should make command ${test.key} ==> ${test.expected}`, () =>
      expect(result[test.key]).to.deep.equal(test.expected)));
});
<commit_msg>Make some vars lets for strictness purposes.<commit_after>"use strict";

var expect = require('chai').expect;
var parse = require('../src/commands/parse');

describe('YAML-to-commands parser', () => {
  let filename = 'test/test.usher.yml';
  let result = parse(filename);
  let tests = [
    {
      key: 'basic',
      expected: 'docker build -t usher .'
    },
    {
      key: 'custom',
      expected: 'do thing'
    },
    {
      key: 'sequence',
      expected: [
        'docker build -t usher .',
        'do thing'
      ]
    },
    {
      key: 'bogus',
      expected: false
    },
    {
      key: 'null',
      expected: false
    }
  ];

  tests.forEach( test =>
    it(`should make command ${test.key} ==> ${test.expected}`, () =>
      expect(result[test.key]).to.deep.equal(test.expected)));
});
<|endoftext|>