<commit_before>/* jQuery tubular plugin
|* by Sean McCambridge
|* http://www.seanmccambridge.com/tubular
|* version: 1.0
|* updated: October 1, 2012
|* since 2010
|* licensed under the MIT License
|* Enjoy.
|*
|* Thanks,
|* Sean */

;(function ($, window) {

    // test for feature support and return if failure

    // defaults
    var defaults = {
        ratio: 16/9, // usually either 4/3 or 16/9 -- tweak as needed
        videoId: 'ZCAnLxRvNNc', // toy robot in space is a good default, no?
        mute: true,
        repeat: true,
        width: $(window).width(),
        wrapperZIndex: 99,
        playButtonClass: 'tubular-play',
        pauseButtonClass: 'tubular-pause',
        muteButtonClass: 'tubular-mute',
        volumeUpClass: 'tubular-volume-up',
        volumeDownClass: 'tubular-volume-down',
        increaseVolumeBy: 10,
        start: 0
    };

    // methods

    var tubular = function(node, options) { // should be called on the wrapper div
        var options = $.extend({}, defaults, options),
            $body = $('body') // cache body node
            $node = $(node); // cache wrapper node

        // build container
        var tubularContainer = '

';

        // set up css prereq's, inject tubular container and set up wrapper defaults
        $('html,body').css({'width': '100%', 'height': '100%'});
        $body.prepend(tubularContainer);
        $node.css({position: 'relative', 'z-index': options.wrapperZIndex});

        // set up iframe player, use global scope so YT api can talk
        window.player;
        window.onYouTubeIframeAPIReady = function() {
            player = new YT.Player('tubular-player', {
                width: options.width,
                height: Math.ceil(options.width / options.ratio),
                videoId: options.videoId,
                playerVars: {
                    controls: 0,
                    showinfo: 0,
                    modestbranding: 1,
                    wmode: 'transparent'
                },
                events: {
                    'onReady': onPlayerReady,
                    'onStateChange': onPlayerStateChange
                }
            });
        }

        window.onPlayerReady = function(e) {
            resize();
            if (options.mute) e.target.mute();
            e.target.seekTo(options.start);
            e.target.playVideo();
        }

        window.onPlayerStateChange = function(state) {
            if (state.data === 0 && options.repeat) { // video ended and repeat option is set true
                player.seekTo(options.start); // restart
            }
        }

        // resize handler updates width, height and offset of player after resize/init
        var resize = function() {
            var width = $(window).width(),
                pWidth, // player width, to be defined
                height = $(window).height(),
                pHeight, // player height, tbd
                $tubularPlayer = $('#tubular-player');

            // when screen aspect ratio differs from video, video must center and underlay one dimension

            if (width / options.ratio < height) { // if new video height < window height (gap underneath)
                pWidth = Math.ceil(height * options.ratio); // get new player width
                $tubularPlayer.width(pWidth).height(height).css({left: (width - pWidth) / 2, top: 0}); // player width is greater, offset left; reset top
            } else { // new video width < window width (gap to right)
                pHeight = Math.ceil(width / options.ratio); // get new player height
                $tubularPlayer.width(width).height(pHeight).css({left: 0, top: (height - pHeight) / 2}); // player height is greater, offset top; reset left
            }

        }

        // events
        $(window).on('resize.tubular', function() {
            resize();
        })

        $('body').on('click','.' + options.playButtonClass, function(e) { // play button
            e.preventDefault();
            player.playVideo();
        }).on('click', '.' + options.pauseButtonClass, function(e) { // pause button
            e.preventDefault();
            player.pauseVideo();
        }).on('click', '.' + options.muteButtonClass, function(e) { // mute button
            e.preventDefault();
            (player.isMuted()) ? player.unMute() : player.mute();
        }).on('click', '.' + options.volumeDownClass, function(e) { // volume down button
            e.preventDefault();
            var currentVolume = player.getVolume();
            if (currentVolume < options.increaseVolumeBy) currentVolume = options.increaseVolumeBy;
            player.setVolume(currentVolume - options.increaseVolumeBy);
        }).on('click', '.' + options.volumeUpClass, function(e) { // volume up button
            e.preventDefault();
            if (player.isMuted()) player.unMute(); // if mute is on, unmute
            var currentVolume = player.getVolume();
            if (currentVolume > 100 - options.increaseVolumeBy) currentVolume = 100 - options.increaseVolumeBy;
            player.setVolume(currentVolume + options.increaseVolumeBy);
        })
    }

    // load yt iframe js api

    var tag = document.createElement('script');
    tag.src = "//www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

    // create plugin

    $.fn.tubular = function (options) {
        return this.each(function () {
            if (!$.data(this, 'tubular_instantiated')) { // let's only run one
                $.data(this, 'tubular_instantiated',
                tubular(this, options));
            }
        });
    }

})(jQuery, window);<commit_msg>Video player is now hidden while its initially buffering (and showed when starts playing)<commit_after>/* jQuery tubular plugin
|* by Sean McCambridge
|* http://www.seanmccambridge.com/tubular
|* version: 1.0
|* updated: October 1, 2012
|* since 2010
|* licensed under the MIT License
|* Enjoy.
|*
|* Thanks,
|* Sean */

;(function ($, window) {

    // test for feature support and return if failure

    // defaults
    var defaults = {
        ratio: 16/9, // usually either 4/3 or 16/9 -- tweak as needed
        videoId: 'ZCAnLxRvNNc', // toy robot in space is a good default, no?
        mute: true,
        repeat: true,
        width: $(window).width(),
        wrapperZIndex: 99,
        playButtonClass: 'tubular-play',
        pauseButtonClass: 'tubular-pause',
        muteButtonClass: 'tubular-mute',
        volumeUpClass: 'tubular-volume-up',
        volumeDownClass: 'tubular-volume-down',
        increaseVolumeBy: 10,
        start: 0
    };

    // methods

    var tubular = function(node, options) { // should be called on the wrapper div
        var options = $.extend({}, defaults, options),
            $body = $('body') // cache body node
            $node = $(node); // cache wrapper node

        // build container
        var tubularContainer = '

';

        // set up css prereq's, inject tubular container and set up wrapper defaults
        $('html,body').css({'width': '100%', 'height': '100%'});
        $body.prepend(tubularContainer);
        $node.css({position: 'relative', 'z-index': options.wrapperZIndex});

        // set up iframe player, use global scope so YT api can talk
        window.player;
        window.onYouTubeIframeAPIReady = function() {
            player = new YT.Player('tubular-player', {
                width: options.width,
                height: Math.ceil(options.width / options.ratio),
                videoId: options.videoId,
                iv_load_policy: 3,
                playerVars: {
                    controls: 0,
                    showinfo: 0,
                    modestbranding: 1,
                    wmode: 'transparent'
                },
                events: {
                    'onReady': onPlayerReady,
                    'onStateChange': onPlayerStateChange
                }
            });
        }

        window.onPlayerReady = function(e) {
            resize();
            if (options.mute) e.target.mute();
            e.target.seekTo(options.start);
            e.target.playVideo();
        }

        window.onPlayerStateChange = function(state) {
            if (state.data === 0 && options.repeat) { // video ended and repeat option is set true
                player.seekTo(options.start); // restart
            }

            if(state.data !== 1){
                // Video not playing, hide it temporarily
                $('#tubular-container').css('opacity', '0');
            }else{
                $('#tubular-container').css('opacity', '1');
            }
        }

        // resize handler updates width, height and offset of player after resize/init
        var resize = function() {
            var width = $(window).width(),
                pWidth, // player width, to be defined
                height = $(window).height(),
                pHeight, // player height, tbd
                $tubularPlayer = $('#tubular-player');

            // when screen aspect ratio differs from video, video must center and underlay one dimension

            if (width / options.ratio < height) { // if new video height < window height (gap underneath)
                pWidth = Math.ceil(height * options.ratio); // get new player width
                $tubularPlayer.width(pWidth).height(height).css({left: (width - pWidth) / 2, top: 0}); // player width is greater, offset left; reset top
            } else { // new video width < window width (gap to right)
                pHeight = Math.ceil(width / options.ratio); // get new player height
                $tubularPlayer.width(width).height(pHeight).css({left: 0, top: (height - pHeight) / 2}); // player height is greater, offset top; reset left
            }

        }

        // events
        $(window).on('resize.tubular', function() {
            resize();
        })

        $('body').on('click','.' + options.playButtonClass, function(e) { // play button
            e.preventDefault();
            player.playVideo();
        }).on('click', '.' + options.pauseButtonClass, function(e) { // pause button
            e.preventDefault();
            player.pauseVideo();
        }).on('click', '.' + options.muteButtonClass, function(e) { // mute button
            e.preventDefault();
            (player.isMuted()) ? player.unMute() : player.mute();
        }).on('click', '.' + options.volumeDownClass, function(e) { // volume down button
            e.preventDefault();
            var currentVolume = player.getVolume();
            if (currentVolume < options.increaseVolumeBy) currentVolume = options.increaseVolumeBy;
            player.setVolume(currentVolume - options.increaseVolumeBy);
        }).on('click', '.' + options.volumeUpClass, function(e) { // volume up button
            e.preventDefault();
            if (player.isMuted()) player.unMute(); // if mute is on, unmute
            var currentVolume = player.getVolume();
            if (currentVolume > 100 - options.increaseVolumeBy) currentVolume = 100 - options.increaseVolumeBy;
            player.setVolume(currentVolume + options.increaseVolumeBy);
        })
    }

    // load yt iframe js api

    var tag = document.createElement('script');
    tag.src = "//www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

    // create plugin

    $.fn.tubular = function (options) {
        return this.each(function () {
            if (!$.data(this, 'tubular_instantiated')) { // let's only run one
                $.data(this, 'tubular_instantiated',
                tubular(this, options));
            }
        });
    }

})(jQuery, window);<|endoftext|>


