<commit_before>/*!
Kettle File DataSource

Copyright 2012-2013 OCAD University
Copyright 2016 OCAD University

Licensed under the New BSD license. You may not use this file except in
compliance with this License.

You may obtain a copy of the License at
https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
*/

"use strict";

var fluid = fluid || require("infusion"),
    kettle = fluid.registerNamespace("kettle"),
    http = http || require("http"),
    https = https || require("https"),
    urlModule = urlModule || require("url");

fluid.registerNamespace("kettle.dataSource");

/**** URL DATASOURCE SUPPORT ****/

fluid.defaults("kettle.dataSource.URL", {
    gradeNames: ["kettle.dataSource"],
    readOnlyGrade: "kettle.dataSource.URL",
    invokers: {
        resolveUrl: "kettle.dataSource.URL.resolveUrl", // url, termMap, directModel, noencode
        getImpl: {
            funcName: "kettle.dataSource.URL.handle",
            args: ["{that}", "{arguments}.0", "{arguments}.1"] // options, directModel
        }
    },
    components: {
        cookieJar: "{cookieJar}"
    },
    termMap: {}
});

fluid.defaults("kettle.dataSource.URL.writable", {
    gradeNames: ["kettle.dataSource.writable"],
    invokers: {
        setImpl: {
            funcName: "kettle.dataSource.URL.handle",
            args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] // options, directModel, model
        }
    }
});

/**
 * Resolves (expands) a url or path with respect to the "directModel" supplied to a dataSource's API (get or set). There are three rounds of expansion - firstly, the string
 * entries as the values in "termMap" will be looked up as paths within `directModel`. The resulting values will then be URI Encoded, unless their value
 * the termMap is prefixed with `noencode:`. Secondly,
 * this resolved termMap will be used for a round of the standard fluid.stringTemplate algorithm applied to the supplied URL. Finally, any argument `expand` will be
 * used to expand the entire URL.
 * @param  {String} url a url or path to expand.
 * @param  {Object String -> String} termMap A map of terms to be used for string interpolation. Any values which begin with the prefix `noencode:` will have this prefix stripped off, and
 * URI encoding will not be applied to the substituted value. After the value is normalised in this way, the remaining value may be used for indirection in the directModel if it
 * begins with the prefix "%", or else directly for interpolation
 * @param  {Object} directModel a set of data used to expand the url template.
 * @return {String} resolved/expanded url.
 */
kettle.dataSource.URL.resolveUrl = function (url, termMap, directModel, noencode) {
    var map = fluid.transform(termMap, function resolve(entry) {
        entry = String(entry);
        var encode = !noencode;
        if (entry.indexOf("noencode:") === 0) {
            encode = false;
            entry = entry.substring("noencode:".length);
        }
        var value = entry.charAt(0) === "%" ? fluid.get(directModel, entry.substring(1)) : entry;
        if (encode) {
            value = encodeURIComponent(value);
        }
        return value;
    });
    var replaced = fluid.stringTemplate(url, map);
    return replaced;
};

kettle.dataSource.URL.requestOptions = ["url", "protocol", "host", "hostname", "family", "port", "localAddress", "socketPath", "method", "path", "headers", "auth", "agent", "termMap"];

// TODO: Deal with the anomalous status of "charEncoding" - in theory it could be set per-request but currently can't be. Currently all
// "requestOptions" have a common fate in that they end up as the arguments to http.request. We need to split these into two levels,
// httpRequestOptions and the outer level with everything else. We also need to do something similar for kettle.dataSource.file

/** Assemble the `requestOptions` structure that will be sent to `http.request` by `kettle.dataSource.URL` by fusing together values from the user, the component
 * with filtration by a list of permitted options, e.g. those listed in `kettle.dataSource.URL.requestOptions`. A poorly factored method that needs to be
 * reformed as a proper merge pipeline.
 */

kettle.dataSource.URL.prepareRequestOptions = function (componentOptions, cookieJar, userOptions, permittedOptions, directModel, userStaticOptions, resolveUrl) {
    var staticOptions = fluid.filterKeys(componentOptions, permittedOptions);
    var requestOptions = fluid.extend(true, {headers: {}}, userStaticOptions, staticOptions, userOptions);
    var termMap = fluid.transform(requestOptions.termMap, encodeURIComponent);

    requestOptions.path = (resolveUrl || kettle.dataSource.URL.resolveUrl)(requestOptions.path, requestOptions.termMap, directModel);

    fluid.stringTemplate(requestOptions.path, termMap);
    if (cookieJar && cookieJar.cookie && componentOptions.storeCookies) {
        requestOptions.headers.Cookie = cookieJar.cookie;
    }
    return requestOptions;
};

/** Given an HTTP status code as returned by node's `http.IncomingMessage` class (or otherwise), determine whether it corresponds to
 * an error status. This performs a simple-minded check to see if it a number outside the range [200, 300).
 * @param {Number} statusCode The HTTP status code to be tested
 * @return {Boolean} `true` if the status code represents an error status
 */

kettle.dataSource.URL.isErrorStatus = function (statusCode) {
    return statusCode < 200 || statusCode >= 300;
};

/**
 * Handles calls to a URL data source's get and set.
 * @param  {kettle.dataSource.urlResolver} A URLResolver that will convert the contents of the
 * directModel supplied as the 3rd argument into a concrete URL used for this
 * HTTP request.
 * @param  options {Object} an options block that encodes:
 *     operation {String}: "set"/"get"
 *     notFoundIsEmpty {Boolean}: true if a missing file on read should count as a successful empty payload rather than a rejection
 *     writeMethod {String}: "PUT"/ "POST" (option - if not provided will be defaulted by the concrete dataSource implementation)
 * @param  directModel {Object} a model holding the coordinates of the data to be read or written
 * @param  model {Object} [Optional] - the payload to be written by this write operation
 * @return {Promise} a promise for the successful or failed datasource operation
 */
kettle.dataSource.URL.handle = function (that, userOptions, directModel, model) {
    var permittedOptions = kettle.dataSource.URL.requestOptions;
    var url = that.resolveUrl(that.options.url, that.options.termMap, directModel);
    var parsed = fluid.filterKeys(urlModule.parse(url, true), permittedOptions);
    var requestOptions = kettle.dataSource.URL.prepareRequestOptions(that.options, that.cookieJar, userOptions, permittedOptions, directModel, parsed);

    return kettle.dataSource.URL.handle.http(that, requestOptions, model);
};

// Attempt to parse the error response as JSON, but if failed, just stick it into "message" quietly
kettle.dataSource.URL.relayError = function (response, received, whileMsg) {
    var rejectPayload;
    try {
        rejectPayload = JSON.parse(received);
    } catch (e) {
        var message = typeof(received) === "string" && received.indexOf("<html") !== -1 ?
            kettle.extractHtmlError(received) : received;
        rejectPayload = {message: message};
    }
    rejectPayload.message = rejectPayload.message || rejectPayload.error;
    delete rejectPayload.error;
    rejectPayload.isError = true;
    rejectPayload.statusCode = response.statusCode;
    return kettle.upgradeError(rejectPayload, whileMsg);
};

/** Compute the callback to be supplied to `http.request` for this dataSource operation
 * @param requestOptions {Object} The fully merged options which were sent to `http.request`
 * @param promise {Promise} The outgoing promise to be returned to the user
 * @param whileMsg {String} A readable summary of the current operation (including HTTP method and URL) to be suffixed to any rejection message
 * @return {Function} The callback to be supplied to `http.request`
 */

kettle.dataSource.URL.httpCallback = function (requestOptions, promise, whileMsg) {
    return function (res) {
        var received = "";
        res.setEncoding(requestOptions.charEncoding);
        res.on("data", function onData(chunk) {
            received += chunk;
        });
        res.on("end", kettle.wrapCallback(
            function () {
                if (kettle.dataSource.URL.isErrorStatus(res.statusCode)) {
                    var relayed = kettle.dataSource.URL.relayError(res, received, whileMsg);
                    if (requestOptions.notFoundIsEmpty && relayed.statusCode === 404) {
                        promise.resolve(undefined);
                    } else {
                        promise.reject(relayed);
                    }
                } else {
                    promise.resolve(received);
                }
            })
        );
    };
};

/** Compute the listener to be attached to the `http.request` `error` event for this dataSource operation
 * @param promise {Promise} The outgoing promise to be returned to the user
 * @param whileMsg {String} A readable summary of the current operation (including HTTP method and URL) to be suffixed to any rejection message
 */

kettle.dataSource.URL.errorCallback = function (promise, whileMsg) {
    return function (error) {
        error.isError = true;
        promise.reject(kettle.upgradeError(error, whileMsg));
    };
};

/** Central strategy point for all HTTP-backed DataSource operations (both read and write).
 * Accumulates options to be sent to the underlying node.js `http.request` primitives, collects and interprets the
 * results back into promise resolutions.
 * @param that {Component} The DataSource itself
 * @param baseOptions {Object} A partially merged form of the options sent to the top-level `dataSource.get` method together with relevant
 * static options configured on the component. Information in the `directModel` argument has already been encoded into the url member.
 * @param data {String} The `model` argument sent to top-level `dataSource.get/set` after it has been pushed through the transform chain
 */