<commit_before>(function() {
	var arrow = document.createElement('canvas'),
		controlSize = 100,
		controlThird = controlSize / 3,
		controlTwoThird = controlThird * 2,
		controlHalf = controlSize / 2,
		controls = [
			[
				document.createElement('img'),
				[controlThird, controlThird],
				[controlTwoThird, controlThird],
				[controlHalf, controlTwoThird]
			],
			[
				document.createElement('img'),
				[controlTwoThird, controlThird],
				[controlTwoThird, controlTwoThird],
				[controlThird, controlHalf]
			],
			[
				document.createElement('img'),
				[controlThird, controlTwoThird],
				[controlTwoThird, controlTwoThird],
				[controlHalf, controlThird]
			],
			[
				document.createElement('img'),
				[controlThird, controlThird],
				[controlThird, controlTwoThird],
				[controlTwoThird, controlHalf]
			]
		],
		ctx,
		c;

	if (!arrow.getContext) {
		throw 'This browser does not support the use of canvas';
	}

	arrow.width = arrow.height = controlSize;
	ctx = arrow.getContext('2d');

	ctx.fillStyle = '#888';
	ctx.strokeRect(0, 0, controlSize, controlSize);
	for (c in controls) {
		ctx.clearRect(1, 1, controlSize - 2, controlSize - 2);
		ctx.beginPath();
		ctx.moveTo(controls[c][1][0], controls[c][1][1]);
		ctx.lineTo(controls[c][2][0], controls[c][2][1]);
		ctx.lineTo(controls[c][3][0], controls[c][3][1]);
		ctx.fill();
		controls[c][0].src = arrow.toDataURL("image/png").replace("image/png", "image/octet-stream");
	}

	var ad = function(parent) {
		if (parent.tagName != 'CANVAS') {
			throw 'The parent element must be a canvas tag';
		}

		this.parent = parent;
		this.ctx = this.parent.getContext('2d');
		this._controlButtons = [],
		_init.apply(this, [4, 4]);
	};

	var _init = function(gridWidth, nbColors) {
		_generateColors.apply(this, [nbColors]);
		_generateGrid.apply(this, [gridWidth, nbColors]);
		_displayGrid.apply(this, [true]);
		this._nbBlobs = _detectBlobs.apply(this);
		_setEvents.apply(this);
	};

	var _randomInt = function(maxExcluded) {
		return 0 | Math.random() * maxExcluded;
	};

	/**
	 * Generate an array of colors. A color is an array of 3 integer(0, 255).
	 * The first color is randomly generated, the last one is the opposite of
	 * the first one. The others are calculated with a lerp computation.
	 */
	var _generateColors = function(nb) {
		var colors = [],
			c = 1,
			last = nb - 1,
			percentColor,
			_generateRandomColor,
			_lerp;

		_generateRandomColor = function() {
			return [
				_randomInt(256),
				_randomInt(256),
				_randomInt(256)
			]
		};

		_lerp = function(val1, val2, ratio) {
			return 0 | (Math.min(val1, val2) + Math.abs(val1 - val2) * ratio);
		};

		colors[0] = _generateRandomColor();
		colors[last] = [
			(colors[0][0] + 128) % 256,
			(colors[0][1] + 128) % 256,
			(colors[0][2] + 128) % 256
		];

		for (; c < last; c++) {
			percentColor = c / last;
			colors[c] = [
				_lerp(colors[0][0], colors[last][0], percentColor),
				_lerp(colors[0][2], colors[last][1], percentColor),
				_lerp(colors[0][1], colors[last][2], percentColor)
			];
		}

		this.colors = colors;
		this.nbColors = colors.length;
	};

	/**
	 * Generate the grid from a width (the grid is a square) and a number of
	 * colors.
	 * The returned grid is a 2 dimensions array containing, for each cell, an
	 * integer corresponding to the cell's color index (0, nbColors - 1)
	 */
	var _generateGrid = function(width, nbColors) {
		var x = 0,
			y;

		this.grid = [];

		for (; x < width; x++) {
			this.grid[x] = [];
			for (y = 0; y < width; y++) {
				this.grid[x][y] = _randomInt(nbColors);
			}
		}
	};

	/**
	 * Display the grid in the canvas
	 */
	var _displayGrid = function(init) {
		var canvasWidth = this.ctx.canvas.clientWidth,
			canvasHeight = this.ctx.canvas.clientHeight,
			gridWidth = this.grid[0].length,
			x, y, contX, contY,
			color,
			_displayCell;

		this.cellSize = canvasWidth / (gridWidth + 2);

		_displayCell = function(x, y) {
			var color = this.colors[this.grid[x][y]];
			this.ctx.fillStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
			this.ctx.fillRect((x + 1) * this.cellSize, (y + 1) * this.cellSize, this.cellSize, this.cellSize);
		};

		for (x = 0; x < gridWidth; x++) {
			if (init) {
				// control buttons
				// to factorize in a loop?
				contX = (x + 1) * this.cellSize;
				contY = 0;
				this._controlButtons.push([contX, contY]);
				this.ctx.drawImage(controls[0][0], contX, contY, this.cellSize, this.cellSize);
				this._controlButtons.push([contY, contX]);
				this.ctx.drawImage(controls[3][0], contY, contX, this.cellSize, this.cellSize);
				contX = canvasWidth - this.cellSize;
				contY = (x + 1) * this.cellSize;
				this._controlButtons.push([contX, contY]);
				this.ctx.drawImage(controls[1][0], contX, contY, this.cellSize, this.cellSize);
				this._controlButtons.push([contY, contX]);
				this.ctx.drawImage(controls[2][0], contY, contX, this.cellSize, this.cellSize);
			}

			for (y = 0; y < gridWidth; y++) {
				_displayCell.apply(this, [x, y]);
			}
		}
	};

	var _setEvents = function() {
		var _onControl = function(x, y) {
			var b, nbButtons = this._controlButtons.length;

			for (b = 0; b < nbButtons; b++) {
				if (
					this._controlButtons[b][0] <= x
					&& x <= this._controlButtons[b][0] + this.cellSize
					&& this._controlButtons[b][1] <= y
					&& y <= this._controlButtons[b][1] + this.cellSize
				) {
					break;
				}
			}

			return b == nbButtons ? null : b;
		};

		B.addEvent(this.parent, 'click', function(e){
			var button = _onControl.apply(this, [e.layerX, e.layerY]),
				_shiftDown, _shiftRight, _shiftLeft, _shiftUp,
				row;
			if (button == null) return;

			_shiftDown = function(col) {
				var last = this.grid[col][this.grid[col].length - 1];
				this.grid[col].pop();
				this.grid[col].unshift(last);
			};
			_shiftRight = function(row) {
				var old1, old2 = -1, r, last = this.grid.length - 1;
				for (r in this.grid) {
					old1 = this.grid[r][row];
					this.grid[r][row] = ~old2 ? old2 : this.grid[(r + last) % this.grid.length][row];
					old2 = old1;
				}
			};
			_shiftUp = function(col) {
				var first = this.grid[col][0];
				this.grid[col].shift();
				this.grid[col].push(first);
			};
			_shiftLeft = function(row) {
				var r, first = this.grid[0][row];
				for (r in this.grid) {
					this.grid[r][row] = this.grid[(parseInt(r) + 1) % this.grid.length][row];
				}
				this.grid[this.grid.length - 1][row] = first;
			};

			row = 0|button/4;
			switch (button % 4) {
				case 0:
					_shiftDown.apply(this, [row]);
					break;
				case 1:
					_shiftRight.apply(this, [row]);
					break;
				case 2:
					_shiftLeft.apply(this, [row]);
					break;
				case 3:
					_shiftUp.apply(this, [row]);
					break;
			};

			this._nbBlobs = _detectBlobs.apply(this);
			_displayGrid.apply(this, [false]);
		}.bind(this));
	};

	var _detectBlobs = function() {
		var blobs, nbBlobs = 0,
			x, y, current,
			width = this.grid.length,
			height = this.grid[0].length,
			nbCells = width * height,
			north, west,
			_setBlobsAndPropagate;

		blobs = new Array(nbCells);

		_setBlobsAndPropagate = function(x, y, id) {
			var current = this.grid[x][y],
				currentIndex = y * width + x,
				north = currentIndex - width,
				south = currentIndex + width,
				east = currentIndex + 1,
				west = currentIndex - 1;
			blobs[currentIndex] = id;

			if (north > 0 && blobs[north] == undefined && this.grid[x][y-1] == current) {
				_setBlobsAndPropagate.apply(this, [x, y-1, id]);
			}
			if (east%width > currentIndex%width && blobs[east] == undefined && this.grid[x+1][y] == current) {
				_setBlobsAndPropagate.apply(this, [x+1, y, id]);
			}
			if (south < nbCells && blobs[south] == undefined && this.grid[x][y+1] == current) {
				_setBlobsAndPropagate.apply(this, [x, y+1, id]);
			}
			if (Math.abs(west%width) < currentIndex%width && blobs[west] == undefined && this.grid[x-1][y] == current) {
				_setBlobsAndPropagate.apply(this, [x-1, y, id]);
			}
		};

		for (x = 0; x < width; x++) {
			for (y = 0; y < height; y++) {
				if (blobs[y * width + x] != undefined) {
					continue;
				}

				_setBlobsAndPropagate.apply(this, [x, y, nbBlobs]);
				nbBlobs++;
			}
		}

		return nbBlobs;
	};

	window.Ad = ad;
})();
<commit_msg>empty test added to check if the player won<commit_after>(function() {
	var arrow = document.createElement('canvas'),
		controlSize = 100,
		controlThird = controlSize / 3,
		controlTwoThird = controlThird * 2,
		controlHalf = controlSize / 2,
		controls = [
			[
				document.createElement('img'),
				[controlThird, controlThird],
				[controlTwoThird, controlThird],
				[controlHalf, controlTwoThird]
			],
			[
				document.createElement('img'),
				[controlTwoThird, controlThird],
				[controlTwoThird, controlTwoThird],
				[controlThird, controlHalf]
			],
			[
				document.createElement('img'),
				[controlThird, controlTwoThird],
				[controlTwoThird, controlTwoThird],
				[controlHalf, controlThird]
			],
			[
				document.createElement('img'),
				[controlThird, controlThird],
				[controlThird, controlTwoThird],
				[controlTwoThird, controlHalf]
			]
		],
		ctx,
		c;

	if (!arrow.getContext) {
		throw 'This browser does not support the use of canvas';
	}

	arrow.width = arrow.height = controlSize;
	ctx = arrow.getContext('2d');

	ctx.fillStyle = '#888';
	ctx.strokeRect(0, 0, controlSize, controlSize);
	for (c in controls) {
		ctx.clearRect(1, 1, controlSize - 2, controlSize - 2);
		ctx.beginPath();
		ctx.moveTo(controls[c][1][0], controls[c][1][1]);
		ctx.lineTo(controls[c][2][0], controls[c][2][1]);
		ctx.lineTo(controls[c][3][0], controls[c][3][1]);
		ctx.fill();
		controls[c][0].src = arrow.toDataURL("image/png").replace("image/png", "image/octet-stream");
	}

	var ad = function(parent) {
		if (parent.tagName != 'CANVAS') {
			throw 'The parent element must be a canvas tag';
		}

		this.parent = parent;
		this.ctx = this.parent.getContext('2d');
		this._controlButtons = [],
		_init.apply(this, [4, 4]);
	};

	var _init = function(gridWidth, nbColors) {
		_generateColors.apply(this, [nbColors]);
		_generateGrid.apply(this, [gridWidth, nbColors]);
		_displayGrid.apply(this, [true]);
		this._nbBlobs = _detectBlobs.apply(this);
		_setEvents.apply(this);
	};

	var _randomInt = function(maxExcluded) {
		return 0 | Math.random() * maxExcluded;
	};

	/**
	 * Generate an array of colors. A color is an array of 3 integer(0, 255).
	 * The first color is randomly generated, the last one is the opposite of
	 * the first one. The others are calculated with a lerp computation.
	 */
	var _generateColors = function(nb) {
		var colors = [],
			c = 1,
			last = nb - 1,
			percentColor,
			_generateRandomColor,
			_lerp;

		_generateRandomColor = function() {
			return [
				_randomInt(256),
				_randomInt(256),
				_randomInt(256)
			]
		};

		_lerp = function(val1, val2, ratio) {
			return 0 | (Math.min(val1, val2) + Math.abs(val1 - val2) * ratio);
		};

		colors[0] = _generateRandomColor();
		colors[last] = [
			(colors[0][0] + 128) % 256,
			(colors[0][1] + 128) % 256,
			(colors[0][2] + 128) % 256
		];

		for (; c < last; c++) {
			percentColor = c / last;
			colors[c] = [
				_lerp(colors[0][0], colors[last][0], percentColor),
				_lerp(colors[0][2], colors[last][1], percentColor),
				_lerp(colors[0][1], colors[last][2], percentColor)
			];
		}

		this.colors = colors;
		this.nbColors = colors.length;
	};

	/**
	 * Generate the grid from a width (the grid is a square) and a number of
	 * colors.
	 * The returned grid is a 2 dimensions array containing, for each cell, an
	 * integer corresponding to the cell's color index (0, nbColors - 1)
	 */
	var _generateGrid = function(width, nbColors) {
		var x = 0,
			y;

		this.grid = [];

		for (; x < width; x++) {
			this.grid[x] = [];
			for (y = 0; y < width; y++) {
				this.grid[x][y] = _randomInt(nbColors);
			}
		}
	};

	/**
	 * Display the grid in the canvas
	 */
	var _displayGrid = function(init) {
		var canvasWidth = this.ctx.canvas.clientWidth,
			canvasHeight = this.ctx.canvas.clientHeight,
			gridWidth = this.grid[0].length,
			x, y, contX, contY,
			color,
			_displayCell;

		this.cellSize = canvasWidth / (gridWidth + 2);

		_displayCell = function(x, y) {
			var color = this.colors[this.grid[x][y]];
			this.ctx.fillStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
			this.ctx.fillRect((x + 1) * this.cellSize, (y + 1) * this.cellSize, this.cellSize, this.cellSize);
		};

		for (x = 0; x < gridWidth; x++) {
			if (init) {
				// control buttons
				// to factorize in a loop?
				contX = (x + 1) * this.cellSize;
				contY = 0;
				this._controlButtons.push([contX, contY]);
				this.ctx.drawImage(controls[0][0], contX, contY, this.cellSize, this.cellSize);
				this._controlButtons.push([contY, contX]);
				this.ctx.drawImage(controls[3][0], contY, contX, this.cellSize, this.cellSize);
				contX = canvasWidth - this.cellSize;
				contY = (x + 1) * this.cellSize;
				this._controlButtons.push([contX, contY]);
				this.ctx.drawImage(controls[1][0], contX, contY, this.cellSize, this.cellSize);
				this._controlButtons.push([contY, contX]);
				this.ctx.drawImage(controls[2][0], contY, contX, this.cellSize, this.cellSize);
			}

			for (y = 0; y < gridWidth; y++) {
				_displayCell.apply(this, [x, y]);
			}
		}
	};

	var _setEvents = function() {
		var _onControl = function(x, y) {
			var b, nbButtons = this._controlButtons.length;

			for (b = 0; b < nbButtons; b++) {
				if (
					this._controlButtons[b][0] <= x
					&& x <= this._controlButtons[b][0] + this.cellSize
					&& this._controlButtons[b][1] <= y
					&& y <= this._controlButtons[b][1] + this.cellSize
				) {
					break;
				}
			}

			return b == nbButtons ? null : b;
		};

		B.addEvent(this.parent, 'click', function(e){
			var button = _onControl.apply(this, [e.layerX, e.layerY]),
				_shiftDown, _shiftRight, _shiftLeft, _shiftUp,
				row;
			if (button == null) return;

			_shiftDown = function(col) {
				var last = this.grid[col][this.grid[col].length - 1];
				this.grid[col].pop();
				this.grid[col].unshift(last);
			};
			_shiftRight = function(row) {
				var old1, old2 = -1, r, last = this.grid.length - 1;
				for (r in this.grid) {
					old1 = this.grid[r][row];
					this.grid[r][row] = ~old2 ? old2 : this.grid[(r + last) % this.grid.length][row];
					old2 = old1;
				}
			};
			_shiftUp = function(col) {
				var first = this.grid[col][0];
				this.grid[col].shift();
				this.grid[col].push(first);
			};
			_shiftLeft = function(row) {
				var r, first = this.grid[0][row];
				for (r in this.grid) {
					this.grid[r][row] = this.grid[(parseInt(r) + 1) % this.grid.length][row];
				}
				this.grid[this.grid.length - 1][row] = first;
			};

			row = 0|button/4;
			switch (button % 4) {
				case 0:
					_shiftDown.apply(this, [row]);
					break;
				case 1:
					_shiftRight.apply(this, [row]);
					break;
				case 2:
					_shiftLeft.apply(this, [row]);
					break;
				case 3:
					_shiftUp.apply(this, [row]);
					break;
			};

			this._nbBlobs = _detectBlobs.apply(this);
			_displayGrid.apply(this, [false]);

			if (this._nbBlobs == this.nbColors) {
			}
		}.bind(this));
	};

	var _detectBlobs = function() {
		var blobs, nbBlobs = 0,
			x, y, current,
			width = this.grid.length,
			height = this.grid[0].length,
			nbCells = width * height,
			north, west,
			_setBlobsAndPropagate;

		blobs = new Array(nbCells);

		_setBlobsAndPropagate = function(x, y, id) {
			var current = this.grid[x][y],
				currentIndex = y * width + x,
				north = currentIndex - width,
				south = currentIndex + width,
				east = currentIndex + 1,
				west = currentIndex - 1;
			blobs[currentIndex] = id;

			if (north > 0 && blobs[north] == undefined && this.grid[x][y-1] == current) {
				_setBlobsAndPropagate.apply(this, [x, y-1, id]);
			}
			if (east%width > currentIndex%width && blobs[east] == undefined && this.grid[x+1][y] == current) {
				_setBlobsAndPropagate.apply(this, [x+1, y, id]);
			}
			if (south < nbCells && blobs[south] == undefined && this.grid[x][y+1] == current) {
				_setBlobsAndPropagate.apply(this, [x, y+1, id]);
			}
			if (Math.abs(west%width) < currentIndex%width && blobs[west] == undefined && this.grid[x-1][y] == current) {
				_setBlobsAndPropagate.apply(this, [x-1, y, id]);
			}
		};

		for (x = 0; x < width; x++) {
			for (y = 0; y < height; y++) {
				if (blobs[y * width + x] != undefined) {
					continue;
				}

				_setBlobsAndPropagate.apply(this, [x, y, nbBlobs]);
				nbBlobs++;
			}
		}

		return nbBlobs;
	};

	window.Ad = ad;
})();
<|endoftext|>


<commit_before>class TagView extends HTMLElement {
  constructor(comment) {
    super();

    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(this.getTemplate());
    this.comment = comment;
  }

  getTemplate() {
    const template = document.createElement('template');
    template.innerHTML = `

`;

    return template.content.cloneNode(true);
  }

  connectedCallback() {
    //create a label
    const tagContainer = this.shadowRoot.querySelector('.tagContainer');
    const tagLabel = document.createElement('span');
    tagLabel.classList.add('tagLabel');
    tagLabel.innerHTML = 'tags:';
    tagContainer.appendChild(tagLabel);

    //create tag elements and add them to the div
    this.comment.commentTags.forEach(tag => {
      tagContainer.appendChild(this.createTag(tag));
    });
  }

  disconnectedCallback() {
  }

  createTag(tagText) {
    const tagDiv = document.createElement('div');
    //add the tag name to the class for easy searching later
    tagDiv.classList.add(tagText);
    tagDiv.classList.add('tagDiv');

    //make a button for each tag
    const newTag = document.createElement('button');
    newTag.appendChild(document.createTextNode(tagText));
    //when the button is clicked perform a search for comments with this tag
    newTag.addEventListener('click', event => {
      //send event to search for this tag
      this.sendSearchRequest(tagText);
    });

    const clearButton = document.createElement('button');
    clearButton.classList.add('hidden');
    clearButton.appendChild(document.createTextNode('✕'));
    clearButton.addEventListener('click', event => {
      //send an empty string to search for
      this.sendSearchRequest('');
      event.stopPropagation();
    });

    tagDiv.appendChild(newTag);
    tagDiv.appendChild(clearButton);

    return tagDiv;
  }

  highlightTag(searchText) {
    //get the tag by searching for it (if it is present)
    const tagDiv = this.shadowRoot.querySelector(`.${searchText}`);
    if(tagDiv) {
      //make the clear button visible
      const clearButton = tagDiv.children[1];
      clearButton.classList.remove('hidden');
    }
  }

  dehighlightTags() {
    //get all of the tags
    const tagDivs = this.shadowRoot.querySelectorAll('.tagDiv');
    tagDivs.forEach(tagDiv => {
      //hide each clear button
      const clearButton = tagDiv.children[1];
      clearButton.classList.add('hidden');
    });
  }

  sendSearchRequest(searchText) {
    const event = new CustomEvent('search', {
      detail: {searchText: searchText.length > 0 ? `tag:${searchText}` : ''},
      bubbles: true,
      composed: true
    });
    this.dispatchEvent(event);
  }
}