kettle.dataSource.URL.handle.http = function (that, baseOptions, data) {
    var promise = fluid.promise();
    var defaultOptions = {
        port: 80,
        method: "GET"
    };

    var dataBuffer;

    if (baseOptions.operation === "set") {
        dataBuffer = new Buffer(data);
        defaultOptions.headers = {
            "Content-Type": that.encoding.options.contentType,
            "Content-Length": dataBuffer.length
        };
        defaultOptions.method = baseOptions.writeMethod;
    }
    var requestOptions = fluid.extend(true, defaultOptions, baseOptions);
    var whileMsg = " while executing HTTP " + requestOptions.method + " on url " + requestOptions.url;
    fluid.log("DataSource Issuing " + (requestOptions.protocol.toUpperCase()).slice(0, -1) + " request with options ", requestOptions);
    promise.accumulateRejectionReason = function (originError) {
        return kettle.upgradeError(originError, whileMsg);
    };
    var callback = kettle.wrapCallback(kettle.dataSource.URL.httpCallback(requestOptions, promise, whileMsg));
    var req;
    if (requestOptions.protocol === "http:") {
        req = http.request(requestOptions, callback);
    } else if (requestOptions.protocol === "https:") {
        req = https.request(requestOptions, callback);
    } else {
        fluid.fail("kettle.dataSource.URL cannot handle unknown protocol "  + requestOptions.protocol);
    }
    req.on("error", kettle.wrapCallback(kettle.dataSource.URL.errorCallback(promise, whileMsg)));
    req.end(dataBuffer ? dataBuffer : data);
    return promise;
};
<commit_msg>GPII-2147: Replacing localhost with 127.0.0.1<commit_after>/*!
Kettle File DataSource

Copyright 2012-2013 OCAD University
Copyright 2016 OCAD University

Licensed under the New BSD license. You may not use this file except in
compliance with this License.

You may obtain a copy of the License at
https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
*/

"use strict";

var fluid = fluid || require("infusion"),
    kettle = fluid.registerNamespace("kettle"),
    http = http || require("http"),
    https = https || require("https"),
    urlModule = urlModule || require("url");

fluid.registerNamespace("kettle.dataSource");

/**** URL DATASOURCE SUPPORT ****/

fluid.defaults("kettle.dataSource.URL", {
    gradeNames: ["kettle.dataSource"],
    readOnlyGrade: "kettle.dataSource.URL",
    invokers: {
        resolveUrl: "kettle.dataSource.URL.resolveUrl", // url, termMap, directModel, noencode
        getImpl: {
            funcName: "kettle.dataSource.URL.handle",
            args: ["{that}", "{arguments}.0", "{arguments}.1"] // options, directModel
        }
    },
    components: {
        cookieJar: "{cookieJar}"
    },
    termMap: {}
});

fluid.defaults("kettle.dataSource.URL.writable", {
    gradeNames: ["kettle.dataSource.writable"],
    invokers: {
        setImpl: {
            funcName: "kettle.dataSource.URL.handle",
            args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] // options, directModel, model
        }
    }
});

/**
 * Resolves (expands) a url or path with respect to the "directModel" supplied to a dataSource's API (get or set). There are three rounds of expansion - firstly, the string
 * entries as the values in "termMap" will be looked up as paths within `directModel`. The resulting values will then be URI Encoded, unless their value
 * the termMap is prefixed with `noencode:`. Secondly,
 * this resolved termMap will be used for a round of the standard fluid.stringTemplate algorithm applied to the supplied URL. Finally, any argument `expand` will be
 * used to expand the entire URL.
 * @param  {String} url a url or path to expand.
 * @param  {Object String -> String} termMap A map of terms to be used for string interpolation. Any values which begin with the prefix `noencode:` will have this prefix stripped off, and
 * URI encoding will not be applied to the substituted value. After the value is normalised in this way, the remaining value may be used for indirection in the directModel if it
 * begins with the prefix "%", or else directly for interpolation
 * @param  {Object} directModel a set of data used to expand the url template.
 * @return {String} resolved/expanded url.
 */
kettle.dataSource.URL.resolveUrl = function (url, termMap, directModel, noencode) {
    var map = fluid.transform(termMap, function resolve(entry) {
        entry = String(entry);
        var encode = !noencode;
        if (entry.indexOf("noencode:") === 0) {
            encode = false;
            entry = entry.substring("noencode:".length);
        }
        var value = entry.charAt(0) === "%" ? fluid.get(directModel, entry.substring(1)) : entry;
        if (encode) {
            value = encodeURIComponent(value);
        }
        return value;
    });
    var replaced = fluid.stringTemplate(url, map);
    return replaced;
};

kettle.dataSource.URL.requestOptions = ["url", "protocol", "host", "hostname", "family", "port", "localAddress", "socketPath", "method", "path", "headers", "auth", "agent", "termMap"];

// TODO: Deal with the anomalous status of "charEncoding" - in theory it could be set per-request but currently can't be. Currently all
// "requestOptions" have a common fate in that they end up as the arguments to http.request. We need to split these into two levels,
// httpRequestOptions and the outer level with everything else. We also need to do something similar for kettle.dataSource.file

/** Assemble the `requestOptions` structure that will be sent to `http.request` by `kettle.dataSource.URL` by fusing together values from the user, the component
 * with filtration by a list of permitted options, e.g. those listed in `kettle.dataSource.URL.requestOptions`. A poorly factored method that needs to be
 * reformed as a proper merge pipeline.
 */

kettle.dataSource.URL.prepareRequestOptions = function (componentOptions, cookieJar, userOptions, permittedOptions, directModel, userStaticOptions, resolveUrl) {
    var staticOptions = fluid.filterKeys(componentOptions, permittedOptions);
    var requestOptions = fluid.extend(true, {headers: {}}, userStaticOptions, staticOptions, userOptions);
    if (requestOptions.hostname === "localhost" || requestOptions.host === "localhost") {
        requestOptions = fluid.extend(requestOptions, { hostname: "127.0.0.1", host: "127.0.0.1" });
    }
    var termMap = fluid.transform(requestOptions.termMap, encodeURIComponent);

    requestOptions.path = (resolveUrl || kettle.dataSource.URL.resolveUrl)(requestOptions.path, requestOptions.termMap, directModel);

    fluid.stringTemplate(requestOptions.path, termMap);
    if (cookieJar && cookieJar.cookie && componentOptions.storeCookies) {
        requestOptions.headers.Cookie = cookieJar.cookie;
    }
    return requestOptions;
};

/** Given an HTTP status code as returned by node's `http.IncomingMessage` class (or otherwise), determine whether it corresponds to
 * an error status. This performs a simple-minded check to see if it a number outside the range [200, 300).
 * @param {Number} statusCode The HTTP status code to be tested
 * @return {Boolean} `true` if the status code represents an error status
 */

kettle.dataSource.URL.isErrorStatus = function (statusCode) {
    return statusCode < 200 || statusCode >= 300;
};

/**
 * Handles calls to a URL data source's get and set.
 * @param  {kettle.dataSource.urlResolver} A URLResolver that will convert the contents of the
 * directModel supplied as the 3rd argument into a concrete URL used for this
 * HTTP request.
 * @param  options {Object} an options block that encodes:
 *     operation {String}: "set"/"get"
 *     notFoundIsEmpty {Boolean}: true if a missing file on read should count as a successful empty payload rather than a rejection
 *     writeMethod {String}: "PUT"/ "POST" (option - if not provided will be defaulted by the concrete dataSource implementation)
 * @param  directModel {Object} a model holding the coordinates of the data to be read or written
 * @param  model {Object} [Optional] - the payload to be written by this write operation
 * @return {Promise} a promise for the successful or failed datasource operation
 */
kettle.dataSource.URL.handle = function (that, userOptions, directModel, model) {
    var permittedOptions = kettle.dataSource.URL.requestOptions;
    var url = that.resolveUrl(that.options.url, that.options.termMap, directModel);
    var parsed = fluid.filterKeys(urlModule.parse(url, true), permittedOptions);
    var requestOptions = kettle.dataSource.URL.prepareRequestOptions(that.options, that.cookieJar, userOptions, permittedOptions, directModel, parsed);

    return kettle.dataSource.URL.handle.http(that, requestOptions, model);
};

// Attempt to parse the error response as JSON, but if failed, just stick it into "message" quietly
kettle.dataSource.URL.relayError = function (response, received, whileMsg) {
    var rejectPayload;
    try {
        rejectPayload = JSON.parse(received);
    } catch (e) {
        var message = typeof(received) === "string" && received.indexOf("<html") !== -1 ?
            kettle.extractHtmlError(received) : received;
        rejectPayload = {message: message};
    }
    rejectPayload.message = rejectPayload.message || rejectPayload.error;
    delete rejectPayload.error;
    rejectPayload.isError = true;
    rejectPayload.statusCode = response.statusCode;
    return kettle.upgradeError(rejectPayload, whileMsg);
};

/** Compute the callback to be supplied to `http.request` for this dataSource operation
 * @param requestOptions {Object} The fully merged options which were sent to `http.request`
 * @param promise {Promise} The outgoing promise to be returned to the user
 * @param whileMsg {String} A readable summary of the current operation (including HTTP method and URL) to be suffixed to any rejection message
 * @return {Function} The callback to be supplied to `http.request`
 */