window.customElements.define('st-tag-view', TagView);<commit_msg>Small change in tags<commit_after>class TagView extends HTMLElement {
  constructor(comment) {
    super();

    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(this.getTemplate());
    this.comment = comment;
  }

  getTemplate() {
    const template = document.createElement('template');
    template.innerHTML = `

`;

    return template.content.cloneNode(true);
  }

  connectedCallback() {
    //create a label
    const tagContainer = this.shadowRoot.querySelector('.tagContainer');
    const tagLabel = document.createElement('span');
    tagLabel.classList.add('tagLabel');
    tagLabel.innerHTML = 'tags:';
    tagContainer.appendChild(tagLabel);

    //create tag elements and add them to the div
    this.comment.commentTags.forEach(tag => {
      tagContainer.appendChild(this.createTag(tag));
    });
  }

  disconnectedCallback() {
  }

  createTag(tagText) {
    const tagDiv = document.createElement('div');
    //add the tag name to the class for easy searching later
    tagDiv.classList.add(tagText);
    tagDiv.classList.add('tagDiv');

    //make a button for each tag
    const newTag = document.createElement('button');
    newTag.appendChild(document.createTextNode(tagText));
    //when the button is clicked perform a search for comments with this tag
    newTag.addEventListener('click', event => {
      //send event to search for this tag
      this.sendSearchRequest(tagText);
    });

    const clearButton = document.createElement('button');
    clearButton.classList.add('hidden');
    clearButton.appendChild(document.createTextNode('✕'));
    clearButton.addEventListener('click', event => {
      //send an empty string to search for
      this.sendSearchRequest('');
      event.stopPropagation();
    });

    tagDiv.appendChild(newTag);
    tagDiv.appendChild(clearButton);

    return tagDiv;
  }

  highlightTag(searchText) {
    //get the tag by searching for it (if it is present)
    const tagDiv = this.shadowRoot.querySelector(`.${searchText}`);
    if(tagDiv) {
      //make the clear button visible
      const clearButton = tagDiv.children[1];
      clearButton.classList.remove('hidden');
    }
  }

  dehighlightTags() {
    //get all of the tags
    const tagDivs = this.shadowRoot.querySelectorAll('.tagDiv');
    tagDivs.forEach(tagDiv => {
      //hide each clear button
      const clearButton = tagDiv.children[1];
      clearButton.classList.add('hidden');
    });
  }

  sendSearchRequest(searchText) {
    const event = new CustomEvent('search', {
      detail: {searchText: searchText.length > 0 ? `tag:${searchText}` : ''},
      bubbles: true,
      composed: true
    });
    this.dispatchEvent(event);
  }
}

window.customElements.define('st-tag-view', TagView);<|endoftext|>


<commit_before>const Eris = require("eris");

var bot = new Eris("BOT_TOKEN");
// Replace BOT_TOKEN with your bot account's token

bot.on("ready", () => { // When the bot is ready
    console.log("Ready!"); // Log "Ready!"
});

bot.on("guildMemberAdd", (guild, member) => { // When a member joins a guild
    var response = `Welcome ${member.mention} to ${guild.name}!`;
    // Make a welcome string with the member mention and guild name

    bot.createMessage(guild.defaultChannel.id, response);
    // Send the response in the guild's default channel
});

bot.on("guildMemberRemove", (guild, member) => { // When a member leaves a guild
    var response = `Farewell @${member.user.username}!`;
    // Make a farewell string with the member's username

    bot.createMessage(guild.defaultChannel.id, response);
    // Send the response in the guild's default channel
});

bot.connect(); // Get the bot to connect to Discord
<commit_msg>Removed welcomer example<commit_after><|endoftext|>


<commit_before>function checkForLocalStorage() {
    var hasWebStorage = false;

    if(typeof(Storage) !== "undefined") {
    	console.log("local storage supported");
    	hasWebStorage = true;
    } else {
  	console.log("Oh-Noes: local storage unsupported!");
    }
    return hasWebStorage;
}

function getTeamList(hasWebStorage) {
    if(hasWebStorage) {
      var retrievedObject = localStorage.getItem('teamList');
      console.log('retrievedObject: ', JSON.parse(retrievedObject));
      teamList = JSON.parse(retrievedObject);
    }
    if (teamList === null) {
      console.log("nothing was there");
      teamList = new Array();
    }
    return teamList;
}

function storeTeamList(hasWebStorage, tempTeamList) {
    console.log('parse v2');
    if(hasWebStorage) {
      localStorage.setItem('teamList', JSON.stringify(tempTeamList));
    } else {
      console.log("localStorage not supported! :(");
    }
}
<commit_msg>changed added temp prefix to all local variables to remove scope errors<commit_after>function checkForLocalStorage() {
    var temphasWebStorage = false;

    if(typeof(Storage) !== "undefined") {
    	console.log("local storage supported");
    	temphasWebStorage = true;
    } else {
  	console.log("Oh-Noes: local storage unsupported!");
    }
    return temphasWebStorage;
}

function getTeamList(temphasWebStorage) {
    if(temphasWebStorage) {
      var retrievedObject = localStorage.getItem('teamList');
      console.log('retrievedObject: ', JSON.parse(retrievedObject));
      var tempteamList = JSON.parse(retrievedObject);
    }
    if (tempteamList === null) {
      console.log("nothing was there");
      tempteamList = new Array();
    }
    return tempteamList;
}

function storeTeamList(temphasWebStorage, tempTeamList) {
    console.log('parse v2');
    if(temphasWebStorage) {
      localStorage.setItem('teamList', JSON.stringify(tempTeamList));
    } else {
      console.log("localStorage not supported! :(");
    }
}
<|endoftext|>


<commit_before>import React from "react"
import constants from "../../services/constants"
import { roundingNumber } from "../../utils/converter"

const TransactionLoadingView = (props) => {
  if (props.broadcasting) {
    var classPending = props.error === "" ? " pulse" : ""
    return (

Broadcast

                    {props.error === "" ?  : }

                    {props.error === "" &&

Broadcasting your transaction to network

                    }
                    {props.error !== "" &&

                        Couldn't broadcast your transaction to the blockchain

{props.error}

                    }

You can now close your browser window or make another {props.type == 'exchange' ? "exchange" : "transfer"}
{props.type == 'exchange' ? "Exchange" : "Transfer"}

    )
  }
  var classPending = props.status === "pending" ? " pulse" : ""
  return (

Broadcast

Transaction

                  {props.txHash.slice(0, 12)} ... {props.txHash.slice(-10)}

                  {props.status == "pending" ?  : }

                  {props.status === "success" &&

                      Broadcasted your transaction to the blockchain

Current address balance

                      {props.type === "exchange" &&

                            {props.balanceInfo.sourceTokenName}
                            {roundingNumber(props.balanceInfo.sourceAmount)} {props.balanceInfo.sourceTokenSymbol}

                            {props.balanceInfo.destTokenName}
                            {roundingNumber(props.balanceInfo.destAmount)} {props.balanceInfo.destTokenSymbol}

                      }
                      {props.type === "transfer" &&

                            {props.balanceInfo.tokenName}
                            {roundingNumber(props.balanceInfo.amount)} {props.balanceInfo.tokenSymbol}

                      }

                  }
                  {props.status === "failed" &&

                      Transaction error

{props.error}

                  }
                  {props.status === "pending" &&

Waiting for your transaction to be mined

                  }

You can now close your browser window or make another {props.type == 'exchange' ? "exchange" : "transfer"}
{props.type == 'exchange' ? "Exchange" : "Transfer"}

  )
}

export default TransactionLoadingView
<commit_msg>change class transaction loading button class<commit_after>import React from "react"
import constants from "../../services/constants"
import { roundingNumber } from "../../utils/converter"

const TransactionLoadingView = (props) => {
  if (props.broadcasting) {
    var classPending = props.error === "" ? " pulse" : ""
    return (

Broadcast

                    {props.error === "" ?  : }

                    {props.error === "" &&

Broadcasting your transaction to network

                    }
                    {props.error !== "" &&

                        Couldn't broadcast your transaction to the blockchain

{props.error}

                    }

You can now close your browser window or make another {props.type == 'exchange' ? "exchange" : "transfer"}
{props.type == 'exchange' ? "Exchange" : "Transfer"}

    )
  }
  var classPending = props.status === "pending" ? " pulse" : ""
  return (

Broadcast

Transaction

                  {props.txHash.slice(0, 12)} ... {props.txHash.slice(-10)}

                  {props.status == "pending" ?  : }

                  {props.status === "success" &&

                      Broadcasted your transaction to the blockchain

Current address balance

                      {props.type === "exchange" &&

                            {props.balanceInfo.sourceTokenName}
                            {roundingNumber(props.balanceInfo.sourceAmount)} {props.balanceInfo.sourceTokenSymbol}

                            {props.balanceInfo.destTokenName}
                            {roundingNumber(props.balanceInfo.destAmount)} {props.balanceInfo.destTokenSymbol}

                      }
                      {props.type === "transfer" &&

                            {props.balanceInfo.tokenName}
                            {roundingNumber(props.balanceInfo.amount)} {props.balanceInfo.tokenSymbol}

                      }

                  }
                  {props.status === "failed" &&

                      Transaction error

{props.error}

                  }
                  {props.status === "pending" &&

Waiting for your transaction to be mined

                  }

You can now close your browser window or make another {props.type == 'exchange' ? "exchange" : "transfer"}
{props.type == 'exchange' ? "Exchange" : "Transfer"}

  )
}

export default TransactionLoadingView
<|endoftext|>


<commit_before>const colors = require('tailwindcss/colors');

const GRAY = {
  1000: '#18191f',
  900: '#1F2028',
  800: '#272935',
  700: '#353849',
  600: '#575969',

  200: '#BFC3D9',
  100: '#dcdfec',
  50: '#FFF',
};

module.exports = {
  plugins: [require('@tailwindcss/typography')],
  content: [
    './pages/**/*.{js,ts,jsx,tsx,md,mdx}',
    './lib/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {
      typography: {
        lg: {
          css: {
            h1: {
              fontSize: '3.5rem',
            },
            'h2 > a': {
              fontSize: '2rem',
            },
            pre: {
              fontSize: '1.05rem',
              lineHeight: '2.1',
            },
            img: {
              margin: '0',
            },
          },
        },
        md: {
          css: {
            fontSize: '1.125rem',
            h1: {
              fontSize: '3rem',
            },
            pre: {
              lineHeight: '2.1',
            },
            img: {
              margin: '0',
            },
            '.card': {
              maxWidth: 'calc(50% - 5px)',
            },
          },
        },
        DEFAULT: {
          css: {
            img: {
              margin: '0',
            },
            '.card': {
              maxWidth: '100%',
            },
            fontSize: '1rem',
            maxWidth: '70ch',
            color: '#b0b2c3',
            blockquote: {
              color: colors.rose[300],
              fontSize: '90%',
              borderTopRightRadius: '0.25rem',
              borderBottomRightRadius: '0.25rem',
              borderLeftColor: colors.rose[400],
              borderLeftWidth: '2px',
              backgroundColor: `rgba(255,125,225,0.15)`,
              padding: '0.25rem 0.5rem 0.25rem 1rem',
              '> p': {
                margin: 0,
              },
            },
            strong: {
              color: '#fff',
            },
            pre: {
              color: '#cddbf7',
              padding: '1rem 1.5rem',
              lineHeight: '2',
              backgroundColor: '#282834',
              '> code': {
                display: 'grid',
                width: 'max-content',
              },
              '.line': {
                borderLeft: '2px solid transparent',
              },
              '.line.line--highlighted': {
                borderLeftColor: colors.rose[400],
                backgroundColor: '#3b3547',
                margin: '0 -1.5rem',
                padding: '0 1.5rem',
              },
              span: {
                position: 'relative',
                zIndex: '1',
              },
              '.word': {
                backgroundColor: 'rgba(200,200,255,0.2)',
                padding: '0.2rem 0',
                borderRadius: '0.25rem',
                boxShadow:
                  '-2px 0 0 1px #48485d, 2px 0 0 1px #48485d',
                zIndex: '0',
              },
            },
            h1: {
              display: 'inline',
              lineHeight: '1.1',
              wordBreak: 'break-word',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              fontSize: '2.25rem',
              color: 'white',
            },
            h2: {
              color: '#fff',
              wordBreak: 'break-word',
              '> a': {
                color: GRAY[50],
                borderBottomColor: 'transparent',
              },
              '> a:hover': {
                color: GRAY[50],
                borderBottomColor: GRAY[50],
              },
            },
            h3: {
              color: '#BFC3D9',
              wordBreak: 'break-word',
              '> a': {
                color: GRAY[50],
                borderBottomColor: 'transparent',
              },
              '> a:hover': {
                color: GRAY[50],
                borderBottomColor: GRAY[50],
              },
            },
            h4: {
              '> a': {
                color: GRAY[50],
                borderBottomColor: 'transparent',
              },
              '> a:hover': {
                color: GRAY[50],
                borderBottomColor: GRAY[50],
              },
            },
            code: {
              color: '#b4c2f0',
              fontWeight: '500',
              '&::before': {
                display: 'none',
              },
              '&::after': {
                display: 'none',
              },
            },
            ':not(pre) > code': {
              borderRadius: '0.25rem',
              padding: '0.2rem 0.4rem',
              backgroundColor: '#30303e',
            },
            a: {
              color: colors.rose[300],
              textDecoration: 'none',
              borderBottom: `2px solid #744d60`,
              paddingBottom: '0.1em',
              transition: 'border-color 0.15s, color 0.15s',
              '&:hover': {
                borderBottomColor: `${colors.rose[200]}`,
                color: `${colors.rose[200]}`,
              },
              '&:active': {
                borderBottomColor: `${colors.rose[100]}`,
              },
            },
            'a code': {
              color: '#fff',
            },
            'blockquote p:first-of-type::before': {
              display: 'none',
            },
          },
        },
      },
      colors: {
        green: colors.emerald,
        yellow: colors.amber,
        purple: colors.violet,
        gray: GRAY,
      },
      zIndex: {
        '-1': '-1',
      },
      backgroundImage: {
        'gradient-radial':
          'radial-gradient(circle at 50% 10%, var(--tw-gradient-stops))',
      },
      inset: {
        '-32': '-128px',
      },
      width: {
        '1200px': '1200px',
      },
      height: {
        128: '32rem',
      },
      lineHeight: {
        'gradient-heading': '1.15 !important',
      },
    },
  },
};
<commit_msg>docs: fix line highlighting<commit_after>const colors = require('tailwindcss/colors');

const GRAY = {
  1000: '#18191f',
  900: '#1F2028',
  800: '#272935',
  700: '#353849',
  600: '#575969',

  200: '#BFC3D9',
  100: '#dcdfec',
  50: '#FFF',
};