kettle.dataSource.URL.httpCallback = function (requestOptions, promise, whileMsg) {
    return function (res) {
        var received = "";
        res.setEncoding(requestOptions.charEncoding);
        res.on("data", function onData(chunk) {
            received += chunk;
        });
        res.on("end", kettle.wrapCallback(
            function () {
                if (kettle.dataSource.URL.isErrorStatus(res.statusCode)) {
                    var relayed = kettle.dataSource.URL.relayError(res, received, whileMsg);
                    if (requestOptions.notFoundIsEmpty && relayed.statusCode === 404) {
                        promise.resolve(undefined);
                    } else {
                        promise.reject(relayed);
                    }
                } else {
                    promise.resolve(received);
                }
            })
        );
    };
};

/** Compute the listener to be attached to the `http.request` `error` event for this dataSource operation
 * @param promise {Promise} The outgoing promise to be returned to the user
 * @param whileMsg {String} A readable summary of the current operation (including HTTP method and URL) to be suffixed to any rejection message
 */

kettle.dataSource.URL.errorCallback = function (promise, whileMsg) {
    return function (error) {
        error.isError = true;
        promise.reject(kettle.upgradeError(error, whileMsg));
    };
};

/** Central strategy point for all HTTP-backed DataSource operations (both read and write).
 * Accumulates options to be sent to the underlying node.js `http.request` primitives, collects and interprets the
 * results back into promise resolutions.
 * @param that {Component} The DataSource itself
 * @param baseOptions {Object} A partially merged form of the options sent to the top-level `dataSource.get` method together with relevant
 * static options configured on the component. Information in the `directModel` argument has already been encoded into the url member.
 * @param data {String} The `model` argument sent to top-level `dataSource.get/set` after it has been pushed through the transform chain
 */

kettle.dataSource.URL.handle.http = function (that, baseOptions, data) {
    var promise = fluid.promise();
    var defaultOptions = {
        port: 80,
        method: "GET"
    };

    var dataBuffer;

    if (baseOptions.operation === "set") {
        dataBuffer = new Buffer(data);
        defaultOptions.headers = {
            "Content-Type": that.encoding.options.contentType,
            "Content-Length": dataBuffer.length
        };
        defaultOptions.method = baseOptions.writeMethod;
    }
    var requestOptions = fluid.extend(true, defaultOptions, baseOptions);
    var whileMsg = " while executing HTTP " + requestOptions.method + " on url " + requestOptions.url;
    fluid.log("DataSource Issuing " + (requestOptions.protocol.toUpperCase()).slice(0, -1) + " request with options ", requestOptions);
    promise.accumulateRejectionReason = function (originError) {
        return kettle.upgradeError(originError, whileMsg);
    };
    var callback = kettle.wrapCallback(kettle.dataSource.URL.httpCallback(requestOptions, promise, whileMsg));
    var req;
    if (requestOptions.protocol === "http:") {
        req = http.request(requestOptions, callback);
    } else if (requestOptions.protocol === "https:") {
        req = https.request(requestOptions, callback);
    } else {
        fluid.fail("kettle.dataSource.URL cannot handle unknown protocol "  + requestOptions.protocol);
    }
    req.on("error", kettle.wrapCallback(kettle.dataSource.URL.errorCallback(promise, whileMsg)));
    req.end(dataBuffer ? dataBuffer : data);
    return promise;
};
<|endoftext|>


<commit_before>class UI {
	topic(topic) {
		let _topic = topic;

		if (!_topic) {
			_topic = '';
		}

		select('#channel-console header input').value = _topic;
	}

	users(connectionId, channel) {
		// Clear users bar
		select('#users ul').innerHTML = '';
		select('#users header p').innerHTML = '';

		// Set up user list
		const network = Maid.sessions[connectionId];
		const users = network.sources[channel].users;
		let userList = [];

		userList = Object.keys(users);

		// Sort the user list based on rank and alphabetization
		userList.sort((a, b) => {
			const rankString = '\r+%@&~';
			const rankA = rankString.indexOf(users[a]);
			const rankB = rankString.indexOf(users[b]);
			let rankSort;

			if (rankA === rankB) {
				rankSort = 0;
			} else {
				if (rankA > rankB) {
					rankSort = 1;
				} else {
					rankSort = -1;
				}
			}

			if (rankSort === 0) {
				return a.toLowerCase() < b.toLowerCase() ? 1 : -1;
			}

			return rankSort;
		});

		userList.forEach((element) => {
			const identifyer = {
				rank: users[element],
				icon: '',
			};

			if (users[element] !== '') {
				switch (users[element]) {
					case '~': // Owners
						identifyer.icon = '';
						break;
					case '&': // Admins
						identifyer.icon = '';
						break;
					case '@': // Ops
						identifyer.icon = '';
						break;
					case '%': // Half-ops
						identifyer.icon = '';
						break;
					case '+': // Voiced
						identifyer.icon = '';
						break;
					default:
						identifyer.icon = '';
						break;
				}
			}

			// Display the user in the list
			select('#users ul').insertAdjacentHTML('afterbegin', Maid.Templates['src/client/views/users.hbs']({
				rank: identifyer.rank,
				icon: identifyer.icon,
				nick: element,
			}));
		});

		// Get user count
		select('#users header p').innerHTML = `${userList.length} users`;
	}
}
<commit_msg>Simplified topic code a a lot.<commit_after>class UI {
	topic(topic) {
		select('#channel-console header input').value = topic || '';
	}

	users(connectionId, channel) {
		// Clear users bar
		select('#users ul').innerHTML = '';
		select('#users header p').innerHTML = '';

		// Set up user list
		const network = Maid.sessions[connectionId];
		const users = network.sources[channel].users;
		let userList = [];

		userList = Object.keys(users);

		// Sort the user list based on rank and alphabetization
		userList.sort((a, b) => {
			const rankString = '\r+%@&~';
			const rankA = rankString.indexOf(users[a]);
			const rankB = rankString.indexOf(users[b]);
			let rankSort;

			if (rankA === rankB) {
				rankSort = 0;
			} else {
				if (rankA > rankB) {
					rankSort = 1;
				} else {
					rankSort = -1;
				}
			}

			if (rankSort === 0) {
				return a.toLowerCase() < b.toLowerCase() ? 1 : -1;
			}

			return rankSort;
		});

		userList.forEach((element) => {
			const identifyer = {
				rank: users[element],
				icon: '',
			};

			if (users[element] !== '') {
				switch (users[element]) {
					case '~': // Owners
						identifyer.icon = '';
						break;
					case '&': // Admins
						identifyer.icon = '';
						break;
					case '@': // Ops
						identifyer.icon = '';
						break;
					case '%': // Half-ops
						identifyer.icon = '';
						break;
					case '+': // Voiced
						identifyer.icon = '';
						break;
					default:
						identifyer.icon = '';
						break;
				}
			}

			// Display the user in the list
			select('#users ul').insertAdjacentHTML('afterbegin', Maid.Templates['src/client/views/users.hbs']({
				rank: identifyer.rank,
				icon: identifyer.icon,
				nick: element,
			}));
		});

		// Get user count
		select('#users header p').innerHTML = `${userList.length} users`;
	}
}
<|endoftext|>


<commit_before>import babel from 'rollup-plugin-babel';
import compiler from '@ampproject/rollup-plugin-closure-compiler';
import copy from 'rollup-plugin-copy';
import prettier from 'rollup-plugin-prettier';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

const PRETTY = !!process.env.PRETTY;
const SOURCE_DIR = 'packages/history';
const OUTPUT_DIR = 'build/history';