module.exports = {
  plugins: [require('@tailwindcss/typography')],
  content: [
    './pages/**/*.{js,ts,jsx,tsx,md,mdx}',
    './lib/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {
      typography: {
        lg: {
          css: {
            h1: {
              fontSize: '3.5rem',
            },
            'h2 > a': {
              fontSize: '2rem',
            },
            pre: {
              fontSize: '1.05rem',
              lineHeight: '2.1',
            },
            img: {
              margin: '0',
            },
          },
        },
        md: {
          css: {
            fontSize: '1.125rem',
            h1: {
              fontSize: '3rem',
            },
            pre: {
              lineHeight: '2.1',
            },
            img: {
              margin: '0',
            },
            '.card': {
              maxWidth: 'calc(50% - 5px)',
            },
          },
        },
        DEFAULT: {
          css: {
            img: {
              margin: '0',
            },
            '.card': {
              maxWidth: '100%',
            },
            fontSize: '1rem',
            maxWidth: '70ch',
            color: '#b0b2c3',
            blockquote: {
              color: colors.rose[300],
              fontSize: '90%',
              borderTopRightRadius: '0.25rem',
              borderBottomRightRadius: '0.25rem',
              borderLeftColor: colors.rose[400],
              borderLeftWidth: '2px',
              backgroundColor: `rgba(255,125,225,0.15)`,
              padding: '0.25rem 0.5rem 0.25rem 1rem',
              '> p': {
                margin: 0,
              },
            },
            strong: {
              color: '#fff',
            },
            pre: {
              color: '#cddbf7',
              padding: '1rem 1.5rem',
              lineHeight: '2',
              backgroundColor: '#282834',
              '> code': {
                display: 'grid',
              },
              '.line': {
                borderLeft: '2px solid transparent',
                margin: '0 -1.5rem',
                padding: '0 1.5rem',
              },
              '.line.line--highlighted': {
                borderLeftColor: colors.rose[400],
                backgroundColor: '#3b3547',
              },
              span: {
                position: 'relative',
                zIndex: '1',
              },
              '.word': {
                backgroundColor: 'rgba(200,200,255,0.2)',
                padding: '0.2rem 0',
                borderRadius: '0.25rem',
                boxShadow:
                  '-2px 0 0 1px #48485d, 2px 0 0 1px #48485d',
                zIndex: '0',
              },
            },
            h1: {
              display: 'inline',
              lineHeight: '1.1',
              wordBreak: 'break-word',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              fontSize: '2.25rem',
              color: 'white',
            },
            h2: {
              color: '#fff',
              wordBreak: 'break-word',
              '> a': {
                color: GRAY[50],
                borderBottomColor: 'transparent',
              },
              '> a:hover': {
                color: GRAY[50],
                borderBottomColor: GRAY[50],
              },
            },
            h3: {
              color: '#BFC3D9',
              wordBreak: 'break-word',
              '> a': {
                color: GRAY[50],
                borderBottomColor: 'transparent',
              },
              '> a:hover': {
                color: GRAY[50],
                borderBottomColor: GRAY[50],
              },
            },
            h4: {
              '> a': {
                color: GRAY[50],
                borderBottomColor: 'transparent',
              },
              '> a:hover': {
                color: GRAY[50],
                borderBottomColor: GRAY[50],
              },
            },
            code: {
              color: '#b4c2f0',
              fontWeight: '500',
              '&::before': {
                display: 'none',
              },
              '&::after': {
                display: 'none',
              },
            },
            ':not(pre) > code': {
              borderRadius: '0.25rem',
              padding: '0.2rem 0.4rem',
              backgroundColor: '#30303e',
            },
            a: {
              color: colors.rose[300],
              textDecoration: 'none',
              borderBottom: `2px solid #744d60`,
              paddingBottom: '0.1em',
              transition: 'border-color 0.15s, color 0.15s',
              '&:hover': {
                borderBottomColor: `${colors.rose[200]}`,
                color: `${colors.rose[200]}`,
              },
              '&:active': {
                borderBottomColor: `${colors.rose[100]}`,
              },
            },
            'a code': {
              color: '#fff',
            },
            'blockquote p:first-of-type::before': {
              display: 'none',
            },
          },
        },
      },
      colors: {
        green: colors.emerald,
        yellow: colors.amber,
        purple: colors.violet,
        gray: GRAY,
      },
      zIndex: {
        '-1': '-1',
      },
      backgroundImage: {
        'gradient-radial':
          'radial-gradient(circle at 50% 10%, var(--tw-gradient-stops))',
      },
      inset: {
        '-32': '-128px',
      },
      width: {
        '1200px': '1200px',
      },
      height: {
        128: '32rem',
      },
      lineHeight: {
        'gradient-heading': '1.15 !important',
      },
    },
  },
};
<|endoftext|>


<commit_before>"use strict";

/**
 * Small lib to go through the OneDrive authentication process and get back the token.
 *
 * Instructions:
 * - Host a copy of callback.html and odauth.js on your domain.
 * - Embed odauth.js in your app like this:
 *   <script src="odauth.js"></script>
 * - Define the onAuthenticated(token) function in your app to receive the auth token.
 * - Create instance of OneDriveAuth and call auth() method to begin and whenever you
 *   need an auth token to make an API call. If you're making an api call in response to
 *   a user's click action, call auth(true), otherwise just call auth(). The difference
 *   is that sometimes OneDriveAuth needs to pop up a window so the user can sign in,
 *   grant your app permission, etc. The pop up can only be launched in response
 *   to a user click, otherwise the browser's popup blocker will block it. When
 *   OneDriveAuth isn't called in click mode, it'll put a sign-in button at the top of
 *   your page for the user to click. When it's done, it'll remove that button.
 */
class OneDriveAuth {
  /**
   * @constructor
   * @param {Array} appInfo
   * @param {string} appInfo.clientId from App Settings
   * @param {string} appInfo.scopes separated by space ("onedrive.readonly wl.signin" for example)
   * @param {string} appInfo.redirectUri
   */
  constructor(appInfo) {
    if (!appInfo.clientId) {
      throw "appInfo object should have `clientId` property set to your application id";
    }

    if (!appInfo.scopes) {
      throw "appInfo object should have `scopes` property set to the scopes your app needs";
    }

    if (!appInfo.redirectUri) {
      throw "appInfo object should have `redirectUri` property set to your redirect landing url";
    }
    this.appInfo = appInfo;
  }

  /**
   * The main auth method. First, we check if we have the user's auth token stored
   * in a cookie. if so, we read it and immediately call your onAuthenticated() method.
   * If we can't find the auth cookie, we need to pop up a window and send the user
   * to Microsoft Account so that they can sign in or grant your app the permissions
   * it needs. Depending on whether or not auth() was called in response to a user
   * click, it will either pop up the auth window or display a sign-in button for
   * the user to click (which results in the pop-up). when the user finishes the
   * auth flow, the popup window redirects back to your hosted callback.html file,
   * which calls the onAuthCallback() static method below. It then sets the auth cookie
   * and calls your app's onAuthenticated() function, passing in the optional 'window'
   * argument for the popup window. your onAuthenticated function should close the
   * popup window if it's passed in.
   *
   * Subsequent calls to auth() will usually complete immediately without the
   * popup because the cookie is still fresh.
   *
   * @param {boolean} wasClicked whether the call is result of a click or not
   */
  auth(wasClicked) {
    this.ensureHttps();
    var token = this.getTokenFromCookie();
    if (token) {
      window.onAuthenticated(token);
    } else if (wasClicked) {
      this.challengeForAuth();
    } else {
      this.showLoginButton();
    }
  }

  /**
   * For added security we require https
   */
  ensureHttps() {
    if (window.location.protocol != "https:") {
      window.location.href = "https:" + window.location.href.substring(window.location.protocol.length);
    }
  }

  getTokenFromCookie() {
    var cookies = document.cookie;
    var name = "odauth=";
    var start = cookies.indexOf(name);
    if (start >= 0) {
      start += name.length;
      var end = cookies.indexOf(';', start);
      if (end < 0) {
        end = cookies.length;
      }

      var value = cookies.substring(start, end);
      return value;
    }

    return "";
  }

  /**
   * Called when a login button needs to be displayed for the user to click on.
   * If a customLoginButton() function is defined by your app, it will be called
   * with `callback` passed in to indicate the button should be added. Otherwise, it
   * will insert a textual login link at the top of the page. If defined, your
   * showCustomLoginButton should call `callback` when clicked.
   */
  showLoginButton() {
    var callback = this.challengeForAuth.bind(this);
    if (typeof window.showCustomLoginButton === "function") {
      window.showCustomLoginButton(callback);
      return;
    }

    var loginText = document.createElement('a');
    loginText.href = "#";
    loginText.id = "loginText";
    loginText.onclick = callback;
    loginText.innerText = "[sign in]";
    document.body.insertBefore(loginText, document.body.children[0]);
  }

  /**
   * Called with the login button created by showLoginButton() needs to be
   * removed. If a customLoginButton() function is defined by your app, it will
   * be called with 'false' passed in to indicate the button should be removed.
   * Otherwise it will remove the textual link that showLoginButton() created.
   */
  removeLoginButton() {
    if (typeof window.showCustomLoginButton === "function") {
      window.showCustomLoginButton(false);
      return;
    }

    var loginText = document.getElementById("loginText");
    if (loginText) {
      document.body.removeChild(loginText);
    }
  }

  /**
   * Send user to the Microsoft Account OAuth2.0 page to authorize the app
   * and return the user to your redirectUri.
   */
  challengeForAuth() {
    var appInfo = this.appInfo;
    var url =
      "https://login.live.com/oauth20_authorize.srf" +
      "?client_id=" + appInfo.clientId +
      "&scope=" + encodeURIComponent(appInfo.scopes) +
      "&response_type=token" +
      "&redirect_uri=" + encodeURIComponent(appInfo.redirectUri);
    this.popup(url);
  }

  popup(url) {
    var width = 525,
      height = 525,
      screenX = window.screenX,
      screenY = window.screenY,
      outerWidth = window.outerWidth,
      outerHeight = window.outerHeight;

    var left = screenX + Math.max(outerWidth - width, 0) / 2;
    var top = screenY + Math.max(outerHeight - height, 0) / 2;

    var features = [
      "width=" + width,
      "height=" + height,
      "top=" + top,
      "left=" + left,
      "status=no",
      "resizable=yes",
      "toolbar=no",
      "menubar=no",
      "scrollbars=yes"
    ];
    var popup = window.open(url, "oauth", features.join(","));
    if (!popup) {
      console.error("failed to pop up auth window");
    }

    popup.focus();
  }

  /**
   * Called from the callback page after OAuth authorization.
   * On success it save the token to cookie and call user's onAuthenticated()
   * function in parent window.
   */
  static onAuthCallback() {
    var authInfo = this.getAuthInfoFromUrl();
    var token = authInfo["access_token"];
    var expiry = parseInt(authInfo["expires_in"]);
    this.setCookie(token, expiry);
    window.opener.onAuthenticated(token, window);
  }

  static getAuthInfoFromUrl() {
    if (window.location.hash) {
      var authResponse = window.location.hash.substring(1);
      var authInfo = JSON.parse(
        '{"' + authResponse.replace(/&/g, '","').replace(/=/g, '":"') + '"}',
        (key, value) => key === "" ? value : decodeURIComponent(value)
      );
      return authInfo;
    } else {
      console.error("failed to receive auth token");
    }
  }

  static setCookie(token, expiresInSeconds) {
    var expiration = new Date();
    expiration.setTime(expiration.getTime() + expiresInSeconds * 1000);
    var cookie = "odauth=" + token + "; path=/; expires=" + expiration.toUTCString();

    if (document.location.protocol.toLowerCase() == "https") {
      cookie = cookie + ";secure";
    }

    document.cookie = cookie;
  }
}
<commit_msg>Remove calls to custom button functions<commit_after>"use strict";

/**
 * Small lib to go through the OneDrive authentication process and get back the token.
 *
 * Instructions:
 * - Host a copy of callback.html and odauth.js on your domain.
 * - Embed odauth.js in your app like this:
 *   <script src="odauth.js"></script>
 * - Define the onAuthenticated(token) function in your app to receive the auth token.
 * - Create instance of OneDriveAuth and call auth() method to begin and whenever you
 *   need an auth token to make an API call. If you're making an api call in response to
 *   a user's click action, call auth(true), otherwise just call auth(). The difference
 *   is that sometimes OneDriveAuth needs to pop up a window so the user can sign in,
 *   grant your app permission, etc. The pop up can only be launched in response
 *   to a user click, otherwise the browser's popup blocker will block it. When
 *   OneDriveAuth isn't called in click mode, it'll put a sign-in button at the top of
 *   your page for the user to click. When it's done, it'll remove that button.
 */
class OneDriveAuth {
  /**
   * @constructor
   * @param {Array} appInfo
   * @param {string} appInfo.clientId from App Settings
   * @param {string} appInfo.scopes separated by space ("onedrive.readonly wl.signin" for example)
   * @param {string} appInfo.redirectUri
   */
  constructor(appInfo) {
    if (!appInfo.clientId) {
      throw "appInfo object should have `clientId` property set to your application id";
    }

    if (!appInfo.scopes) {
      throw "appInfo object should have `scopes` property set to the scopes your app needs";
    }

    if (!appInfo.redirectUri) {
      throw "appInfo object should have `redirectUri` property set to your redirect landing url";
    }
    this.appInfo = appInfo;
  }

  /**
   * The main auth method. First, we check if we have the user's auth token stored
   * in a cookie. if so, we read it and immediately call your onAuthenticated() method.
   * If we can't find the auth cookie, we need to pop up a window and send the user
   * to Microsoft Account so that they can sign in or grant your app the permissions
   * it needs. Depending on whether or not auth() was called in response to a user
   * click, it will either pop up the auth window or display a sign-in button for
   * the user to click (which results in the pop-up). when the user finishes the
   * auth flow, the popup window redirects back to your hosted callback.html file,
   * which calls the onAuthCallback() static method below. It then sets the auth cookie
   * and calls your app's onAuthenticated() function, passing in the optional 'window'
   * argument for the popup window. your onAuthenticated function should close the
   * popup window if it's passed in.
   *
   * Subsequent calls to auth() will usually complete immediately without the
   * popup because the cookie is still fresh.
   *
   * @param {boolean} wasClicked whether the call is result of a click or not
   */
  auth(wasClicked) {
    this.ensureHttps();
    var token = this.getTokenFromCookie();
    if (token) {
      window.onAuthenticated(token);
    } else if (wasClicked) {
      this.challengeForAuth();
    } else {
      this.showLoginButton();
    }
  }

  /**
   * For added security we require https
   */
  ensureHttps() {
    if (window.location.protocol != "https:") {
      window.location.href = "https:" + window.location.href.substring(window.location.protocol.length);
    }
  }

  getTokenFromCookie() {
    var cookies = document.cookie;
    var name = "odauth=";
    var start = cookies.indexOf(name);
    if (start >= 0) {
      start += name.length;
      var end = cookies.indexOf(';', start);
      if (end < 0) {
        end = cookies.length;
      }

      var value = cookies.substring(start, end);
      return value;
    }

    return "";
  }

  /**
   * Called when a login button needs to be displayed for the user to click on.
   * It will insert a textual login link at the top of the page.
   */
  showLoginButton() {
    var callback = this.challengeForAuth.bind(this);
    var loginText = document.createElement('a');
    loginText.href = "#";
    loginText.id = "loginText";
    loginText.onclick = callback;
    loginText.innerText = "[sign in]";
    document.body.insertBefore(loginText, document.body.children[0]);
  }

  /**
   * Called with the login button created by showLoginButton() needs to be
   * removed. If a customLoginButton() function is defined by your app, it will
   * be called with 'false' passed in to indicate the button should be removed.
   * Otherwise it will remove the textual link that showLoginButton() created.
   */
  removeLoginButton() {
    var loginText = document.getElementById("loginText");
    if (loginText) {
      document.body.removeChild(loginText);
    }
  }

  /**
   * Send user to the Microsoft Account OAuth2.0 page to authorize the app
   * and return the user to your redirectUri.
   */
  challengeForAuth() {
    var appInfo = this.appInfo;
    var url =
      "https://login.live.com/oauth20_authorize.srf" +
      "?client_id=" + appInfo.clientId +
      "&scope=" + encodeURIComponent(appInfo.scopes) +
      "&response_type=token" +
      "&redirect_uri=" + encodeURIComponent(appInfo.redirectUri);
    this.popup(url);
  }

  popup(url) {
    var width = 525,
      height = 525,
      screenX = window.screenX,
      screenY = window.screenY,
      outerWidth = window.outerWidth,
      outerHeight = window.outerHeight;

    var left = screenX + Math.max(outerWidth - width, 0) / 2;
    var top = screenY + Math.max(outerHeight - height, 0) / 2;

    var features = [
      "width=" + width,
      "height=" + height,
      "top=" + top,
      "left=" + left,
      "status=no",
      "resizable=yes",
      "toolbar=no",
      "menubar=no",
      "scrollbars=yes"
    ];
    var popup = window.open(url, "oauth", features.join(","));
    if (!popup) {
      console.error("failed to pop up auth window");
    }

    popup.focus();
  }

  /**
   * Called from the callback page after OAuth authorization.
   * On success it save the token to cookie and call user's onAuthenticated()
   * function in parent window.
   */
  static onAuthCallback() {
    var authInfo = this.getAuthInfoFromUrl();
    var token = authInfo["access_token"];
    var expiry = parseInt(authInfo["expires_in"]);
    this.setCookie(token, expiry);
    window.opener.onAuthenticated(token, window);
  }

  static getAuthInfoFromUrl() {
    if (window.location.hash) {
      var authResponse = window.location.hash.substring(1);
      var authInfo = JSON.parse(
        '{"' + authResponse.replace(/&/g, '","').replace(/=/g, '":"') + '"}',
        (key, value) => key === "" ? value : decodeURIComponent(value)
      );
      return authInfo;
    } else {
      console.error("failed to receive auth token");
    }
  }

  static setCookie(token, expiresInSeconds) {
    var expiration = new Date();
    expiration.setTime(expiration.getTime() + expiresInSeconds * 1000);
    var cookie = "odauth=" + token + "; path=/; expires=" + expiration.toUTCString();

    if (document.location.protocol.toLowerCase() == "https") {
      cookie = cookie + ";secure";
    }

    document.cookie = cookie;
  }
}
<|endoftext|>