const modules = [
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/history.js`,
      format: 'esm',
      sourcemap: !PRETTY
    },
    external: ['@babel/runtime/helpers/esm/extends'],
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: [['@babel/preset-env', { loose: true }]],
        plugins: [
          'babel-plugin-dev-expression',
          ['@babel/plugin-transform-runtime', { useESModules: true }]
        ],
        runtimeHelpers: true
      }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      }),
      copy({
        targets: [
          { src: `${SOURCE_DIR}/package.json`, dest: OUTPUT_DIR },
          { src: 'README.md', dest: OUTPUT_DIR },
          { src: 'LICENSE', dest: OUTPUT_DIR }
        ],
        verbose: true
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  },
  ...['browser', 'hash', 'memory'].map(env => {
    return {
      input: `${SOURCE_DIR}/modules/${env}.js`,
      output: {
        file: `${OUTPUT_DIR}/${env}.js`,
        format: 'esm',
        sourcemap: !PRETTY
      },
      plugins: [
        babel({
          exclude: /node_modules/,
          presets: [['@babel/preset-env', { loose: true }]]
        }),
        compiler({
          compilation_level: 'SIMPLE_OPTIMIZATIONS',
          language_in: 'ECMASCRIPT5_STRICT',
          language_out: 'ECMASCRIPT5_STRICT'
        })
      ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
    };
  })
];

const webModules = [
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/history.development.js`,
      format: 'esm',
      sourcemap: !PRETTY
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: ['@babel/preset-modules'],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT_2018',
        language_out: 'ECMASCRIPT_2017'
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  },
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/history.production.min.js`,
      format: 'esm',
      sourcemap: !PRETTY
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: ['@babel/preset-modules'],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT_2018',
        language_out: 'ECMASCRIPT_2017'
      }),
      terser({ ecma: 8, safari10: true })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  }
];

const globals = [
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/umd/history.development.js`,
      format: 'umd',
      sourcemap: !PRETTY,
      name: 'HistoryLibrary'
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: [['@babel/preset-env', { loose: true }]],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  },
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/umd/history.production.min.js`,
      format: 'umd',
      sourcemap: !PRETTY,
      name: 'HistoryLibrary'
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: [['@babel/preset-env', { loose: true }]],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      }),
      terser()
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  }
];

const node = [
  {
    input: `${SOURCE_DIR}/modules/node-main.js`,
    output: {
      file: `${OUTPUT_DIR}/node-main.js`,
      format: 'cjs'
    },
    plugins: [
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  }
];

export default [...modules, ...webModules, ...globals, ...node];
<commit_msg>Remove unnecessary "use strict"<commit_after>import babel from 'rollup-plugin-babel';
import compiler from '@ampproject/rollup-plugin-closure-compiler';
import copy from 'rollup-plugin-copy';
import prettier from 'rollup-plugin-prettier';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

const PRETTY = !!process.env.PRETTY;
const SOURCE_DIR = 'packages/history';
const OUTPUT_DIR = 'build/history';

const modules = [
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/history.js`,
      format: 'esm',
      sourcemap: !PRETTY
    },
    external: ['@babel/runtime/helpers/esm/extends'],
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: [['@babel/preset-env', { loose: true }]],
        plugins: [
          'babel-plugin-dev-expression',
          ['@babel/plugin-transform-runtime', { useESModules: true }]
        ],
        runtimeHelpers: true
      }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      }),
      copy({
        targets: [
          { src: `${SOURCE_DIR}/package.json`, dest: OUTPUT_DIR },
          { src: 'README.md', dest: OUTPUT_DIR },
          { src: 'LICENSE', dest: OUTPUT_DIR }
        ],
        verbose: true
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  },
  ...['browser', 'hash', 'memory'].map(env => {
    return {
      input: `${SOURCE_DIR}/modules/${env}.js`,
      output: {
        file: `${OUTPUT_DIR}/${env}.js`,
        format: 'esm',
        sourcemap: !PRETTY
      },
      plugins: [
        babel({
          exclude: /node_modules/,
          presets: [['@babel/preset-env', { loose: true }]]
        }),
        compiler({
          compilation_level: 'SIMPLE_OPTIMIZATIONS',
          language_in: 'ECMASCRIPT5_STRICT',
          language_out: 'ECMASCRIPT5_STRICT'
        })
      ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
    };
  })
];

const webModules = [
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/history.development.js`,
      format: 'esm',
      sourcemap: !PRETTY
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: ['@babel/preset-modules'],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT_2018',
        language_out: 'ECMASCRIPT_2017'
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  },
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/history.production.min.js`,
      format: 'esm',
      sourcemap: !PRETTY
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: ['@babel/preset-modules'],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT_2018',
        language_out: 'ECMASCRIPT_2017'
      }),
      terser({ ecma: 8, safari10: true })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  }
];