<commit_before>describe("The Model Class", function() {

    it("Inherits from Emitter", function() {

        var model = new MVCzar.Model();

        expect(model instanceof MVCzar.Emitter).toBe(true);

    });

    it("Allows events to be set on initialisation", function() {

        var result = false,
            that = null;

        var model = new MVCzar.Model({
            events: {
                someEvent: function() {
                    result = true;
                    that = this;
                }
            }
        });

        model.emit('someEvent');

        expect(result).toBe(true);
        expect(that).toBe(model);

    });

    it("Has a getter and setter", function() {

        var model = new MVCzar.Model();

        var obj = {
            qwe: 123
        };

        // setting a single property
        model.set("someProp", "someValue");

        expect(model.get("someProp")).toBe("someValue");
        expect(model.get("anotherProp")).toBeUndefined();

        // setting many properties
        model.set({
            anotherProp: "hey",
            moreProp: true
        });

        expect(model.get("someProp")).toBe("someValue");
        expect(model.get("anotherProp")).toBe("hey");
        expect(model.get("moreProp")).toBe(true);

        // setting an object
        model.set("object", obj);

        expect(model.get("object")).toBe(obj);

    });

    it("Allows properties to be set on initialisation", function() {

        var model = new MVCzar.Model({
            initial: {
                someProp: "hey",
                someNum: 42
            }
        });

        expect(model.get("someProp")).toBe("hey");
        expect(model.get("someNum")).toBe(42);
        expect(model.get("anotherProp")).toBeUndefined();

    });

    it("Will return a copy of the object's properties when .get() is used without arguments", function() {

        var obj = {
                someProp: "hey",
                someNum: 42,
                subObj: {
                    blah: "blah"
                }
            };

        var model = new MVCzar.Model({
            initial: obj
        });

        // should return copy
        expect(model.get()).toEqual(obj);
        expect(model.get()).not.toBe(obj);

        // a deep copy
        expect(model.get().subObj).toEqual(obj.subObj);
        expect(model.get().subObj).not.toBe(obj.subObj);

    });

    it("Allows properties to be unset", function() {

        var model = new MVCzar.Model({
            initial: {
                someProp: "hey",
                someNum: 42
            }
        });

        // at first you see me ....
        expect(model.get("someProp")).toBe("hey");
        expect(model.get("someNum")).toBe(42);

        // and now you don't ....
        model.unset("someProp");
        expect(model.get("someProp")).toBeUndefined();
        expect(model.get("someNum")).toBe(42);

    });

    it("Allows chaining of set and unset", function() {

        var model = new MVCzar.Model({
            initial: {
                someProp: "hey",
                someNum: 42
            }
        });

        // chaining of set and unset
        model.unset("someNum").set("qwe", 123).set("asd", {prop: "erty"}).unset("qwe");

        expect(model.get()).toEqual({
            someProp: "hey",
            asd: {
                prop: "erty"
            }
        });

    });

    it("Emits change events when changing properties", function() {

        var changeAny = 0,
            changeParticular = 0;

        var model = new MVCzar.Model({
            events: {
                change: function() {
                    changeAny++;
                },
                "change:someProp": function() {
                    changeParticular++;
                }
            }
        });

        model.set("aProp", "value");
        expect(changeAny).toBe(1);

        // setting the same value shouldn't trigger a change event
        model.set("aProp", "value");
        expect(changeAny).toBe(1);

        model.set("someProp", "someValue");
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        model.set("aProp", "different");
        expect(changeAny).toBe(3);
        expect(changeParticular).toBe(1);

        // setting multiple properties at once
        // should only trigger change event once
        // also maintaining same value for someProp
        // so shouldn't trigger that particular handler
        model.set({
            someProp: "someValue",
            tooManyProps: 10
        });
        expect(changeAny).toBe(4);
        expect(changeParticular).toBe(1);

        // unsetting a property that doesn't exist shouldn't trigger hander
        model.unset("notAProp");
        expect(changeAny).toBe(4);
        expect(changeParticular).toBe(1);

        // unsetting a property
        model.unset("aProp");
        expect(changeAny).toBe(5);
        expect(changeParticular).toBe(1);

        // unsetting a particular property
        model.unset("someProp");
        expect(changeAny).toBe(6);
        expect(changeParticular).toBe(2);

    });

    it("allows you to set/unset properties silently", function() {

        var changeAny = 0,
            changeParticular = 0;

        var model = new MVCzar.Model({
            events: {
                change: function() {
                    changeAny++;
                },
                "change:someProp": function() {
                    changeParticular++;
                }
            }
        });

        // check handlers are attached
        model.set("aProp", "value");
        expect(changeAny).toBe(1);

        model.set("someProp", "someValue");
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        // silently change a single value
        model.set("aProp", "value2", true);
        model.set("someProp", "someValue2", true);
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        // silently change multiple values
        model.set({
            someProp: "someValue",
            tooManyProps: 10
        }, true);
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        // silently unset a value
        model.unset("someProp", true);
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

    });

    it("has a .toJSON() method that allows JSON.stringify to be used directly", function() {

        var obj = {
                someProp: "hey",
                someNum: 42,
                subObj: {
                    blah: "blah"
                }
            };

        var model = new MVCzar.Model({
            initial: obj
        });

        // calling .toJSON() directly will actually return a copy
        expect(model.toJSON()).toEqual(obj);
        expect(model.toJSON()).not.toBe(obj);

        // which will give the correct output when JSON.stringify is used
        expect(JSON.stringify(model)).toBe(JSON.stringify(obj));

    });

});<commit_msg>add test for initial values not triggering events<commit_after>describe("The Model Class", function() {

    it("Inherits from Emitter", function() {

        var model = new MVCzar.Model();

        expect(model instanceof MVCzar.Emitter).toBe(true);

    });

    it("Allows events to be set on initialisation", function() {

        var result = false,
            that = null;

        var model = new MVCzar.Model({
            events: {
                someEvent: function() {
                    result = true;
                    that = this;
                }
            }
        });

        model.emit('someEvent');

        expect(result).toBe(true);
        expect(that).toBe(model);

    });

    it("Has a getter and setter", function() {

        var model = new MVCzar.Model();

        var obj = {
            qwe: 123
        };

        // setting a single property
        model.set("someProp", "someValue");

        expect(model.get("someProp")).toBe("someValue");
        expect(model.get("anotherProp")).toBeUndefined();

        // setting many properties
        model.set({
            anotherProp: "hey",
            moreProp: true
        });

        expect(model.get("someProp")).toBe("someValue");
        expect(model.get("anotherProp")).toBe("hey");
        expect(model.get("moreProp")).toBe(true);

        // setting an object
        model.set("object", obj);

        expect(model.get("object")).toBe(obj);

    });

    it("Allows properties to be set on initialisation", function() {

        var model = new MVCzar.Model({
            initial: {
                someProp: "hey",
                someNum: 42
            }
        });

        expect(model.get("someProp")).toBe("hey");
        expect(model.get("someNum")).toBe(42);
        expect(model.get("anotherProp")).toBeUndefined();

    });

    it("Will return a copy of the object's properties when .get() is used without arguments", function() {

        var obj = {
                someProp: "hey",
                someNum: 42,
                subObj: {
                    blah: "blah"
                }
            };

        var model = new MVCzar.Model({
            initial: obj
        });

        // should return copy
        expect(model.get()).toEqual(obj);
        expect(model.get()).not.toBe(obj);

        // a deep copy
        expect(model.get().subObj).toEqual(obj.subObj);
        expect(model.get().subObj).not.toBe(obj.subObj);

    });

    it("Allows properties to be unset", function() {

        var model = new MVCzar.Model({
            initial: {
                someProp: "hey",
                someNum: 42
            }
        });

        // at first you see me ....
        expect(model.get("someProp")).toBe("hey");
        expect(model.get("someNum")).toBe(42);

        // and now you don't ....
        model.unset("someProp");
        expect(model.get("someProp")).toBeUndefined();
        expect(model.get("someNum")).toBe(42);

    });

    it("Allows chaining of set and unset", function() {

        var model = new MVCzar.Model({
            initial: {
                someProp: "hey",
                someNum: 42
            }
        });

        // chaining of set and unset
        model.unset("someNum").set("qwe", 123).set("asd", {prop: "erty"}).unset("qwe");

        expect(model.get()).toEqual({
            someProp: "hey",
            asd: {
                prop: "erty"
            }
        });

    });

    it("Emits change events when changing properties", function() {

        var changeAny = 0,
            changeParticular = 0;

        var model = new MVCzar.Model({
            events: {
                change: function() {
                    changeAny++;
                },
                "change:someProp": function() {
                    changeParticular++;
                }
            }
        });

        model.set("aProp", "value");
        expect(changeAny).toBe(1);

        // setting the same value shouldn't trigger a change event
        model.set("aProp", "value");
        expect(changeAny).toBe(1);

        model.set("someProp", "someValue");
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        model.set("aProp", "different");
        expect(changeAny).toBe(3);
        expect(changeParticular).toBe(1);

        // setting multiple properties at once
        // should only trigger change event once
        // also maintaining same value for someProp
        // so shouldn't trigger that particular handler
        model.set({
            someProp: "someValue",
            tooManyProps: 10
        });
        expect(changeAny).toBe(4);
        expect(changeParticular).toBe(1);

        // unsetting a property that doesn't exist shouldn't trigger hander
        model.unset("notAProp");
        expect(changeAny).toBe(4);
        expect(changeParticular).toBe(1);

        // unsetting a property
        model.unset("aProp");
        expect(changeAny).toBe(5);
        expect(changeParticular).toBe(1);

        // unsetting a particular property
        model.unset("someProp");
        expect(changeAny).toBe(6);
        expect(changeParticular).toBe(2);

    });

    it("won't emit change events for initialisation values", function() {

        var changeAny = 0,
            changeParticular = 0;

        var model = new MVCzar.Model({
            events: {
                change: function() {
                    changeAny++;
                },
                "change:someProp": function() {
                    changeParticular++;
                }
            },
            initial: {
                someProp: "abc",
                another: true
            }
        });

        // no events should have fired
        expect(changeAny).toBe(0);
        expect(changeParticular).toBe(0);

        // although the event handlers are there ...
        model.set("someProp", "someValue");
        expect(changeAny).toBe(1);
        expect(changeParticular).toBe(1);

        model.set("aProp", "different");
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

    });

    it("allows you to set/unset properties silently", function() {

        var changeAny = 0,
            changeParticular = 0;

        var model = new MVCzar.Model({
            events: {
                change: function() {
                    changeAny++;
                },
                "change:someProp": function() {
                    changeParticular++;
                }
            }
        });

        // check handlers are attached
        model.set("aProp", "value");
        expect(changeAny).toBe(1);

        model.set("someProp", "someValue");
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        // silently change a single value
        model.set("aProp", "value2", true);
        model.set("someProp", "someValue2", true);
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        // silently change multiple values
        model.set({
            someProp: "someValue",
            tooManyProps: 10
        }, true);
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

        // silently unset a value
        model.unset("someProp", true);
        expect(changeAny).toBe(2);
        expect(changeParticular).toBe(1);

    });

    it("has a .toJSON() method that allows JSON.stringify to be used directly", function() {

        var obj = {
                someProp: "hey",
                someNum: 42,
                subObj: {
                    blah: "blah"
                }
            };

        var model = new MVCzar.Model({
            initial: obj
        });

        // calling .toJSON() directly will actually return a copy
        expect(model.toJSON()).toEqual(obj);
        expect(model.toJSON()).not.toBe(obj);

        // which will give the correct output when JSON.stringify is used
        expect(JSON.stringify(model)).toBe(JSON.stringify(obj));

    });

});<|endoftext|>