const globals = [
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/umd/history.development.js`,
      format: 'umd',
      sourcemap: !PRETTY,
      name: 'HistoryLibrary'
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: [['@babel/preset-env', { loose: true }]],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  },
  {
    input: `${SOURCE_DIR}/modules/index.js`,
    output: {
      file: `${OUTPUT_DIR}/umd/history.production.min.js`,
      format: 'umd',
      sourcemap: !PRETTY,
      name: 'HistoryLibrary'
    },
    plugins: [
      babel({
        exclude: /node_modules/,
        presets: [['@babel/preset-env', { loose: true }]],
        plugins: ['babel-plugin-dev-expression']
      }),
      replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT5_STRICT',
        language_out: 'ECMASCRIPT5_STRICT'
      }),
      terser()
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  }
];

const node = [
  {
    input: `${SOURCE_DIR}/modules/node-main.js`,
    output: {
      file: `${OUTPUT_DIR}/node-main.js`,
      format: 'cjs'
    },
    plugins: [
      compiler({
        compilation_level: 'SIMPLE_OPTIMIZATIONS',
        language_in: 'ECMASCRIPT6',
        language_out: 'ECMASCRIPT5'
      })
    ].concat(PRETTY ? prettier({ parser: 'babel' }) : [])
  }
];

export default [...modules, ...webModules, ...globals, ...node];
<|endoftext|>


<commit_before>var gala = (function() {
  var LEFT_BTN = '';
  var RIGHT_BTN = '';
  var FIGURE = '

';
  var CAPTION = '

';

  var addButtons = function($gala, $images) {
    moveImages($images, 0); // First transition is ignored w/o this

    $gala.before(LEFT_BTN).prev().click(getOnNavLeft($gala, $images));
    $gala.after(RIGHT_BTN).next().click(getOnNavRight($gala, $images));

    updateButtonState($gala, 0, $images.length);
  };

  var addCaption = function($gala, $images) {
    $gala.wrap(FIGURE).after(CAPTION);
    updateCaptionText($gala, $images, 0);
  };

  var addKeyboardNav = function($gala, $images) {
    if ($('.gala').length != 1) {
      return; // Keyboard navigation works if there is only one gala
    }

    $(document).keydown(function(e) {
      switch (e.which) {
        case 37: // left
          (getOnNavLeft($gala, $images))();
          break;
        case 39: // right
          (getOnNavRight($gala, $images))();
          break;
        default:
          return;
      }
      e.preventDefault();
    });
  };

  var create = function(gala) {
    var $gala = $(gala);
    var $images = $gala.children('img');

    addCaption($gala, $images);
    if ($images.length > 1) {
      addButtons($gala, $images);
      addKeyboardNav($gala, $images);
    }

    $gala.css('display', 'inline-block');
    resize($gala, $images);
  };

  var getCurrentImage = function($gala) {
    return $gala.attr('data-index') || 0;
  };

  var getImageOffset = function($images) {
    return parseInt($images.first().css('left'), 10);
  };

  var getOnNavLeft = function($gala, $images) {
    return function() {
      var width = getRenderWidth($images);
      var offset = getImageOffset($images);
      var totalImages = $images.length;
      if (offset % width != 0 || totalImages <= 1 || offset >= 0) {
        return;
      }
      var currentImage = -offset/width-1;
      navigate($gala, $images, currentImage);
    };
  };

  var getOnNavRight = function($gala, $images) {
    return function() {
      var width = getRenderWidth($images);
      var offset = getImageOffset($images);
      var totalImages = $images.length;
      if (offset % width != 0 || totalImages <= 1 || offset <= -(width * (totalImages-1))) {
        return;
      }
      var currentImage = 1-offset/width;
      navigate($gala, $images, currentImage);
    };
  };

  var getRenderHeight = function($images) {
    return $images.first().height();
  };

  var getRenderWidth = function($images) {
    return $images.first().width();
  };

  var moveImages = function($images, currentImage) {
    var position = -(currentImage * getRenderWidth($images));
    $images.css('left', position+'px');
  };

  var navigate = function($gala, $images, currentImage) {
    var totalImages = $images.length;
    moveImages($images, currentImage);
    saveCurrentImage($gala, currentImage);
    updateButtonState($gala, currentImage, totalImages);
    updateCaptionText($gala, $images, currentImage);
  };

  var resize = function($gala, $images) {
    $gala.css('max-width', getRenderWidth($images)+'px');

    moveImages($images, getCurrentImage($gala));

    if ($images.length > 1) {
      var height = getRenderHeight($images) + 'px';
      $gala.prev().css('line-height', height);
      $gala.next().css('line-height', height);
    }
  };

  var saveCurrentImage = function($gala, currentImage) {
    $gala.attr('data-index', currentImage);
  };

  var updateButtonState = function($gala, currentImage, totalImages) {
    if (currentImage <= 0) {
      $gala.prev().addClass('gala-disabled');
    } else {
      $gala.prev().removeClass('gala-disabled');
    }

    if (currentImage >= totalImages-1) {
      $gala.next().addClass('gala-disabled');
    } else {
      $gala.next().removeClass('gala-disabled');
    }
  };

  var updateCaptionText = function($gala, $images, currentImage) {
    var $caption = $gala.siblings('figcaption');
    $caption.fadeOut(200, function() {
      $caption.text($images[currentImage].getAttribute('alt')).fadeIn(200);
    });
  };

  return {
    initialize: function() {
      $('.gala').each(function(index, gala) {
        create(gala);
      });
    },
    resize: function() {
      $('.gala').each(function(index, gala) {
        var $gala = $(gala);
        var $images = $gala.children('img');
        resize($gala, $images);
      });
    }
  }
})();

$(window).load(gala.initialize).resize(gala.resize);
<commit_msg>Simplified math for sliding the gala<commit_after>var gala = (function() {
  var LEFT_BTN = '';
  var RIGHT_BTN = '';
  var FIGURE = '

';
  var CAPTION = '

';

  var addButtons = function($gala, $images) {
    moveImages($images, 0); // First transition is ignored w/o this

    $gala.before(LEFT_BTN).prev().click(getOnNavLeft($gala, $images));
    $gala.after(RIGHT_BTN).next().click(getOnNavRight($gala, $images));

    updateButtonState($gala, 0, $images.length);
  };

  var addCaption = function($gala, $images) {
    $gala.wrap(FIGURE).after(CAPTION);
    updateCaptionText($gala, $images, 0);
  };

  var addKeyboardNav = function($gala, $images) {
    if ($('.gala').length != 1) {
      return; // Keyboard navigation works if there is only one gala
    }

    $(document).keydown(function(e) {
      switch (e.which) {
        case 37: // left
          (getOnNavLeft($gala, $images))();
          break;
        case 39: // right
          (getOnNavRight($gala, $images))();
          break;
        default:
          return;
      }
      e.preventDefault();
    });
  };

  var create = function(gala) {
    var $gala = $(gala);
    var $images = $gala.children('img');

    addCaption($gala, $images);
    if ($images.length > 1) {
      addButtons($gala, $images);
      addKeyboardNav($gala, $images);
    }

    $gala.css('display', 'inline-block');
    resize($gala, $images);
  };

  var getCurrentImage = function($gala) {
    return parseInt($gala.attr('data-index'), 10) || 0;
  };

  var getImageOffset = function($images) {
    return parseInt($images.first().css('left'), 10);
  };

  var getOnNavLeft = function($gala, $images) {
    return function() {
      var width = getRenderWidth($images);
      var currentImage = getCurrentImage($gala) - 1;
      var totalImages = $images.length;
      var offset = getImageOffset($images);
      if (totalImages <= 1 || currentImage < 0 || offset % width != 0) {
        return;
      }
      navigate($gala, $images, currentImage);
    };
  };

  var getOnNavRight = function($gala, $images) {
    return function() {
      var width = getRenderWidth($images);
      var currentImage = getCurrentImage($gala) + 1;
      var totalImages = $images.length;
      var offset = getImageOffset($images);
      if (totalImages <= 1 || currentImage > totalImages || offset % width != 0) {
        return;
      }
      navigate($gala, $images, currentImage);
    };
  };

  var getRenderHeight = function($images) {
    return $images.first().height();
  };

  var getRenderWidth = function($images) {
    return $images.first().width();
  };

  var moveImages = function($images, currentImage) {
    var position = -(currentImage * getRenderWidth($images));
    $images.css('left', position+'px');
  };

  var navigate = function($gala, $images, currentImage) {
    console.log(currentImage);
    var totalImages = $images.length;
    moveImages($images, currentImage);
    saveCurrentImage($gala, currentImage);
    updateButtonState($gala, currentImage, totalImages);
    updateCaptionText($gala, $images, currentImage);
  };

  var resize = function($gala, $images) {
    $gala.css('max-width', getRenderWidth($images)+'px');

    moveImages($images, getCurrentImage($gala));

    if ($images.length > 1) {
      var height = getRenderHeight($images) + 'px';
      $gala.prev().css('line-height', height);
      $gala.next().css('line-height', height);
    }
  };

  var saveCurrentImage = function($gala, currentImage) {
    $gala.attr('data-index', currentImage);
  };

  var updateButtonState = function($gala, currentImage, totalImages) {
    if (currentImage <= 0) {
      $gala.prev().addClass('gala-disabled');
    } else {
      $gala.prev().removeClass('gala-disabled');
    }

    if (currentImage >= totalImages-1) {
      $gala.next().addClass('gala-disabled');
    } else {
      $gala.next().removeClass('gala-disabled');
    }
  };

  var updateCaptionText = function($gala, $images, currentImage) {
    var $caption = $gala.siblings('figcaption');
    $caption.fadeOut(200, function() {
      $caption.text($images[currentImage].getAttribute('alt')).fadeIn(200);
    });
  };

  return {
    initialize: function() {
      $('.gala').each(function(index, gala) {
        create(gala);
      });
    },
    resize: function() {
      $('.gala').each(function(index, gala) {
        var $gala = $(gala);
        var $images = $gala.children('img');
        resize($gala, $images);
      });
    }
  }
})();

$(window).load(gala.initialize).resize(gala.resize);
<|endoftext|>


<commit_before>"use strict";
/**
 * @module BrowserSync.options
 */
module.exports = {

    /**
     * Browsersync includes a user-interface that is accessed via a separate port.
     * The UI allows to controls all devices, push sync updates and much more.
     * @property ui
     * @type Object
     * @param {Number} [port=3001]
     * @param {Number} [weinre.port=8080]
     * @since 2.0.0
     * @default false
     */
    ui: {
        port: 3001,
        weinre: {
            port: 8080
        }
    },

    /**
     * Browsersync can watch your files as you work. Changes you make will either
     * be injected into the page (CSS & images) or will cause all browsers to do
     * a full-page refresh. See [anymatch](https://github.com/es128/anymatch) for more information on glob patterns.
     * @property files
     * @type Array|String
     * @default false
     */
    files: false,

    /**
     * File watching options that get passed along to [Chokidar](https://github.com/paulmillr/chokidar).
     * Check their docs for available options
     * @property watchOptions
     * @type Object
     * @default undefined
     * @since 2.6.0
     */
    watchOptions: {
        /*
         persistent: true,

         ignored: '*.txt',
         ignoreInitial: false,
         followSymlinks: true,
         cwd: '.',

         usePolling: true,
         alwaysStat: false,
         depth: undefined,
         interval: 100,

         ignorePermissionErrors: false,
         atomic: true
         */
    },

    /**
     * Use the built-in static server for basic HTML/JS/CSS websites.
     * @property server
     * @type Object|Boolean
     * @default false
     */
    server: false,

    /**
     * Proxy an EXISTING vhost. Browsersync will wrap your vhost with a proxy URL to view your site.
     * @property proxy
     * @type String|Object|Boolean
     * @param {String} [target]
     * @param {Boolean} [ws] - Enable websocket proxying
     * @param {Function|Array} [middleware]
     * @param {Function} [reqHeaders]
     * @param {Array} [proxyRes]
     * @default false
     */
    proxy: false,

    /**
     * @property port
     * @type Number
     * @default 3000
     */
    port: 3000,

    middleware: false,

    /**
     * Add additional directories from which static
     * files should be served.
     */
    serveStatic: [],

    /**
     * Enable https for localhost development. **Note** - this is not needed for proxy
     * option as it will be inferred from your target url.
     * @property https
     * @type Boolean
     * @default undefined
     * @since 1.3.0
     */

    /**
     * Clicks, Scrolls & Form inputs on any device will be mirrored to all others.
     * @property ghostMode
     * @param {Boolean} [clicks=true]
     * @param {Boolean} [scroll=true]
     * @param {Boolean} [forms=true]
     * @param {Boolean} [forms.submit=true]
     * @param {Boolean} [forms.inputs=true]
     * @param {Boolean} [forms.toggles=true]
     * @type Object
     */
    ghostMode: {
        clicks: true,
        scroll: true,
        forms: {
            submit: true,
            inputs: true,
            toggles: true
        }
    },

    /**
     * Can be either "info", "debug", "warn", or "silent"
     * @property logLevel
     * @type String
     * @default info
     */
    logLevel: "info",

    /**
     * Change the console logging prefix. Useful if you're creating your
     * own project based on Browsersync
     * @property logPrefix
     * @type String
     * @default BS
     * @since 1.5.1
     */
    logPrefix: "BS",

    /**
     * @property logConnections
     * @type Boolean
     * @default false
     */
    logConnections: false,

    /**
     * @property logFileChanges
     * @type Boolean
     * @default true
     */
    logFileChanges: true,

    /**
     * Log the snippet to the console when you're in snippet mode (no proxy/server)
     * @property logSnippet
     * @type: Boolean
     * @default true
     * @since 1.5.2
     */
    logSnippet: true,

    /**
     * You can control how the snippet is injected
     * onto each page via a custom regex + function.
     * You can also provide patterns for certain urls
     * that should be ignored from the snippet injection.
     * @property snippetOptions
     * @since 2.0.0
     * @param {Boolean} [async] - should the script tags have the async attribute?
     * @param {Array} [blacklist]
     * @param {Array} [whitelist]
     * @param {RegExp} [rule.match=/<body[^>]*>/i]
     * @param {Function} [rule.fn=Function]
     * @type Object
     */
    snippetOptions: {
        async: true,
        whitelist: [],
        blacklist: [],
        rule: {
            match: /<body[^>]*>/i,
            fn: function (snippet, match) {
                return match + snippet;
            }
        }
    },

    /**
     * Add additional HTML rewriting rules.
     * @property rewriteRules
     * @since 2.4.0
     * @type Array|Boolean
     * @default false
     */
    rewriteRules: false,

    /**
     * @property tunnel
     * @type String|Boolean
     * @default null
     */

    /**
     * Some features of Browsersync (such as `xip` & `tunnel`) require an internet connection, but if you're
     * working offline, you can reduce start-up time by setting this option to `false`
     * @property online
     * @type Boolean
     * @default undefined
     */

    /**
     * Decide which URL to open automatically when Browsersync starts. Defaults to "local" if none set.
     * Can be true, `local`, `external`, `ui`, `ui-external`, `tunnel` or `false`
     * @property open
     * @type Boolean|String
     * @default true
     */
    open: "local",

    /**
     * @property browser
     * @type String|Array
     * @default default
     */
    browser: "default",

    /**
     * Requires an internet connection - useful for services such as [Typekit](https://typekit.com/)
     * as it allows you to configure domains such as `*.xip.io` in your kit settings
     * @property xip
     * @type Boolean
     * @default false
     */
    xip: false,

    hostnameSuffix: false,

    /**
     * Reload each browser when Browsersync is restarted.
     * @property reloadOnRestart
     * @type Boolean
     * @default false
     */
    reloadOnRestart: false,

    /**
     * The small pop-over notifications in the browser are not always needed/wanted.
     * @property notify
     * @type Boolean
     * @default true
     */
    notify: true,

    /**
     * @property scrollProportionally
     * @type Boolean
     * @default true
     */
    scrollProportionally: true,

    /**
     * @property scrollThrottle
     * @type Number
     * @default 0
     */
    scrollThrottle: 0,

    /**
     * Decide which technique should be used to restore
     * scroll position following a reload.
     * Can be `window.name` or `cookie`
     * @property scrollRestoreTechnique
     * @type String
     * @default 'window.name'
     */
    scrollRestoreTechnique: "window.name",

    /**
     * Time, in milliseconds, to wait before
     * instruction browser to reload/inject following a
     * file change event
     * @property reloadDelay
     * @type Number
     * @default 0
     */
    reloadDelay: 0,

    /**
     * Restrict the frequency in which browser:reload events
     * can be emitted to connected clients
     * @property reloadDebounce
     * @type Number
     * @default 0
     * @since 2.6.0
     */
    reloadDebounce: 0,

    /**
     * User provided plugins
     * @property plugins
     * @type Array
     * @default []
     * @since 2.6.0
     */
    plugins: [],

    /**
     * @property injectChanges
     * @type Boolean
     * @default true
     */
    injectChanges: true,

    /**
     * @property startPath
     * @type String|Null
     * @default null
     */
    startPath: null,

    /**
     * Whether to minify client script, or not.
     * @property minify
     * @type Boolean
     * @default true
     */
    minify: true,

    /**
     * @property host
     * @type String
     * @default null
     */
    host: null,

    /**
     * @property codeSync
     * @type Boolean
     * @default true
     */
    codeSync: true,

    /**
     * @property timestamps
     * @type Boolean
     * @default true
     */
    timestamps: true,

    clientEvents: [
        "scroll",
        "input:text",
        "input:toggles",
        "form:submit",
        "form:reset",
        "click"
    ],

    /**
     * Alter the script path for complete control over where the Browsersync
     * Javascript is served from. Whatever you return from this function
     * will be used as the script path.
     * @property scriptPath
     * @default undefined
     * @since 1.5.0
     * @type Function
     */

    /**
     * Configure the Socket.IO path and namespace & domain to avoid collisions.
     * @property socket
     * @param {String} [path="/browser-sync/socket.io"]
     * @param {String} [clientPath="/browser-sync"]
     * @param {String|Function} [namespace="/browser-sync"]
     * @param {String|Function} [domain=undefined]
     * @param {String|Function} [port=undefined]
     * @param {Object} [clients.heartbeatTimeout=5000]
     * @since 1.6.2
     * @type Object
     */
    socket: {
        socketIoOptions: {
            log: false,
        },
        path: "/browser-sync/socket.io",
        clientPath: "/browser-sync",
        namespace: "/browser-sync",
        clients: {
            heartbeatTimeout: 5000
        }
    },

    tagNames: {
        "less": "link",
        "scss": "link",
        "css":  "link",
        "jpg":  "img",
        "jpeg": "img",
        "png":  "img",
        "svg":  "img",
        "gif":  "img",
        "js":   "script"
    },

    injectFileTypes: ["css", "png", "jpg", "jpeg", "svg", "gif", "webp"],
    excludedFileTypes: [
        "js",
        "css",
        "pdf",
        "map",
        "svg",
        "ico",
        "woff",
        "json",
        "eot",
        "ttf",
        "png",
        "jpg",
        "jpeg",
        "webp",
        "gif",
        "mp4",
        "mp3",
        "3gp",
        "ogg",
        "ogv",
        "webm",
        "m4a",
        "flv",
        "wmv",
        "avi",
        "swf",
        "scss"
    ]
};
<commit_msg>docs: Add notes about serveStatic option<commit_after>"use strict";
/**
 * @module BrowserSync.options
 */
module.exports = {

    /**
     * Browsersync includes a user-interface that is accessed via a separate port.
     * The UI allows to controls all devices, push sync updates and much more.
     * @property ui
     * @type Object
     * @param {Number} [port=3001]
     * @param {Number} [weinre.port=8080]
     * @since 2.0.0
     * @default false
     */
    ui: {
        port: 3001,
        weinre: {
            port: 8080
        }
    },

    /**
     * Browsersync can watch your files as you work. Changes you make will either
     * be injected into the page (CSS & images) or will cause all browsers to do
     * a full-page refresh. See [anymatch](https://github.com/es128/anymatch) for more information on glob patterns.
     * @property files
     * @type Array|String
     * @default false
     */
    files: false,

    /**
     * File watching options that get passed along to [Chokidar](https://github.com/paulmillr/chokidar).
     * Check their docs for available options
     * @property watchOptions
     * @type Object
     * @default undefined
     * @since 2.6.0
     */
    watchOptions: {
        /*
         persistent: true,

         ignored: '*.txt',
         ignoreInitial: false,
         followSymlinks: true,
         cwd: '.',

         usePolling: true,
         alwaysStat: false,
         depth: undefined,
         interval: 100,

         ignorePermissionErrors: false,
         atomic: true
         */
    },

    /**
     * Use the built-in static server for basic HTML/JS/CSS websites.
     * @property server
     * @type Object|Boolean
     * @default false
     */
    server: false,

    /**
     * Proxy an EXISTING vhost. Browsersync will wrap your vhost with a proxy URL to view your site.
     * @property proxy
     * @type String|Object|Boolean
     * @param {String} [target]
     * @param {Boolean} [ws] - Enable websocket proxying
     * @param {Function|Array} [middleware]
     * @param {Function} [reqHeaders]
     * @param {Array} [proxyRes]
     * @default false
     */
    proxy: false,

    /**
     * @property port
     * @type Number
     * @default 3000
     */
    port: 3000,

    middleware: false,

    /**
     * Add additional directories from which static
     * files should be served. Should only be used in `proxy` or `snippet`
     * mode.
     * @property serveStatic
     * @type Array
     * @default []
     * @since 2.8.0
     */
    serveStatic: [],

    /**
     * Enable https for localhost development. **Note** - this is not needed for proxy
     * option as it will be inferred from your target url.
     * @property https
     * @type Boolean
     * @default undefined
     * @since 1.3.0
     */

    /**
     * Clicks, Scrolls & Form inputs on any device will be mirrored to all others.
     * @property ghostMode
     * @param {Boolean} [clicks=true]
     * @param {Boolean} [scroll=true]
     * @param {Boolean} [forms=true]
     * @param {Boolean} [forms.submit=true]
     * @param {Boolean} [forms.inputs=true]
     * @param {Boolean} [forms.toggles=true]
     * @type Object
     */
    ghostMode: {
        clicks: true,
        scroll: true,
        forms: {
            submit: true,
            inputs: true,
            toggles: true
        }
    },

    /**
     * Can be either "info", "debug", "warn", or "silent"
     * @property logLevel
     * @type String
     * @default info
     */
    logLevel: "info",

    /**
     * Change the console logging prefix. Useful if you're creating your
     * own project based on Browsersync
     * @property logPrefix
     * @type String
     * @default BS
     * @since 1.5.1
     */
    logPrefix: "BS",

    /**
     * @property logConnections
     * @type Boolean
     * @default false
     */
    logConnections: false,

    /**
     * @property logFileChanges
     * @type Boolean
     * @default true
     */
    logFileChanges: true,

    /**
     * Log the snippet to the console when you're in snippet mode (no proxy/server)
     * @property logSnippet
     * @type: Boolean
     * @default true
     * @since 1.5.2
     */
    logSnippet: true,

    /**
     * You can control how the snippet is injected
     * onto each page via a custom regex + function.
     * You can also provide patterns for certain urls
     * that should be ignored from the snippet injection.
     * @property snippetOptions
     * @since 2.0.0
     * @param {Boolean} [async] - should the script tags have the async attribute?
     * @param {Array} [blacklist]
     * @param {Array} [whitelist]
     * @param {RegExp} [rule.match=/<body[^>]*>/i]
     * @param {Function} [rule.fn=Function]
     * @type Object
     */
    snippetOptions: {
        async: true,
        whitelist: [],
        blacklist: [],
        rule: {
            match: /<body[^>]*>/i,
            fn: function (snippet, match) {
                return match + snippet;
            }
        }
    },

    /**
     * Add additional HTML rewriting rules.
     * @property rewriteRules
     * @since 2.4.0
     * @type Array|Boolean
     * @default false
     */
    rewriteRules: false,

    /**
     * @property tunnel
     * @type String|Boolean
     * @default null
     */

    /**
     * Some features of Browsersync (such as `xip` & `tunnel`) require an internet connection, but if you're
     * working offline, you can reduce start-up time by setting this option to `false`
     * @property online
     * @type Boolean
     * @default undefined
     */

    /**
     * Decide which URL to open automatically when Browsersync starts. Defaults to "local" if none set.
     * Can be true, `local`, `external`, `ui`, `ui-external`, `tunnel` or `false`
     * @property open
     * @type Boolean|String
     * @default true
     */
    open: "local",

    /**
     * @property browser
     * @type String|Array
     * @default default
     */
    browser: "default",

    /**
     * Requires an internet connection - useful for services such as [Typekit](https://typekit.com/)
     * as it allows you to configure domains such as `*.xip.io` in your kit settings
     * @property xip
     * @type Boolean
     * @default false
     */
    xip: false,

    hostnameSuffix: false,

    /**
     * Reload each browser when Browsersync is restarted.
     * @property reloadOnRestart
     * @type Boolean
     * @default false
     */
    reloadOnRestart: false,

    /**
     * The small pop-over notifications in the browser are not always needed/wanted.
     * @property notify
     * @type Boolean
     * @default true
     */
    notify: true,

    /**
     * @property scrollProportionally
     * @type Boolean
     * @default true
     */
    scrollProportionally: true,

    /**
     * @property scrollThrottle
     * @type Number
     * @default 0
     */
    scrollThrottle: 0,

    /**
     * Decide which technique should be used to restore
     * scroll position following a reload.
     * Can be `window.name` or `cookie`
     * @property scrollRestoreTechnique
     * @type String
     * @default 'window.name'
     */
    scrollRestoreTechnique: "window.name",

    /**
     * Time, in milliseconds, to wait before
     * instruction browser to reload/inject following a
     * file change event
     * @property reloadDelay
     * @type Number
     * @default 0
     */
    reloadDelay: 0,

    /**
     * Restrict the frequency in which browser:reload events
     * can be emitted to connected clients
     * @property reloadDebounce
     * @type Number
     * @default 0
     * @since 2.6.0
     */
    reloadDebounce: 0,

    /**
     * User provided plugins
     * @property plugins
     * @type Array
     * @default []
     * @since 2.6.0
     */
    plugins: [],

    /**
     * @property injectChanges
     * @type Boolean
     * @default true
     */
    injectChanges: true,

    /**
     * @property startPath
     * @type String|Null
     * @default null
     */
    startPath: null,

    /**
     * Whether to minify client script, or not.
     * @property minify
     * @type Boolean
     * @default true
     */
    minify: true,

    /**
     * @property host
     * @type String
     * @default null
     */
    host: null,

    /**
     * @property codeSync
     * @type Boolean
     * @default true
     */
    codeSync: true,

    /**
     * @property timestamps
     * @type Boolean
     * @default true
     */
    timestamps: true,

    clientEvents: [
        "scroll",
        "input:text",
        "input:toggles",
        "form:submit",
        "form:reset",
        "click"
    ],

    /**
     * Alter the script path for complete control over where the Browsersync
     * Javascript is served from. Whatever you return from this function
     * will be used as the script path.
     * @property scriptPath
     * @default undefined
     * @since 1.5.0
     * @type Function
     */

    /**
     * Configure the Socket.IO path and namespace & domain to avoid collisions.
     * @property socket
     * @param {String} [path="/browser-sync/socket.io"]
     * @param {String} [clientPath="/browser-sync"]
     * @param {String|Function} [namespace="/browser-sync"]
     * @param {String|Function} [domain=undefined]
     * @param {String|Function} [port=undefined]
     * @param {Object} [clients.heartbeatTimeout=5000]
     * @since 1.6.2
     * @type Object
     */
    socket: {
        socketIoOptions: {
            log: false,
        },
        path: "/browser-sync/socket.io",
        clientPath: "/browser-sync",
        namespace: "/browser-sync",
        clients: {
            heartbeatTimeout: 5000
        }
    },

    tagNames: {
        "less": "link",
        "scss": "link",
        "css":  "link",
        "jpg":  "img",
        "jpeg": "img",
        "png":  "img",
        "svg":  "img",
        "gif":  "img",
        "js":   "script"
    },

    injectFileTypes: ["css", "png", "jpg", "jpeg", "svg", "gif", "webp"],
    excludedFileTypes: [
        "js",
        "css",
        "pdf",
        "map",
        "svg",
        "ico",
        "woff",
        "json",
        "eot",
        "ttf",
        "png",
        "jpg",
        "jpeg",
        "webp",
        "gif",
        "mp4",
        "mp3",
        "3gp",
        "ogg",
        "ogv",
        "webm",
        "m4a",
        "flv",
        "wmv",
        "avi",
        "swf",
        "scss"
    ]
};
<|endoftext|>


<commit_before>const udp = require('dgram');
const dns = require('dns');
const { VoiceOPCodes } = require('../../util/Constants');
const EventEmitter = require('events');
const { Error } = require('../../errors');

/**
 * Represents a UDP client for a Voice Connection.
 * @extends {EventEmitter}
 * @private
 */
class VoiceConnectionUDPClient extends EventEmitter {
  constructor(voiceConnection) {
    super();

    /**
     * The voice connection that this UDP client serves
     * @type {VoiceConnection}
     */
    this.voiceConnection = voiceConnection;

    /**
     * The UDP socket
     * @type {?Socket}
     */
    this.socket = null;

    /**
     * The address of the Discord voice server
     * @type {?string}
     */
    this.discordAddress = null;

    /**
     * The local IP address
     * @type {?string}
     */
    this.localAddress = null;

    /**
     * The local port
     * @type {?string}
     */
    this.localPort = null;

    this.voiceConnection.on('closing', this.shutdown.bind(this));
  }

  shutdown() {
    if (this.socket) {
      this.socket.removeAllListeners('message');
      try {
        this.socket.close();
      } finally {
        this.socket = null;
      }
    }
  }

  /**
   * The port of the Discord voice server
   * @type {number}
   * @readonly
   */
  get discordPort() {
    return this.voiceConnection.authentication.port;
  }

  /**
   * Tries to resolve the voice server endpoint to an address.
   * @returns {Promise<string>}
   */
  findEndpointAddress() {
    return new Promise((resolve, reject) => {
      dns.lookup(this.voiceConnection.authentication.endpoint, (error, address) => {
        if (error) {
          reject(error);
          return;
        }
        this.discordAddress = address;
        resolve(address);
      });
    });
  }

  /**
   * Send a packet to the UDP client.
   * @param {Object} packet The packet to send
   * @returns {Promise<Object>}
   */
  send(packet) {
    return new Promise((resolve, reject) => {
      if (!this.socket) throw new Error('UDP_SEND_FAIL');
      if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED');
      this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
        if (error) reject(error); else resolve(packet);
      });
    });
  }

  createUDPSocket(address) {
    this.discordAddress = address;
    const socket = this.socket = udp.createSocket('udp4');

    socket.once('message', message => {
      const packet = parseLocalPacket(message);
      if (packet.error) {
        this.emit('error', packet.error);
        return;
      }

      this.localAddress = packet.address;
      this.localPort = packet.port;

      this.voiceConnection.sockets.ws.sendPacket({
        op: VoiceOPCodes.SELECT_PROTOCOL,
        d: {
          protocol: 'udp',
          data: {
            address: packet.address,
            port: packet.port,
            mode: 'xsalsa20_poly1305',
          },
        },
      });
    });

    const blankMessage = Buffer.alloc(70);
    blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4);
    this.send(blankMessage);
  }
}

function parseLocalPacket(message) {
  try {
    const packet = Buffer.from(message);
    let address = '';
    for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
    const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
    return { address, port };
  } catch (error) {
    return { error };
  }
}

module.exports = VoiceConnectionUDPClient;
<commit_msg>Fix #2018 (rapid joining/leaving of voice channel causes a crash)<commit_after>const udp = require('dgram');
const dns = require('dns');
const { VoiceOPCodes } = require('../../util/Constants');
const EventEmitter = require('events');
const { Error } = require('../../errors');

/**
 * Represents a UDP client for a Voice Connection.
 * @extends {EventEmitter}
 * @private
 */
class VoiceConnectionUDPClient extends EventEmitter {
  constructor(voiceConnection) {
    super();

    /**
     * The voice connection that this UDP client serves
     * @type {VoiceConnection}
     */
    this.voiceConnection = voiceConnection;

    /**
     * The UDP socket
     * @type {?Socket}
     */
    this.socket = null;

    /**
     * The address of the Discord voice server
     * @type {?string}
     */
    this.discordAddress = null;

    /**
     * The local IP address
     * @type {?string}
     */
    this.localAddress = null;

    /**
     * The local port
     * @type {?string}
     */
    this.localPort = null;

    this.voiceConnection.on('closing', this.shutdown.bind(this));
  }

  shutdown() {
    if (this.socket) {
      this.socket.removeAllListeners('message');
      try {
        this.socket.close();
      } finally {
        this.socket = null;
      }
    }
  }

  /**
   * The port of the Discord voice server
   * @type {number}
   * @readonly
   */
  get discordPort() {
    return this.voiceConnection.authentication.port;
  }

  /**
   * Tries to resolve the voice server endpoint to an address.
   * @returns {Promise<string>}
   */
  findEndpointAddress() {
    return new Promise((resolve, reject) => {
      dns.lookup(this.voiceConnection.authentication.endpoint, (error, address) => {
        if (error) {
          reject(error);
          return;
        }
        this.discordAddress = address;
        resolve(address);
      });
    });
  }

  /**
   * Send a packet to the UDP client.
   * @param {Object} packet The packet to send
   * @returns {Promise<Object>}
   */
  send(packet) {
    return new Promise((resolve, reject) => {
      if (!this.socket) throw new Error('UDP_SEND_FAIL');
      if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED');
      this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
        if (error) reject(error); else resolve(packet);
      });
    });
  }

  createUDPSocket(address) {
    this.discordAddress = address;
    const socket = this.socket = udp.createSocket('udp4');

    socket.once('message', message => {

      // Stop if the sockets have been deleted because the connection has been closed already
      if (!this.voiceConnection.sockets.ws) return;

      const packet = parseLocalPacket(message);
      if (packet.error) {
        this.emit('error', packet.error);
        return;
      }

      this.localAddress = packet.address;
      this.localPort = packet.port;

      this.voiceConnection.sockets.ws.sendPacket({
        op: VoiceOPCodes.SELECT_PROTOCOL,
        d: {
          protocol: 'udp',
          data: {
            address: packet.address,
            port: packet.port,
            mode: 'xsalsa20_poly1305',
          },
        },
      });
    });

    const blankMessage = Buffer.alloc(70);
    blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4);
    this.send(blankMessage);
  }
}

function parseLocalPacket(message) {
  try {
    const packet = Buffer.from(message);
    let address = '';
    for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
    const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
    return { address, port };
  } catch (error) {
    return { error };
  }
}

module.exports = VoiceConnectionUDPClient;
<|endoftext|>


<commit_before>var Game = function(boardString){
  var board = '';
  this.board = '';
  if (arguments.length === 1) {
    this.board = this.toArray(boardString);
  } else {
    function random() {
      return Math.floor(Math.random() * 10 + 6);
    };
    var firstTwo = random(), secondTwo = random();
    for ( var i = 0; i < 16; i++ ) {
      if (i === firstTwo || i === secondTwo) {
        board += '2';
      } else {
        board += '0';
      };
    };
    this.board = this.toArray(board);
  }
};

Game.prototype = {

  toString: function() {
    that = this
    $('.row').each(function(outer) {
      $(this).children().each(function(inner) {
        $(this).html("
" + that.board[outer][inner] + "
");
      });
    });
  },

  toArray: function(chars) {
    var boardArray = [];
    for( var i = 0; i < 16; i += 4) {
      var subarray = chars.slice(i, 4 + i);
      boardArray.push(subarray.split(''));
    }
    return boardArray;
  },

    filterNonZeros: function(matrix) {
    filtered = [];
    matrix.forEach(function(row) {
      filtered.push(row.filter(function(value) {
        return value === '0';
      }));
    });
    return filtered
  },

  randomNumber: function() {
    var randoNumber = (Math.random() < .8) ? '2' : '4';
    return randoNumber
  },

  spawn: function() {
      var index = (Math.floor(Math.random() * 4));
      var sampledArray = _.sample(this.board);
      if (sampledArray[index] === "0") {
        sampledArray.splice(index, 1, this.randomNumber());
      } else {
        this.spawn();
      }

  },

  filterZeros: function(matrix) {
    filtered = []
    matrix.forEach(function(row) {
      filtered.push(row.filter(function(value) {
        return value !== '0';
      }));
    });
    return filtered
  },

  leftAddNumbers: function(subarrays) {
    subarrays.forEach(function(arr) {
      if (arr.length > 1){
        for(var i = 0; i < arr.length; i++){
          if (arr[i] === arr[i+1]){
            arr[i] = (parseInt(arr[i]) + parseInt(arr[i+1])).toString();
            arr.splice(i+1, 1);
          }
        }
      }
    })
    return subarrays;
  },

  rightAddNumbers: function(subarrays) {
    subarrays.forEach(function(arr) {
      if (arr.length > 1){
        for(var i = arr.length - 1; i > 0; i--) {
          if (arr[i] === arr[i-1]){
            arr[i] = (parseInt(arr[i]) + parseInt(arr[i-1])).toString();
            arr.splice(i-1, 1);
          }
        }
      }
    })
    return subarrays;

  },

  move: function(dir) {
    that = this;
    switch (dir) {
      case 'left':
        this.board = this.leftAddNumbers(this.filterZeros(this.board))
        this.board.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.push('0');
          }
        })
        break;
      case 'right':
        this.board = this.rightAddNumbers(this.filterZeros(this.board));
        this.board.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.unshift('0');
          }
        })
        break;
      case 'up':
        var transposedArray = _.zip.apply(null, this.board);
        transposedArray = this.leftAddNumbers(this.filterZeros(transposedArray));
        transposedArray.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.push('0');
          }
        })
        this.board = _.zip.apply(null, transposedArray);
        break;
      case 'down':
        var transposedArray = _.zip.apply(null, this.board);
        transposedArray = this.rightAddNumbers(this.filterZeros(transposedArray));
        transposedArray.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.unshift('0');
          }
        })
        this.board = _.zip.apply(null, transposedArray);
        break;
    }
  that.spawn()
  }
};

<commit_msg>fixes indentation<commit_after>var Game = function(boardString){
  var board = '';
  this.board = '';
  if (arguments.length === 1) {
    this.board = this.toArray(boardString);
  } else {
    function random() {
      return Math.floor(Math.random() * 10 + 6);
    };
    var firstTwo = random(), secondTwo = random();
    for ( var i = 0; i < 16; i++ ) {
      if (i === firstTwo || i === secondTwo) {
        board += '2';
      } else {
        board += '0';
      };
    };
    this.board = this.toArray(board);
  }
};

Game.prototype = {

  toString: function() {
    that = this
    $('.row').each(function(outer) {
      $(this).children().each(function(inner) {
        $(this).html("
" + that.board[outer][inner] + "
");
      });
    });
  },

  toArray: function(chars) {
    var boardArray = [];
    for( var i = 0; i < 16; i += 4) {
      var subarray = chars.slice(i, 4 + i);
      boardArray.push(subarray.split(''));
    }
    return boardArray;
  },

  filterNonZeros: function(matrix) {
    filtered = [];
    matrix.forEach(function(row) {
      filtered.push(row.filter(function(value) {
        return value === '0';
      }));
    });
    return filtered
  },

  randomNumber: function() {
    var randoNumber = (Math.random() < .8) ? '2' : '4';
    return randoNumber
  },

  spawn: function() {
      var index = (Math.floor(Math.random() * 4));
      var sampledArray = _.sample(this.board);
      if (sampledArray[index] === "0") {
        sampledArray.splice(index, 1, this.randomNumber());
      } else {
        this.spawn();
      }

  },

  filterZeros: function(matrix) {
    filtered = []
    matrix.forEach(function(row) {
      filtered.push(row.filter(function(value) {
        return value !== '0';
      }));
    });
    return filtered
  },

  leftAddNumbers: function(subarrays) {
    subarrays.forEach(function(arr) {
      if (arr.length > 1){
        for(var i = 0; i < arr.length; i++){
          if (arr[i] === arr[i+1]){
            arr[i] = (parseInt(arr[i]) + parseInt(arr[i+1])).toString();
            arr.splice(i+1, 1);
          }
        }
      }
    })
    return subarrays;
  },

  rightAddNumbers: function(subarrays) {
    subarrays.forEach(function(arr) {
      if (arr.length > 1){
        for(var i = arr.length - 1; i > 0; i--) {
          if (arr[i] === arr[i-1]){
            arr[i] = (parseInt(arr[i]) + parseInt(arr[i-1])).toString();
            arr.splice(i-1, 1);
          }
        }
      }
    })
    return subarrays;

  },

  move: function(dir) {
    that = this;
    switch (dir) {
      case 'left':
        this.board = this.leftAddNumbers(this.filterZeros(this.board))
        this.board.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.push('0');
          }
        })
        break;
      case 'right':
        this.board = this.rightAddNumbers(this.filterZeros(this.board));
        this.board.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.unshift('0');
          }
        })
        break;
      case 'up':
        var transposedArray = _.zip.apply(null, this.board);
        transposedArray = this.leftAddNumbers(this.filterZeros(transposedArray));
        transposedArray.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.push('0');
          }
        })
        this.board = _.zip.apply(null, transposedArray);
        break;
      case 'down':
        var transposedArray = _.zip.apply(null, this.board);
        transposedArray = this.rightAddNumbers(this.filterZeros(transposedArray));
        transposedArray.forEach(function(row) {
          for( var i = 0; row.length < 4; i++ ) {
            row.unshift('0');
          }
        })
        this.board = _.zip.apply(null, transposedArray);
        break;
    }
  that.spawn()
  }
};

<|endoftext|>