<commit_before>
define([
        "core/framework/TundraSDK",
        "core/framework/ITundraPlugin"
    ], function(TundraSDK, ITundraPlugin) {

/**
    This plugin provides a basic user interface for connecting to a server with a username.

    @class LoginScreenPlugin
    @extends ITundraPlugin
    @constructor
*/
var LoginScreenPlugin = ITundraPlugin.$extend(
{
    __init__ : function()
    {
        this.$super("LoginScreenPlugin");

        /**
            Login properties that will be sent to the server. Application/plugin code can set custom properties to this
            before a connection is made. This property map will be cleared when server connection is disconnected.

            Note: 'username' will always be overwritten from the UI controls of this plugin.

            @property loginProperties
            @type Object
        */
        this.loginProperties = {};

        // Private
        this.ui = {};
        this.loadingScreen = null;
    },

    __classvars__ :
    {
        /** If login controls are enabled and should be shown.
            LoginScreenPlugin.LoginControlsEnabled = false; */
        LoginControlsEnabled : true,

        /** If loading screen is enabled and should be shown.
            LoginScreenPlugin.LoadingScreenEnabled = false; */
        LoadingScreenEnabled : true,

        /** Text that appears to the loading screen while
            the world assets are being loaded */
        LoadingScreenConnectingText : "Loading 3D Space",

        /// Loading screen header text
        LoadingScreenHeaderText     : "realXtend WebTundra",
        /// Loading screen header link url
        LoadingScreenHeaderLinkUrl  : "https://github.com/realXtend/WebTundra"
    },

    initialize : function()
    {
        this.framework.client.onConnectionError(this, this.onConnectionError);
        this.framework.client.onConnected(this, this.onConnectedToServer);
        this.framework.client.onDisconnected(this, this.onDisconnectedFromServer);
        this.framework.ui.onWindowResize(this, this.onWindowResized);

        this.createLoginControls();
        this.showLoadingScreen();
    },

    createLoginControls : function()
    {
        if (!LoginScreenPlugin.LoginControlsEnabled)
            return;

        this.ui.loginControls = $("
", {
            id      : "login-controls"
        });
        this.ui.loginHost = $("<input/>", {
            id      : "login-host",
            type    : "text",
            value   : "ws://127.0.0.1:2345"
        });
        this.ui.loginUsername = $("<input/>", {
            id      : "login-username",
            type    : "text",
            value   : "WebTundra User"
        });
        this.ui.loginError = $("
", {
            id      : "login-error",
            css : {
                "padding"     : 15,
                "font-size"   : 16,
                "color"       : "red"
            }
        });
        this.ui.loginButton = $("<button/>", {
            id      : "login-button",
            text    : "Connect",
            css : {
                "font-size"   : 12,
                "font-weight" : "bold"
            }
        }).button();

        this.ui.loginControls.css({
            "position"          : "absolute",
            "top"               : 180,
            "left"              : 0,
            "width"             : "100%",
            "padding"           : 10,
            "border"            : 0,
            "z-index"           : 6,
            "text-align"        : "center",
            "font-family"       : "Arial",
            "font-size"         : 14,
            "font-weight"       : "bold",
            "background-color"  : "transparent"
        });

        var inputs = [ this.ui.loginHost, this.ui.loginUsername, this.ui.loginButton ];
        for (var i = 0; i < inputs.length; i++)
        {
            inputs[i].css({
                "min-width"     : 100,
                "max-height"    : 25,
                "margin-left"   : 6,
                "margin-right"  : 9,
                "padding"       : inputs[i] === this.ui.loginButton ? 0 : 3,
                "border"        : "1px solid lightgrey",
                "border-radius" : 4
            });
        }

        this.ui.loginControls.append("Host");
        this.ui.loginControls.append(this.ui.loginHost);
        this.ui.loginControls.append("Username");
        this.ui.loginControls.append(this.ui.loginUsername);
        this.ui.loginControls.append(this.ui.loginButton);
        this.ui.loginControls.append(this.ui.loginError);
        this.ui.loginControls.fadeIn(1000);

        this.framework.ui.addWidgetToScene(this.ui.loginControls);

        // Login button click
        this.ui.loginButton.click(this.onToggleConnectionState.bind(this));
    },

    onToggleConnectionState : function()
    {
        this.ui.loginError.text("");

        if (!this.framework.client.isConnected())
        {
            // Merge username to the custom login properties.
            if (this.loginProperties === undefined)
                this.loginProperties = {};
            this.loginProperties.username = this.ui.loginUsername.val()

            this.framework.client.connect(this.ui.loginHost.val(), this.loginProperties);
        }
        else
            this.framework.client.disconnect();
    },

    onConnectionError : function(event)
    {
        if (this.ui.loginError !== undefined)
            this.ui.loginError.text("Failed to connect to " + event.target.url);
    },

    onConnectedToServer : function()
    {
        this.updateLoadingScreen(LoginScreenPlugin.LoadingScreenConnectingText, 0);

        if (LoginScreenPlugin.LoginControlsEnabled)
        {
            this.ui.loginHost.attr("disabled", "disabled");
            this.ui.loginUsername.attr("disabled", "disabled");
            this.ui.loginButton.button("option", "label", "Disconnect");

            this.hideLoginControls();
        }
    },

    onDisconnectedFromServer : function()
    {
        this.showLoadingScreen();

        if (LoginScreenPlugin.LoginControlsEnabled)
        {
            this.ui.loginHost.removeAttr("disabled");
            this.ui.loginUsername.removeAttr("disabled");
            this.ui.loginButton.button("option", "label", "Connect");

            // Reset login properties now
            this.loginProperties = {};

            this.showLoginControls();
        }
    },

    showLoginControls : function()
    {
        this.ui.loginControls.fadeIn(1000);
    },

    hideLoginControls : function()
    {
        this.ui.loginControls.fadeOut(500);
    },

    isLoadingScreenVisible : function()
    {
        return (this.loadingScreen != null);
    },

    showLoadingScreen : function()
    {
        if (!LoginScreenPlugin.LoadingScreenEnabled || this.loadingScreen != null)
            return;

        this.loadingScreen = { done: false };

        this.loadingScreen.screen = $("
", { id : "webtundra-loading-screen" });
        this.loadingScreen.screen.css({
            "position"          : "absolute",
            "width"             : "100%",
            "height"            : "100%",
            "background-color"  : "rgb(248,248,248)"
        });

        if (TundraSDK.browser.isOpera)
            this.loadingScreen.screen.css("background-image", "-o-linear-gradient(rgb(248,248,248),rgb(190,190,190))");
        else if (TundraSDK.browser.isFirefox)
            this.loadingScreen.screen.css("background-image", "-moz-linear-gradient(top, rgb(248,248,248), rgb(190,190,190))");
        else if (TundraSDK.browser.isChrome || TundraSDK.browser.isSafari)
            this.loadingScreen.screen.css("background-image", "-webkit-gradient(linear, left top, left bottom, color-stop(0, rgb(248,248,248)), color-stop(0.8, rgb(190,190,190)))");

        this.loadingScreen.text = $("
", {
            id   : "webtundra-loading-screen-label",
        }).css({
            "color"             : "rgb(50,50,50)",
            "text-align"        : "center",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "36pt",
            "text-align"        : "center",
            "vertical-align"    : "center",
            "text-decoration"   : "none"
        });
        var rexLink = $("", {
            text   : LoginScreenPlugin.LoadingScreenHeaderText,
            href   : LoginScreenPlugin.LoadingScreenHeaderLinkUrl,
            target : "_blank"
        });
        rexLink.css({
            "color"             : "rgb(50,50,50)",
            "text-align"        : "center",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "36pt",
            "text-align"        : "center",
            "vertical-align"    : "bottom",
            "text-decoration"   : "none"
        });
        this.loadingScreen.text.append(rexLink);

        this.loadingScreen.progress = $("
", {
            id   : "webtundra-loading-screen-progress"
        });
        this.loadingScreen.progress.css({
            "color"             : "rgba(50,50,50, 0.0)",
            "text-align"        : "center",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "80pt",
            "vertical-align"    : "center"
        });
        this.loadingScreen.progress.hide();

        this.loadingScreen.hideButton = $("
", {
            id   : "webtundra-loading-screen-hidebutton",
            html : "Hide Loading Screen"
        });
        this.loadingScreen.hideButton.css({
            "color"             : "rgba(50,50,50,0.6)",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "7pt",
            "position"          : "absolute",
            "top"               : 5,
            "left"              : 5
        });
        this.loadingScreen.hideButton.click(function(e) {
            /** @todo Evaluate if loading screen can be
                hidden before server connection open. */
            if (this.loadingScreen.progress.text() != "")
            {
                this.hideLoadingScreen();
                e.preventDefault();
                e.stopPropagation();
            }
        }.bind(this));

        this.loadingScreen.screen.append(this.loadingScreen.hideButton);
        this.loadingScreen.screen.append(this.loadingScreen.text);
        this.loadingScreen.screen.append(this.loadingScreen.progress);

        this.framework.ui.addWidgetToScene(this.loadingScreen.screen);

        this.loadingScreen.text.hide();
        this.loadingScreen.text.fadeIn(1000);

        this.onWindowResized();
    },

    /**
        Updates the scene loading screen with your text and progress percentage. Does nothing if loading screen has been hidden.
        @method updateLoadingScreen
        @param {String} [text=null] Text to show. If null or undefined the current text is kept.
        @param {Number} [progress=null] Progress percentage to show with range 0-100.
    */
    updateLoadingScreen : function(text, progress)
    {
        // We don't have a network connection, this must be triggered from loading startup applications.
        /// @todo Evaluate if we want to do this, local apps might load stuff too to the scene etc.
        if (TundraSDK.framework.client.websocket === null)
            return;

        if (!LoginScreenPlugin.LoadingScreenEnabled || this.loadingScreen == null || this.loadingScreen.done)
            return;

        if (text != null && this.loadingScreen.text != null)
            this.loadingScreen.text.text(text);
        if (progress != null && this.loadingScreen.progress != null)
        {
            if (this.loadingScreen.completedOnce !== undefined && this.loadingScreen.completedOnce === true)
                return;

            var wasVisible = this.loadingScreen.progress.is(":visible");
            if (progress < 100)
            {
                var stop = progress/100.0;
                if (stop > 0.9) stop = 0.9;
                this.loadingScreen.progress.html(progress + "%");
                this.loadingScreen.progress.css("color", "rgba(242,154,41, " + stop + ")");
            }
            else
            {
                this.loadingScreen.completedOnce = true;
                this.loadingScreen.text.html("Loading completed
Please wait");
                this.loadingScreen.progress.text("");
                this.loadingScreen.progress.hide();
                this.onWindowResized();
            }

            if (!wasVisible)
            {
                this.loadingScreen.progress.show();
                this.onWindowResized();
            }
        }
    },

    /**
        Hides the loading screen revealing the 3D rendering. Does nothing if the loading screen is already hidden.
        @method hideLoadingScreen
    */
    hideLoadingScreen : function()
    {
        // We don't have a network connection, this must be triggered from loading startup applications.
        if (TundraSDK.framework.client.websocket === null)
            return;

        if (this.loadingScreen == null || this.loadingScreen.done)
            return;

        this.loadingScreen.done = true;
        this.loadingScreen.hideButton.remove();

        var that = this;
        this.loadingScreen.screen.fadeOut(2000, function() {
            that.loadingScreen.screen.remove();
            that.loadingScreen.screen = null;
            that.loadingScreen = null;
            that.onWindowResized();
        });
    },

    onWindowResized : function()
    {
        if (this.loadingScreen != null && !this.loadingScreen.done)
        {
            this.loadingScreen.screen.position({
                my : "left top",
                at : "left top",
                of : this.loadingScreen.screen.parent()
            });

            this.loadingScreen.text.position({
                my : "left top",
                at : "left top+100",
                of : this.loadingScreen.screen
            });

            this.loadingScreen.progress.position({
                my : "left top",
                at : "left bottom+25",
                of : this.loadingScreen.text
            });
        }
    }
});

TundraSDK.registerPlugin(new LoginScreenPlugin());

return LoginScreenPlugin;

}); // require js
<commit_msg>Quick fix for hiding the screen when AssetAPI cant handle any asset requests made to it (eg. no registered factoris for any of the needed assets).<commit_after>
define([
        "core/framework/TundraSDK",
        "core/framework/ITundraPlugin"
    ], function(TundraSDK, ITundraPlugin) {

/**
    This plugin provides a basic user interface for connecting to a server with a username.

    @class LoginScreenPlugin
    @extends ITundraPlugin
    @constructor
*/
var LoginScreenPlugin = ITundraPlugin.$extend(
{
    __init__ : function()
    {
        this.$super("LoginScreenPlugin");

        /**
            Login properties that will be sent to the server. Application/plugin code can set custom properties to this
            before a connection is made. This property map will be cleared when server connection is disconnected.

            Note: 'username' will always be overwritten from the UI controls of this plugin.

            @property loginProperties
            @type Object
        */
        this.loginProperties = {};

        // Private
        this.ui = {};
        this.loadingScreen = null;
    },

    __classvars__ :
    {
        /** If login controls are enabled and should be shown.
            LoginScreenPlugin.LoginControlsEnabled = false; */
        LoginControlsEnabled : true,

        /** If loading screen is enabled and should be shown.
            LoginScreenPlugin.LoadingScreenEnabled = false; */
        LoadingScreenEnabled : true,

        /** Text that appears to the loading screen while
            the world assets are being loaded */
        LoadingScreenConnectingText : "Loading 3D Space",

        /// Loading screen header text
        LoadingScreenHeaderText     : "realXtend WebTundra",
        /// Loading screen header link url
        LoadingScreenHeaderLinkUrl  : "https://github.com/realXtend/WebTundra"
    },

    initialize : function()
    {
        this.framework.client.onConnectionError(this, this.onConnectionError);
        this.framework.client.onConnected(this, this.onConnectedToServer);
        this.framework.client.onDisconnected(this, this.onDisconnectedFromServer);
        this.framework.ui.onWindowResize(this, this.onWindowResized);

        this.createLoginControls();
        this.showLoadingScreen();
    },

    createLoginControls : function()
    {
        if (!LoginScreenPlugin.LoginControlsEnabled)
            return;

        this.ui.loginControls = $("
", {
            id      : "login-controls"
        });
        this.ui.loginHost = $("<input/>", {
            id      : "login-host",
            type    : "text",
            value   : "ws://127.0.0.1:2345"
        });
        this.ui.loginUsername = $("<input/>", {
            id      : "login-username",
            type    : "text",
            value   : "WebTundra User"
        });
        this.ui.loginError = $("
", {
            id      : "login-error",
            css : {
                "padding"     : 15,
                "font-size"   : 16,
                "color"       : "red"
            }
        });
        this.ui.loginButton = $("<button/>", {
            id      : "login-button",
            text    : "Connect",
            css : {
                "font-size"   : 12,
                "font-weight" : "bold"
            }
        }).button();

        this.ui.loginControls.css({
            "position"          : "absolute",
            "top"               : 180,
            "left"              : 0,
            "width"             : "100%",
            "padding"           : 10,
            "border"            : 0,
            "z-index"           : 6,
            "text-align"        : "center",
            "font-family"       : "Arial",
            "font-size"         : 14,
            "font-weight"       : "bold",
            "background-color"  : "transparent"
        });

        var inputs = [ this.ui.loginHost, this.ui.loginUsername, this.ui.loginButton ];
        for (var i = 0; i < inputs.length; i++)
        {
            inputs[i].css({
                "min-width"     : 100,
                "max-height"    : 25,
                "margin-left"   : 6,
                "margin-right"  : 9,
                "padding"       : inputs[i] === this.ui.loginButton ? 0 : 3,
                "border"        : "1px solid lightgrey",
                "border-radius" : 4
            });
        }

        this.ui.loginControls.append("Host");
        this.ui.loginControls.append(this.ui.loginHost);
        this.ui.loginControls.append("Username");
        this.ui.loginControls.append(this.ui.loginUsername);
        this.ui.loginControls.append(this.ui.loginButton);
        this.ui.loginControls.append(this.ui.loginError);
        this.ui.loginControls.fadeIn(1000);

        this.framework.ui.addWidgetToScene(this.ui.loginControls);

        // Login button click
        this.ui.loginButton.click(this.onToggleConnectionState.bind(this));
    },

    onToggleConnectionState : function()
    {
        this.ui.loginError.text("");

        if (!this.framework.client.isConnected())
        {
            // Merge username to the custom login properties.
            if (this.loginProperties === undefined)
                this.loginProperties = {};
            this.loginProperties.username = this.ui.loginUsername.val()

            this.framework.client.connect(this.ui.loginHost.val(), this.loginProperties);
        }
        else
            this.framework.client.disconnect();
    },

    onConnectionError : function(event)
    {
        if (this.ui.loginError !== undefined)
            this.ui.loginError.text("Failed to connect to " + event.target.url);
    },

    onConnectedToServer : function()
    {
        this.updateLoadingScreen(LoginScreenPlugin.LoadingScreenConnectingText, 0);

        if (LoginScreenPlugin.LoginControlsEnabled)
        {
            this.ui.loginHost.attr("disabled", "disabled");
            this.ui.loginUsername.attr("disabled", "disabled");
            this.ui.loginButton.button("option", "label", "Disconnect");

            this.hideLoginControls();

            setTimeout(function() {
                /** This is here to hide the screen if no assets from the scene failed to ever
                    get started by AssetAPI. For example it being full of Ogre assets that can't
                    be loaded with WebTundra. */
                if (this.isLoadingScreenVisible() && this.framework.asset.transfers.length === 0)
                {
                    this.log.debug("Seems there are no pending asset transfers, hiding loading screen...");
                    this.hideLoadingScreen();
                }
            }.bind(this), 2500);
        }
    },

    onDisconnectedFromServer : function()
    {
        this.showLoadingScreen();

        if (LoginScreenPlugin.LoginControlsEnabled)
        {
            this.ui.loginHost.removeAttr("disabled");
            this.ui.loginUsername.removeAttr("disabled");
            this.ui.loginButton.button("option", "label", "Connect");

            // Reset login properties now
            this.loginProperties = {};

            this.showLoginControls();
        }
    },

    showLoginControls : function()
    {
        this.ui.loginControls.fadeIn(1000);
    },

    hideLoginControls : function()
    {
        this.ui.loginControls.fadeOut(500);
    },

    isLoadingScreenVisible : function()
    {
        return (this.loadingScreen != null);
    },

    showLoadingScreen : function()
    {
        if (!LoginScreenPlugin.LoadingScreenEnabled || this.loadingScreen != null)
            return;

        this.loadingScreen = { done: false };

        this.loadingScreen.screen = $("
", { id : "webtundra-loading-screen" });
        this.loadingScreen.screen.css({
            "position"          : "absolute",
            "width"             : "100%",
            "height"            : "100%",
            "background-color"  : "rgb(248,248,248)"
        });

        if (TundraSDK.browser.isOpera)
            this.loadingScreen.screen.css("background-image", "-o-linear-gradient(rgb(248,248,248),rgb(190,190,190))");
        else if (TundraSDK.browser.isFirefox)
            this.loadingScreen.screen.css("background-image", "-moz-linear-gradient(top, rgb(248,248,248), rgb(190,190,190))");
        else if (TundraSDK.browser.isChrome || TundraSDK.browser.isSafari)
            this.loadingScreen.screen.css("background-image", "-webkit-gradient(linear, left top, left bottom, color-stop(0, rgb(248,248,248)), color-stop(0.8, rgb(190,190,190)))");

        this.loadingScreen.text = $("
", {
            id   : "webtundra-loading-screen-label",
        }).css({
            "color"             : "rgb(50,50,50)",
            "text-align"        : "center",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "36pt",
            "text-align"        : "center",
            "vertical-align"    : "center",
            "text-decoration"   : "none"
        });
        var rexLink = $("", {
            text   : LoginScreenPlugin.LoadingScreenHeaderText,
            href   : LoginScreenPlugin.LoadingScreenHeaderLinkUrl,
            target : "_blank"
        });
        rexLink.css({
            "color"             : "rgb(50,50,50)",
            "text-align"        : "center",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "36pt",
            "text-align"        : "center",
            "vertical-align"    : "bottom",
            "text-decoration"   : "none"
        });
        this.loadingScreen.text.append(rexLink);

        this.loadingScreen.progress = $("
", {
            id   : "webtundra-loading-screen-progress"
        });
        this.loadingScreen.progress.css({
            "color"             : "rgba(50,50,50, 0.0)",
            "text-align"        : "center",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "80pt",
            "vertical-align"    : "center"
        });
        this.loadingScreen.progress.hide();

        this.loadingScreen.hideButton = $("
", {
            id   : "webtundra-loading-screen-hidebutton",
            html : "Hide Loading Screen"
        });
        this.loadingScreen.hideButton.css({
            "color"             : "rgba(50,50,50,0.6)",
            "font-family"       : "Verdana",
            "font-weight"       : "bold",
            "font-size"         : "7pt",
            "position"          : "absolute",
            "top"               : 5,
            "left"              : 5
        });
        this.loadingScreen.hideButton.click(function(e) {
            /** @todo Evaluate if loading screen can be
                hidden before server connection open. */
            if (this.loadingScreen.progress.text() != "")
            {
                this.hideLoadingScreen();
                e.preventDefault();
                e.stopPropagation();
            }
        }.bind(this));

        this.loadingScreen.screen.append(this.loadingScreen.hideButton);
        this.loadingScreen.screen.append(this.loadingScreen.text);
        this.loadingScreen.screen.append(this.loadingScreen.progress);

        this.framework.ui.addWidgetToScene(this.loadingScreen.screen);

        this.loadingScreen.text.hide();
        this.loadingScreen.text.fadeIn(1000);

        this.onWindowResized();
    },

    /**
        Updates the scene loading screen with your text and progress percentage. Does nothing if loading screen has been hidden.
        @method updateLoadingScreen
        @param {String} [text=null] Text to show. If null or undefined the current text is kept.
        @param {Number} [progress=null] Progress percentage to show with range 0-100.
    */
    updateLoadingScreen : function(text, progress)
    {
        // We don't have a network connection, this must be triggered from loading startup applications.
        /// @todo Evaluate if we want to do this, local apps might load stuff too to the scene etc.
        if (TundraSDK.framework.client.websocket === null)
            return;

        if (!LoginScreenPlugin.LoadingScreenEnabled || this.loadingScreen == null || this.loadingScreen.done)
            return;

        if (text != null && this.loadingScreen.text != null)
            this.loadingScreen.text.text(text);
        if (progress != null && this.loadingScreen.progress != null)
        {
            if (this.loadingScreen.completedOnce !== undefined && this.loadingScreen.completedOnce === true)
                return;

            var wasVisible = this.loadingScreen.progress.is(":visible");
            if (progress < 100)
            {
                var stop = progress/100.0;
                if (stop > 0.9) stop = 0.9;
                this.loadingScreen.progress.html(progress + "%");
                this.loadingScreen.progress.css("color", "rgba(242,154,41, " + stop + ")");
            }
            else
            {
                this.loadingScreen.completedOnce = true;
                this.loadingScreen.text.html("Loading completed
Please wait");
                this.loadingScreen.progress.text("");
                this.loadingScreen.progress.hide();
                this.onWindowResized();
            }

            if (!wasVisible)
            {
                this.loadingScreen.progress.show();
                this.onWindowResized();
            }
        }
    },

    /**
        Hides the loading screen revealing the 3D rendering. Does nothing if the loading screen is already hidden.
        @method hideLoadingScreen
    */
    hideLoadingScreen : function()
    {
        // We don't have a network connection, this must be triggered from loading startup applications.
        if (TundraSDK.framework.client.websocket === null)
            return;

        if (this.loadingScreen == null || this.loadingScreen.done)
            return;

        this.loadingScreen.done = true;
        this.loadingScreen.hideButton.remove();

        var that = this;
        this.loadingScreen.screen.fadeOut(2000, function() {
            that.loadingScreen.screen.remove();
            that.loadingScreen.screen = null;
            that.loadingScreen = null;
            that.onWindowResized();
        });
    },

    onWindowResized : function()
    {
        if (this.loadingScreen != null && !this.loadingScreen.done)
        {
            this.loadingScreen.screen.position({
                my : "left top",
                at : "left top",
                of : this.loadingScreen.screen.parent()
            });

            this.loadingScreen.text.position({
                my : "left top",
                at : "left top+100",
                of : this.loadingScreen.screen
            });

            this.loadingScreen.progress.position({
                my : "left top",
                at : "left bottom+25",
                of : this.loadingScreen.text
            });
        }
    }
});

TundraSDK.registerPlugin(new LoginScreenPlugin());

return LoginScreenPlugin;

}); // require js
<|endoftext|>


<commit_before>'use strict';

var EventEmitter = require('eventemitter3')
  , listeners = require('loads')
  , AXO = require('axo');

/**
 *
 * @param {Object} options Optional configuration that needs default properties.
 * @api private
 */
function optional(options) {
  for (var key in Requests.defaults) {
    options[key] = key in options ? options[key] : Requests.defaults[key];
  }

  return options;
}

/**
 * RequestS(tream).
 *
 * Options:
 *
 * - streaming: Should the request be streaming.
 * - method: Which HTTP method should be used.
 * - headers: Additional request headers.
 * - mode: Enable CORS mode.
 * - body: The payload for the request.
 *
 * @constructor
 * @param {String} url The URL we want to request.
 * @param {Object} options Various of request options.
 * @api public
 */
function Requests(url, options) {
  if (!(this instanceof Requests)) return new Requests(url, options);
  options = optional(options || {});

  console.log('options', options);

  this.offset = 0;
  this.id = Requests.requested++;
  this.streaming = options.streaming;
  this.socket = Requests[Requests.method](options);
  this.socket.open(options.method.toUpperCase(), url, true);

  //
  // Register this as an active HTTP request.
  //
  Requests.active[this.id] = this;
  this.initialize(options);
}

Requests.prototype = new EventEmitter();
Requests.prototype.constructor = Requests;

/**
 * Initialize and start requesting the supplied resource.
 *
 * @param {Object} options Passed in defaults.
 * @api private
 */
Requests.prototype.initialize = function initialize(options) {
  var what
    , requests = this
    , socket = requests.socket;

  this.on('stream', function stream(data) {
    if (socket.multipart) return this.emit('data', data);

    var chunk = data.slice(this.offset);
    this.offset = data.length;

    this.emit('data', chunk);
  });

  if (options.timeout) {
    socket.timeout = +options.time;
  }

  if ('cors' === options.mode.toLowerCase() && 'withCredentials' in socket) {
    socket.withCredentials = true;
  }

  //
  // We want to prevent pre-flight requests by default for CORS requests so we
  // need to force the content-type to text/plain.
  //
  this.header('Content-Type', 'text/plain');
  for (what in options.headers) {
    this.header(what, options.headers[what]);
  }

  //
  // Set the correct responseType method.
  //
  if (this.streaming) {
    if ('string' === typeof options.body) {
      if ('multipart' in socket) {
        socket.multipart = true;
      } else if (Requests.type.mozchunkedtext) {
        socket.responseType = 'moz-chuncked-text';
      } else if (Requests.type.msstream) {
        socket.responseType = 'ms-stream';
      }
    } else {
      if (Requests.type.mozchunkedarraybuffer) {
        socket.responseType = 'moz-chuncked-array-buffer';
      }
    }
  }

  listeners(socket, this, this.streaming);
  socket.send(options.body);
};

/**
 * Safely set a request header.
 *
 * @param {String} key Name of the header.
 * @param {String} value Value of the header.
 * @returns {Requests}
 * @api public
 */
Requests.prototype.header = function header(key, value) {
  //
  // ActiveXObject will throw an `Type Mismatch` exception when setting the to
  // an null-value and to be consistent with all XHR implementations we're going
  // to cast the value to a string.
  //
  // While we don't technically support the XDomainRequest of IE, we do want to
  // double check that the setRequestHeader is available before adding headers.
  //
  if (value !== undefined && this.socket.setRequestHeader) {
    this.socket.setRequestHeader(key, value +'');
  }

  return this;
};

/**
 * Register a timeout handler.
 *
 * @param {Number} time Maximum duration a request can take.
 * @returns {Requests}
 * @api public
 */
Requests.prototype.timeout = function timeout(time) {
  this.socket.timeout = +time;
  this.socket.ontimeout = this.emits('timeout');

  return this;
};

/**
 * Create a new XMLHttpRequest.
 *
 * @returns {XMLHttpRequest}
 * @api private
 */
Requests.XHR = function create() {
  try { return new XMLHttpRequest(); }
  catch (e) {}
};

/**
 * Create a new ActiveXObject which can be used for XHR.
 *
 * @returns {ActiveXObject}
 * @api private
 */
Requests.AXO = function create() {
  var ids = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'Microsoft.XMLHTTP']
    , id;

  while (ids.length) {
    id = ids.shift();

    try { return new AXO(id); }
    catch (e) {}
  }
};

/**
 * Requests that are currently running.
 *
 * @type {Object}
 * @private
 */
Requests.active = {};

/**
 * Unique id and also an indication on how many XHR requests we've made using
 * this library.
 *
 * @type {Number}
 * @private
 */
Requests.requested = 0;

/**
 * The type of technology we are using to establish a working Ajax connection.
 * This can either be:
 *
 * - XHR: XMLHttpRequest
 * - AXO: ActiveXObject
 *
 * This is also used as internal optimization so we can easily get the correct
 * constructor as we've already feature detected it.
 *
 * @type {String}
 * @public
 */
Requests.method = !!Requests.XHR() ? 'XHR' : (!!Requests.AXO() ? 'AXO' : '');

/**
 * Boolean indicating
 *
 * @type {Boolean}
 * @public
 */
Requests.supported = !!Requests.method;

/**
 * The defaults for the Requests. These values will be used if no options object
 * or matching key is provided. It can be override globally if needed but this
 * is not advised as it can have some potential side affects for other libraries
 * that use this module.
 *
 * @type {Object}
 * @public
 */
Requests.defaults = {
  streaming: false,
  method: 'GET',
  mode: 'cors',
  headers: {},
};

/**
 * The different type of `responseType` parsers that are supported in this XHR
 * implementation.
 *
 * @type {Object}
 * @public
 */
Requests.type = 'XHR' === Requests.method ? (function detect() {
  var types = 'arraybuffer,blob,document,json,text,moz-blob,moz-chunked-text,moz-chunked-arraybuffer,ms-stream'.split(',')
    , supported = {}
    , type, xhr, prop;

  while (types.length) {
    type = types.pop();

    xhr = Requests.XHR();
    xhr.open('get', '/', true);
    prop = type.replace('-', '');

    //
    // We can only set the `responseType` after we've opened the connection or
    // FireFox will throw an error and according to the spec only async requests
    // can use this, which is fine as we force that by default.
    //
    try {
      xhr.responseType = type;
      supported[prop] = 'response' in xhr && xhr.responseType === type;
    } catch (e) {
      supported[prop] = false;
    }

    xhr = null;
  }

  return supported;
}()) : {};

/**
 * Do we support streaming response parsing.
 *
 * @type {Boolean}
 * @private
 */
Requests.streaming = 'XHR' === Requests.method && (
     'multipart' in XMLHttpRequest.prototype
  || Requests.type.mozchunkedarraybuffer
  || Requests.type.mozchunkedtext
  || Requests.type.msstream
  || Requests.type.mozblob
);

//
// Expose the Requests library.
//
module.exports = Requests;
<commit_msg>[fix] Remove console logs. [fix] Correctly remove all dashes [fix] Corrected typos in responseType [minor] Commented on the use of slice<commit_after>'use strict';

var EventEmitter = require('eventemitter3')
  , listeners = require('loads')
  , AXO = require('axo');

/**
 *
 * @param {Object} options Optional configuration that needs default properties.
 * @api private
 */
function optional(options) {
  for (var key in Requests.defaults) {
    options[key] = key in options ? options[key] : Requests.defaults[key];
  }

  return options;
}

/**
 * RequestS(tream).
 *
 * Options:
 *
 * - streaming: Should the request be streaming.
 * - method: Which HTTP method should be used.
 * - headers: Additional request headers.
 * - mode: Enable CORS mode.
 * - body: The payload for the request.
 *
 * @constructor
 * @param {String} url The URL we want to request.
 * @param {Object} options Various of request options.
 * @api public
 */
function Requests(url, options) {
  if (!(this instanceof Requests)) return new Requests(url, options);
  options = optional(options || {});

  this.offset = 0;
  this.id = Requests.requested++;
  this.streaming = options.streaming;
  this.socket = Requests[Requests.method](options);
  this.socket.open(options.method.toUpperCase(), url, true);

  //
  // Register this as an active HTTP request.
  //
  Requests.active[this.id] = this;
  this.initialize(options);
}

Requests.prototype = new EventEmitter();
Requests.prototype.constructor = Requests;

/**
 * Initialize and start requesting the supplied resource.
 *
 * @param {Object} options Passed in defaults.
 * @api private
 */
Requests.prototype.initialize = function initialize(options) {
  var what
    , requests = this
    , socket = requests.socket;

  this.on('stream', function stream(data) {
    if (socket.multipart) return this.emit('data', data);

    //
    // Please note that we need to use a method here that works on both string
    // as well as ArrayBuffer's as we have no certainty that we're receiving
    // text.
    //
    var chunk = data.slice(this.offset);
    this.offset = data.length;

    this.emit('data', chunk);
  });

  if (options.timeout) {
    socket.timeout = +options.time;
  }

  if ('cors' === options.mode.toLowerCase() && 'withCredentials' in socket) {
    socket.withCredentials = true;
  }

  //
  // We want to prevent pre-flight requests by default for CORS requests so we
  // need to force the content-type to text/plain.
  //
  this.header('Content-Type', 'text/plain');
  for (what in options.headers) {
    this.header(what, options.headers[what]);
  }

  //
  // Set the correct responseType method.
  //
  if (this.streaming) {
    if ('string' === typeof options.body) {
      if ('multipart' in socket) {
        socket.multipart = true;
      } else if (Requests.type.mozchunkedtext) {
        socket.responseType = 'moz-chuncked-text';
      } else if (Requests.type.msstream) {
        socket.responseType = 'ms-stream';
      }
    } else {
      if (Requests.type.mozchunkedarraybuffer) {
        socket.responseType = 'moz-chunked-arraybuffer';
      }
    }
  }

  listeners(socket, this, this.streaming);
  socket.send(options.body);
};

/**
 * Safely set a request header.
 *
 * @param {String} key Name of the header.
 * @param {String} value Value of the header.
 * @returns {Requests}
 * @api public
 */
Requests.prototype.header = function header(key, value) {
  //
  // ActiveXObject will throw an `Type Mismatch` exception when setting the to
  // an null-value and to be consistent with all XHR implementations we're going
  // to cast the value to a string.
  //
  // While we don't technically support the XDomainRequest of IE, we do want to
  // double check that the setRequestHeader is available before adding headers.
  //
  if (value !== undefined && this.socket.setRequestHeader) {
    this.socket.setRequestHeader(key, value +'');
  }

  return this;
};

/**
 * Register a timeout handler.
 *
 * @param {Number} time Maximum duration a request can take.
 * @returns {Requests}
 * @api public
 */
Requests.prototype.timeout = function timeout(time) {
  this.socket.timeout = +time;
  this.socket.ontimeout = this.emits('timeout');

  return this;
};

/**
 * Create a new XMLHttpRequest.
 *
 * @returns {XMLHttpRequest}
 * @api private
 */
Requests.XHR = function create() {
  try { return new XMLHttpRequest(); }
  catch (e) {}
};

/**
 * Create a new ActiveXObject which can be used for XHR.
 *
 * @returns {ActiveXObject}
 * @api private
 */
Requests.AXO = function create() {
  var ids = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'Microsoft.XMLHTTP']
    , id;

  while (ids.length) {
    id = ids.shift();

    try { return new AXO(id); }
    catch (e) {}
  }
};

/**
 * Requests that are currently running.
 *
 * @type {Object}
 * @private
 */
Requests.active = {};

/**
 * Unique id and also an indication on how many XHR requests we've made using
 * this library.
 *
 * @type {Number}
 * @private
 */
Requests.requested = 0;

/**
 * The type of technology we are using to establish a working Ajax connection.
 * This can either be:
 *
 * - XHR: XMLHttpRequest
 * - AXO: ActiveXObject
 *
 * This is also used as internal optimization so we can easily get the correct
 * constructor as we've already feature detected it.
 *
 * @type {String}
 * @public
 */
Requests.method = !!Requests.XHR() ? 'XHR' : (!!Requests.AXO() ? 'AXO' : '');

/**
 * Boolean indicating
 *
 * @type {Boolean}
 * @public
 */
Requests.supported = !!Requests.method;

/**
 * The defaults for the Requests. These values will be used if no options object
 * or matching key is provided. It can be override globally if needed but this
 * is not advised as it can have some potential side affects for other libraries
 * that use this module.
 *
 * @type {Object}
 * @public
 */
Requests.defaults = {
  streaming: false,
  method: 'GET',
  mode: 'cors',
  headers: {},
};

/**
 * The different type of `responseType` parsers that are supported in this XHR
 * implementation.
 *
 * @type {Object}
 * @public
 */
Requests.type = 'XHR' === Requests.method ? (function detect() {
  var types = 'arraybuffer,blob,document,json,text,moz-blob,moz-chunked-text,moz-chunked-arraybuffer,ms-stream'.split(',')
    , supported = {}
    , type, xhr, prop;

  while (types.length) {
    type = types.pop();

    xhr = Requests.XHR();
    xhr.open('get', '/', true);
    prop = type.replace(/-/g, '');

    //
    // We can only set the `responseType` after we've opened the connection or
    // FireFox will throw an error and according to the spec only async requests
    // can use this, which is fine as we force that by default.
    //
    try {
      xhr.responseType = type;
      supported[prop] = 'response' in xhr && xhr.responseType === type;
    } catch (e) {
      supported[prop] = false;
    }

    xhr = null;
  }

  return supported;
}()) : {};

/**
 * Do we support streaming response parsing.
 *
 * @type {Boolean}
 * @private
 */
Requests.streaming = 'XHR' === Requests.method && (
     'multipart' in XMLHttpRequest.prototype
  || Requests.type.mozchunkedarraybuffer
  || Requests.type.mozchunkedtext
  || Requests.type.msstream
  || Requests.type.mozblob
);

//
// Expose the Requests library.
//
module.exports = Requests;
<|endoftext|>


<commit_before>var remove    = require('remove-element')
var Emitter   = require('events/')
var xtend     = require('xtend')
var vkey      = require('vkey')
var fs        = require('fs')

module.exports = createMenu

require('insert-css')(
  fs.readFileSync(__dirname + '/style.css', 'utf8')
)

function createMenu(opts) {
  opts = opts || {}
  opts = xtend({
      x: 1
    , y: 1
    , fg: 'white'
    , bg: 'blue'
    , padding: xtend({
        top: 1
      , left: 1
      , right: 1
      , bottom: 1
    }, opts.padding || {})
  }, opts)

  var curr = null
  var list = document.createElement('ul')
  var menu = new Emitter

  var latestLineText = ''
  var latestLine = null
  var items = []

  // methods
  menu.add = add
  menu.reset = reset
  menu.close = close
  menu.write = write
  menu.createStream = createStream

  menu.element = list

  // styles
  list.classList.add('browser-terminal-menu')
  list.style.color = opts.fg
  list.style.backgroundColor = opts.bg

  if ('x' in opts) list.style.left = opts.x + 'em'
  if ('y' in opts) list.style.top  = opts.y + 'em'
  if ('width' in opts) list.style.width = opts.width + 'em'

  var padding = opts.padding
  list.style.paddingTop = padding.top + 'em'
  list.style.paddingLeft = padding.left + 'em'
  list.style.paddingRight = padding.right + 'em'
  list.style.paddingBottom = padding.bottom + 'em'

  // keybindings
  var keys = {
      '<up>': prev
    , '<left>': prev
    , 'J': prev
    , 'H': prev
    , '<down>': next
    , '<right>': next
    , 'L': next
    , 'K': next
    , '<space>': select
    , '<enter>': select
    , 'Q': close
    , '<tab>': next
  }

  window.addEventListener('keydown', keydown, false)
  menu.once('close', function() {
    window.removeEventListener('keydown', keydown, false)
  })

  return menu

  // behavior
  function add(text) {
    var item = document.createElement('li')
    latestLine = null
    latestLineText = ''

    items.push(item)
    list.appendChild(item)
    item.innerHTML = text
    item.addEventListener('mouseover', mouseover, false)
    item.addEventListener('click', click, false)

    return menu.once('close', function() {
      item.removeEventListener('mouseover', mouseover, false)
      item.removeEventListener('click', click, false)
    })

    function mouseover(e) {
      if (curr !== null) {
        disable(items[curr])
        curr = null
      }

      enable(item)
      curr = items.indexOf(item)
    }

    function click() {
      curr = items.indexOf(item)
      select()
    }
  }

  function enable(item) {
    item.style.backgroundColor = opts.fg
    item.style.color = opts.bg
  }

  function disable(item) {
    item.style.color = opts.fg
    item.style.backgroundColor = opts.bg
  }

  function next() {
    if (curr !== null) {
      disable(items[curr])
      curr += items.length + 1
      curr %= items.length
    } else {
      curr = 0
    }

    enable(items[curr])
  }

  function prev() {
    if (curr !== null) {
      disable(items[curr])
      curr += items.length - 1
      curr %= items.length
    } else {
      curr = items.length - 1
    }

    enable(items[curr])
  }

  function select() {
    if (curr === null) return
    if (!items[curr]) return
    var label = items[curr].innerHTML
    menu.emit('select', label, curr)
  }

  function reset() {
    document.body.appendChild(list)
  }

  function close() {
    remove(menu.element)
    menu.emit('close')
  }

  function write(text) {
    if (!latestLine) {
      latestLine = document.createElement('li')
      latestLine.classList.add('non-item')
      list.appendChild(latestLine)
    }

    latestLineText += text
    latestLine.innerHTML =
    latestLineText.replace(/\s+$/g, '')
  }

  function keydown(e) {
    if (e.metaKey) return
    if (e.shiftKey) return
    var key = vkey[e.keyCode] || e.char

    if (e.ctrlKey) {
      if (key !== 'C') return
      return close()
    }

    if (!keys[key]) return
    keys[key]()
    e.preventDefault()
    e.stopPropagation()
    return false
  }

  // just for completeness...
  function createStream() {
    var e = new Emitter
    e.pipe = function(){}
    return e
  }
}
<commit_msg>Add shift+tab support<commit_after>var remove    = require('remove-element')
var Emitter   = require('events/')
var xtend     = require('xtend')
var vkey      = require('vkey')
var fs        = require('fs')

module.exports = createMenu

require('insert-css')(
  fs.readFileSync(__dirname + '/style.css', 'utf8')
)

function createMenu(opts) {
  opts = opts || {}
  opts = xtend({
      x: 1
    , y: 1
    , fg: 'white'
    , bg: 'blue'
    , padding: xtend({
        top: 1
      , left: 1
      , right: 1
      , bottom: 1
    }, opts.padding || {})
  }, opts)

  var curr = null
  var list = document.createElement('ul')
  var menu = new Emitter

  var latestLineText = ''
  var latestLine = null
  var items = []

  // methods
  menu.add = add
  menu.reset = reset
  menu.close = close
  menu.write = write
  menu.createStream = createStream

  menu.element = list

  // styles
  list.classList.add('browser-terminal-menu')
  list.style.color = opts.fg
  list.style.backgroundColor = opts.bg

  if ('x' in opts) list.style.left = opts.x + 'em'
  if ('y' in opts) list.style.top  = opts.y + 'em'
  if ('width' in opts) list.style.width = opts.width + 'em'

  var padding = opts.padding
  list.style.paddingTop = padding.top + 'em'
  list.style.paddingLeft = padding.left + 'em'
  list.style.paddingRight = padding.right + 'em'
  list.style.paddingBottom = padding.bottom + 'em'

  // keybindings
  var keys = {
      '<up>': prev
    , '<left>': prev
    , 'J': prev
    , 'H': prev
    , '<down>': next
    , '<right>': next
    , 'L': next
    , 'K': next
    , '<space>': select
    , '<enter>': select
    , 'Q': close
    , '<tab>': next
  }

  window.addEventListener('keydown', keydown, false)
  menu.once('close', function() {
    window.removeEventListener('keydown', keydown, false)
  })

  return menu

  // behavior
  function add(text) {
    var item = document.createElement('li')
    latestLine = null
    latestLineText = ''

    items.push(item)
    list.appendChild(item)
    item.innerHTML = text
    item.addEventListener('mouseover', mouseover, false)
    item.addEventListener('click', click, false)

    return menu.once('close', function() {
      item.removeEventListener('mouseover', mouseover, false)
      item.removeEventListener('click', click, false)
    })

    function mouseover(e) {
      if (curr !== null) {
        disable(items[curr])
        curr = null
      }

      enable(item)
      curr = items.indexOf(item)
    }

    function click() {
      curr = items.indexOf(item)
      select()
    }
  }

  function enable(item) {
    item.style.backgroundColor = opts.fg
    item.style.color = opts.bg
  }

  function disable(item) {
    item.style.color = opts.fg
    item.style.backgroundColor = opts.bg
  }

  function next() {
    if (curr !== null) {
      disable(items[curr])
      curr += items.length + 1
      curr %= items.length
    } else {
      curr = 0
    }

    enable(items[curr])
  }

  function prev() {
    if (curr !== null) {
      disable(items[curr])
      curr += items.length - 1
      curr %= items.length
    } else {
      curr = items.length - 1
    }

    enable(items[curr])
  }

  function select() {
    if (curr === null) return
    if (!items[curr]) return
    var label = items[curr].innerHTML
    menu.emit('select', label, curr)
  }

  function reset() {
    document.body.appendChild(list)
  }

  function close() {
    remove(menu.element)
    menu.emit('close')
  }

  function write(text) {
    if (!latestLine) {
      latestLine = document.createElement('li')
      latestLine.classList.add('non-item')
      list.appendChild(latestLine)
    }

    latestLineText += text
    latestLine.innerHTML =
    latestLineText.replace(/\s+$/g, '')
  }

  function keydown(e) {
    if (e.metaKey) return
    var key = vkey[e.keyCode] || e.char
    if (e.shiftKey) {
      if (key === '<tab>') {
        key = '<up>'
      } else {
        return
      }
    }

    if (e.ctrlKey) {
      if (key !== 'C') return
      return close()
    }

    if (!keys[key]) return
    keys[key]()
    e.preventDefault()
    e.stopPropagation()
    return false
  }

  // just for completeness...
  function createStream() {
    var e = new Emitter
    e.pipe = function(){}
    return e
  }
}
<|endoftext|>


<commit_before>
/**
 * This is the web browser implementation of `debug()`.
 *
 * Expose `debug()` as the module.
 */

exports = module.exports = require('./debug');
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = 'undefined' != typeof chrome
               && 'undefined' != typeof chrome.storage
                  ? chrome.storage.local
                  : localstorage();

/**
 * Colors.
 */

exports.colors = [
  'lightseagreen',
  'forestgreen',
  'goldenrod',
  'dodgerblue',
  'darkorchid',
  'crimson'
];

/**
 * Currently only WebKit-based Web Inspectors, Firefox >= v31,
 * and the Firebug extension (any Firefox version) are known
 * to support "%c" CSS customizations.
 *
 * TODO: add a `localStorage` variable to explicitly enable/disable colors
 */

function useColors() {
  // is webkit? http://stackoverflow.com/a/16459606/376773
  // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
  return (typeof document !== 'undefined' && 'WebkitAppearance' in document.documentElement.style) ||
    // is firebug? http://stackoverflow.com/a/398120/376773
    (window.console && (console.firebug || (console.exception && console.table))) ||
    // is firefox >= v31?
    // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
    (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
}

/**
 * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
 */

exports.formatters.j = function(v) {
  try {
    return JSON.stringify(v);
  } catch (err) {
    return '[UnexpectedJSONParseError]: ' + err.message;
  }
};

/**
 * Colorize log arguments if enabled.
 *
 * @api public
 */

function formatArgs() {
  var args = arguments;
  var useColors = this.useColors;

  args[0] = (useColors ? '%c' : '')
    + this.namespace
    + (useColors ? ' %c' : ' ')
    + args[0]
    + (useColors ? '%c ' : ' ')
    + '+' + exports.humanize(this.diff);

  if (!useColors) return args;

  var c = 'color: ' + this.color;
  args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));

  // the final "%c" is somewhat tricky, because there could be other
  // arguments passed either before or after the %c, so we need to
  // figure out the correct index to insert the CSS into
  var index = 0;
  var lastC = 0;
  args[0].replace(/%[a-z%]/g, function(match) {
    if ('%%' === match) return;
    index++;
    if ('%c' === match) {
      // we only are interested in the *last* %c
      // (the user may have provided their own)
      lastC = index;
    }
  });

  args.splice(lastC, 0, c);
  return args;
}

/**
 * Invokes `console.log()` when available.
 * No-op when `console.log` is not a "function".
 *
 * @api public
 */

function log() {
  // this hackery is required for IE8/9, where
  // the `console.log` function doesn't have 'apply'
  return 'object' === typeof console
    && console.log
    && Function.prototype.apply.call(console.log, console, arguments);
}

/**
 * Save `namespaces`.
 *
 * @param {String} namespaces
 * @api private
 */

function save(namespaces) {
  try {
    if (null == namespaces) {
      exports.storage.removeItem('debug');
    } else {
      exports.storage.debug = namespaces;
    }
  } catch(e) {}
}

/**
 * Load `namespaces`.
 *
 * @return {String} returns the previously persisted debug modes
 * @api private
 */

function load() {
  var r;
  try {
    return exports.storage.debug;
  } catch(e) {}

  // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
  if (typeof process !== 'undefined' && 'env' in process) {
    return process.env.DEBUG;
  }
}

/**
 * Enable namespaces listed in `localStorage.debug` initially.
 */

exports.enable(load());

/**
 * Localstorage attempts to return the localstorage.
 *
 * This is necessary because safari throws
 * when a user disables cookies/localstorage
 * and you attempt to access it.
 *
 * @return {LocalStorage}
 * @api private
 */

function localstorage(){
  try {
    return window.localStorage;
  } catch (e) {}
}
<commit_msg>allow colours in workers (#335)<commit_after>
/**
 * This is the web browser implementation of `debug()`.
 *
 * Expose `debug()` as the module.
 */

exports = module.exports = require('./debug');
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = 'undefined' != typeof chrome
               && 'undefined' != typeof chrome.storage
                  ? chrome.storage.local
                  : localstorage();

/**
 * Colors.
 */

exports.colors = [
  'lightseagreen',
  'forestgreen',
  'goldenrod',
  'dodgerblue',
  'darkorchid',
  'crimson'
];

/**
 * Currently only WebKit-based Web Inspectors, Firefox >= v31,
 * and the Firebug extension (any Firefox version) are known
 * to support "%c" CSS customizations.
 *
 * TODO: add a `localStorage` variable to explicitly enable/disable colors
 */

function useColors() {
  // is webkit? http://stackoverflow.com/a/16459606/376773
  // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
  return (typeof document !== 'undefined' && 'WebkitAppearance' in document.documentElement.style) ||
    // is firebug? http://stackoverflow.com/a/398120/376773
    (typeof window !== 'undefined' && window.console && (console.firebug || (console.exception && console.table))) ||
    // is firefox >= v31?
    // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
    (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
    // double check webkit in userAgent just in case we are in a worker
    (navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}

/**
 * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
 */

exports.formatters.j = function(v) {
  try {
    return JSON.stringify(v);
  } catch (err) {
    return '[UnexpectedJSONParseError]: ' + err.message;
  }
};

/**
 * Colorize log arguments if enabled.
 *
 * @api public
 */

function formatArgs() {
  var args = arguments;
  var useColors = this.useColors;

  args[0] = (useColors ? '%c' : '')
    + this.namespace
    + (useColors ? ' %c' : ' ')
    + args[0]
    + (useColors ? '%c ' : ' ')
    + '+' + exports.humanize(this.diff);

  if (!useColors) return args;

  var c = 'color: ' + this.color;
  args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));

  // the final "%c" is somewhat tricky, because there could be other
  // arguments passed either before or after the %c, so we need to
  // figure out the correct index to insert the CSS into
  var index = 0;
  var lastC = 0;
  args[0].replace(/%[a-z%]/g, function(match) {
    if ('%%' === match) return;
    index++;
    if ('%c' === match) {
      // we only are interested in the *last* %c
      // (the user may have provided their own)
      lastC = index;
    }
  });

  args.splice(lastC, 0, c);
  return args;
}

/**
 * Invokes `console.log()` when available.
 * No-op when `console.log` is not a "function".
 *
 * @api public
 */

function log() {
  // this hackery is required for IE8/9, where
  // the `console.log` function doesn't have 'apply'
  return 'object' === typeof console
    && console.log
    && Function.prototype.apply.call(console.log, console, arguments);
}

/**
 * Save `namespaces`.
 *
 * @param {String} namespaces
 * @api private
 */

function save(namespaces) {
  try {
    if (null == namespaces) {
      exports.storage.removeItem('debug');
    } else {
      exports.storage.debug = namespaces;
    }
  } catch(e) {}
}

/**
 * Load `namespaces`.
 *
 * @return {String} returns the previously persisted debug modes
 * @api private
 */

function load() {
  var r;
  try {
    return exports.storage.debug;
  } catch(e) {}

  // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
  if (typeof process !== 'undefined' && 'env' in process) {
    return process.env.DEBUG;
  }
}

/**
 * Enable namespaces listed in `localStorage.debug` initially.
 */

exports.enable(load());

/**
 * Localstorage attempts to return the localstorage.
 *
 * This is necessary because safari throws
 * when a user disables cookies/localstorage
 * and you attempt to access it.
 *
 * @return {LocalStorage}
 * @api private
 */

function localstorage(){
  try {
    return window.localStorage;
  } catch (e) {}
}
<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var gutil = require('gulp-util');

/**
 * Interns
 */
var Helpers = require('./helpers.js');

/**
 * Constants
 */
var PLUGIN_NAME = 'gulp-angular-translate-extract';

function ExtractTranslations (options, content) {
    /* jshint validthis: true */
    var self = this;
    this.content = content;
    this.customRegex = _.isArray(options.customRegex) ? options.customRegex : [];
    this.interpolation = options.interpolation || {
        startDelimiter: '{{',
        endDelimiter: '}}'
    };
    this.namespace = (options.namespace) ? options.namespace : false;
    this.verbose = (options.verbose) ? options.verbose : false;

    // Regexs that will be executed on files
    this.regexs = {
        commentSimpleQuote: '\\/\\*\\s*i18nextract\\s*\\*\\/\'((?:\\\\.|[^\'\\\\])*)\'',
        commentDoubleQuote: '\\/\\*\\s*i18nextract\\s*\\*\\/"((?:\\\\.|[^"\\\\])*)"',
        HtmlFilterSimpleQuote: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '\\s*\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlFilterDoubleQuote: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '\\s*"((?:\\\\.|[^"\\\\\])*)"\\s*\\|\\s*translate(:.*?)?\\s*' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlFilterSimpleQuoteOneTimeBinding: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '::\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*\\)' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlFilterDoubleQuoteOneTimeBinding: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '::\\(\\s*"((?:\\\\.|[^"\\\\])*)"\\s*\\|\\s*translate(:.*?)?\\s*\\)' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlDirective: '<[^>]*translate[^{>]*>([^<]*)<\/[^>]*>',
        HtmlDirectiveStandalone: 'translate="((?:\\\\.|[^"\\\\])*)"',
        HtmlDirectivePluralLast: 'translate="((?:\\\\.|[^"\\\\])*)".*angular-plural-extract="((?:\\\\.|[^"\\\\])*)"',
        HtmlDirectivePluralFirst: 'angular-plural-extract="((?:\\\\.|[^"\\\\])*)".*translate="((?:\\\\.|[^"\\\\])*)"',
        HtmlNgBindHtml: 'ng-bind-html="\\s*\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*"',
        JavascriptServiceSimpleQuote: '\\$translate\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'[^\\)]*.*\\)',
        JavascriptServiceDoubleQuote: '\\$translate\\(\\s*"((?:\\\\.|[^"\\\\])*)"[^\\)]*.*\\)',
        JavascriptServiceArraySimpleQuote: '\\$translate\\((?:\\s*(\\[\\s*(?:(?:\'(?:(?:\\.|[^.*\'\\\\])*)\')\\s*,*\\s*)+\\s*\\])\\s*).*\\)',
        JavascriptServiceArrayDoubleQuote: '\\$translate\\((?:\\s*(\\[\\s*(?:(?:"(?:(?:\\.|[^.*\'\\\\])*)")\\s*,*\\s*)+\\s*\\])\\s*).*\\)',
        JavascriptServiceInstantSimpleQuote: '\\$translate\\.instant\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'[^\\)]*.*\\)',
        JavascriptServiceInstantDoubleQuote: '\\$translate\\.instant\\(\\s*"((?:\\\\.|[^"\\\\])*)"[^\\)]*.*\\)',
        JavascriptFilterSimpleQuote: '\\$filter\\(\\s*\'translate\'\\s*\\)\\s*\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'[^\\)]*\\)',
        JavascriptFilterDoubleQuote: '\\$filter\\(\\s*"translate"\\s*\\)\\s*\\(\\s*"((?:\\\\.|[^"\\\\\])*)"[^\\)]*\\)'
    };
    this.setCustomRegex();

    this.results = {};
}

ExtractTranslations.prototype.setCustomRegex = function() {
    var self = this;
    _.forEach(this.customRegex, function (regex, key) {
        self.regexs['others_' + key] = regex;
    });
};

ExtractTranslations.prototype.process = function () {
    var _regex;
    // Execute all regex defined at the top of this file
    for (var i in this.regexs) {
        _regex = new RegExp(this.regexs[i], "gi");
        switch (i) {
            // Case filter HTML simple/double quoted
            case "HtmlFilterSimpleQuote":
            case "HtmlFilterSimpleQuoteOneTimeBinding":
            case "HtmlFilterDoubleQuote":
            case "HtmlFilterDoubleQuoteOneTimeBinding":
            case "HtmlDirective":
            case "HtmlDirectivePluralLast":
            case "HtmlDirectivePluralFirst":
            case "JavascriptFilterSimpleQuote":
            case "JavascriptFilterDoubleQuote":
                // Match all occurences
                var matches = this.content.match(_regex);
                if (_.isArray(matches) && matches.length) {
                    // Through each matches, we'll execute regex to get translation key
                    for (var index in matches) {
                        if (matches[index] !== "") {
                            this.extract(i, _regex, matches[index]);
                        }
                    }
                }
                break;
            // Others regex
            default:
            this.extract(i, _regex, this.content);

        }
    }

    return this.results;
};

// Extract regex strings from content and feed results object
ExtractTranslations.prototype.extract = function (regexName, regex, content) {
    var self = this;
    var r;
    if (this.verbose) {
        gutil.log('----------------------------------------------');
        gutil.log('Process extraction with regex : "' + regexName + '"');
        gutil.log(regex);
    }

    regex.lastIndex = 0;

    while ((r = regex.exec(content)) !== null) {
        if (r.length < 2) {
            return;
        }

        // Result expected [STRING, KEY, SOME_REGEX_STUF]
        // Except for plural hack [STRING, KEY, ARRAY_IN_STRING]

        var translationKey,
            evalString,
            translationDefaultValue = "";

        switch (regexName) {
            case 'HtmlDirectivePluralFirst':
                var tmp = r[1];
                r[1] = r[2];
                r[2] = tmp;
                break;
            case 'HtmlDirectivePluralLast':
                evalString = eval(r[2]);
                if (_.isArray(evalString) &&
                    evalString.length >= 2) {
                    translationDefaultValue = "{NB, plural, one{" + evalString[0] + "} other{" + evalString[1] + "}" + (evalString[2] ? ' ' + evalString[2] : '');
                }
                translationKey = r[1].trim();
                break;
            default:
            translationKey = r[1].trim();
        }

        // Avoid empty translation
        if (translationKey === "" || translationKey === undefined) {
            return;
        }

        if (this.namespace && translationKey.split('.').length < 2) {
            throw new Error(chalk.red(
                'Namespace option need tokens with "." delimiter.' +
                '\n Your token conflicts with namespaces: ') +
                translationKey + chalk.red('.\n'));
        }

        switch (regexName) {
            case "commentSimpleQuote":
            case "HtmlFilterSimpleQuote":
            case "HtmlFilterSimpleQuoteOneTimeBinding":
            case "JavascriptServiceSimpleQuote":
            case "JavascriptServiceInstantSimpleQuote":
            case "JavascriptFilterSimpleQuote":
            case "HtmlNgBindHtml":
                translationKey = translationKey.replace(/\\\'/g, "'");
                break;
            case "commentDoubleQuote":
            case "HtmlFilterDoubleQuote":
            case "HtmlFilterDoubleQuoteOneTimeBinding":
            case "JavascriptServiceDoubleQuote":
            case "JavascriptServiceInstantDoubleQuote":
            case "JavascriptFilterDoubleQuote":
                translationKey = translationKey.replace(/\\\"/g, '"');
                break;
            case "JavascriptServiceArraySimpleQuote":
            case "JavascriptServiceArrayDoubleQuote":
                var key;

                if(regexName === "JavascriptServiceArraySimpleQuote") {
                    key = translationKey.replace(/'/g, '');
                } else {
                    key = translationKey.replace(/"/g, '');
                }
                key = key.replace(/[\][]/g, '');
                key = key.split(',');

                key.forEach(function(item){
                    item = item.replace(/\\\"/g, '"').trim();
                    self.results[item] = translationDefaultValue;
                });
                break;
        }

        if( regexName !== "JavascriptServiceArraySimpleQuote" &&
        regexName !== "JavascriptServiceArrayDoubleQuote") {
            self.results[translationKey] = translationDefaultValue;
        }

    }
};

module.exports = ExtractTranslations;
<commit_msg>Added escape for HTML directive usage for interpolation (see https://github.com/bcabanes/gulp-angular-translate-extract/issues/21)<commit_after>'use strict';
var _ = require('lodash');
var chalk = require('chalk');
var gutil = require('gulp-util');

/**
 * Interns
 */
var Helpers = require('./helpers.js');

/**
 * Constants
 */
var PLUGIN_NAME = 'gulp-angular-translate-extract';

function ExtractTranslations (options, content) {
    /* jshint validthis: true */
    var self = this;
    this.content = content;
    this.customRegex = _.isArray(options.customRegex) ? options.customRegex : [];
    this.interpolation = options.interpolation || {
        startDelimiter: '{{',
        endDelimiter: '}}'
    };
    this.interpolationEscapeChar = _.first(this.interpolation.startDelimiter);
    this.namespace = (options.namespace) ? options.namespace : false;
    this.verbose = (options.verbose) ? options.verbose : false;

    // Regexs that will be executed on files
    this.regexs = {
        commentSimpleQuote: '\\/\\*\\s*i18nextract\\s*\\*\\/\'((?:\\\\.|[^\'\\\\])*)\'',
        commentDoubleQuote: '\\/\\*\\s*i18nextract\\s*\\*\\/"((?:\\\\.|[^"\\\\])*)"',
        HtmlFilterSimpleQuote: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '\\s*\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlFilterDoubleQuote: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '\\s*"((?:\\\\.|[^"\\\\\])*)"\\s*\\|\\s*translate(:.*?)?\\s*' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlFilterSimpleQuoteOneTimeBinding: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '::\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*\\)' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlFilterDoubleQuoteOneTimeBinding: Helpers.escapeRegExp(this.interpolation.startDelimiter) + '::\\(\\s*"((?:\\\\.|[^"\\\\])*)"\\s*\\|\\s*translate(:.*?)?\\s*\\)' + Helpers.escapeRegExp(this.interpolation.endDelimiter),
        HtmlDirective: '<[^>]*translate[^{>]*>([^' + this.interpolationEscapeChar + '<]*)<\/[^>]*>',
        HtmlDirectiveStandalone: 'translate="((?:\\\\.|[^' + this.interpolationEscapeChar + '"\\\\])*)"',
        HtmlDirectivePluralLast: 'translate="((?:\\\\.|[^' + this.interpolationEscapeChar + '"\\\\])*)".*angular-plural-extract="((?:\\\\.|[^"\\\\])*)"',
        HtmlDirectivePluralFirst: 'angular-plural-extract="((?:\\\\.|[^"\\\\])*)".*translate="((?:\\\\.|[^' + this.interpolationEscapeChar + '"\\\\])*)"',
        HtmlNgBindHtml: 'ng-bind-html="\\s*\'((?:\\\\.|[^' + this.interpolationEscapeChar + '\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*"',
        JavascriptServiceSimpleQuote: '\\$translate\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'[^\\)]*.*\\)',
        JavascriptServiceDoubleQuote: '\\$translate\\(\\s*"((?:\\\\.|[^"\\\\])*)"[^\\)]*.*\\)',
        JavascriptServiceArraySimpleQuote: '\\$translate\\((?:\\s*(\\[\\s*(?:(?:\'(?:(?:\\.|[^.*\'\\\\])*)\')\\s*,*\\s*)+\\s*\\])\\s*).*\\)',
        JavascriptServiceArrayDoubleQuote: '\\$translate\\((?:\\s*(\\[\\s*(?:(?:"(?:(?:\\.|[^.*\'\\\\])*)")\\s*,*\\s*)+\\s*\\])\\s*).*\\)',
        JavascriptServiceInstantSimpleQuote: '\\$translate\\.instant\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'[^\\)]*.*\\)',
        JavascriptServiceInstantDoubleQuote: '\\$translate\\.instant\\(\\s*"((?:\\\\.|[^"\\\\])*)"[^\\)]*.*\\)',
        JavascriptFilterSimpleQuote: '\\$filter\\(\\s*\'translate\'\\s*\\)\\s*\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\'[^\\)]*\\)',
        JavascriptFilterDoubleQuote: '\\$filter\\(\\s*"translate"\\s*\\)\\s*\\(\\s*"((?:\\\\.|[^"\\\\\])*)"[^\\)]*\\)'
    };
    this.setCustomRegex();

    this.results = {};
}

ExtractTranslations.prototype.setCustomRegex = function() {
    var self = this;
    _.forEach(this.customRegex, function (regex, key) {
        self.regexs['others_' + key] = regex;
    });
};

ExtractTranslations.prototype.process = function () {
    var _regex;
    // Execute all regex defined at the top of this file
    for (var i in this.regexs) {
        _regex = new RegExp(this.regexs[i], "gi");
        switch (i) {
            // Case filter HTML simple/double quoted
            case "HtmlFilterSimpleQuote":
            case "HtmlFilterSimpleQuoteOneTimeBinding":
            case "HtmlFilterDoubleQuote":
            case "HtmlFilterDoubleQuoteOneTimeBinding":
            case "HtmlDirective":
            case "HtmlDirectivePluralLast":
            case "HtmlDirectivePluralFirst":
            case "JavascriptFilterSimpleQuote":
            case "JavascriptFilterDoubleQuote":
                // Match all occurences
                var matches = this.content.match(_regex);
                if (_.isArray(matches) && matches.length) {
                    // Through each matches, we'll execute regex to get translation key
                    for (var index in matches) {
                        if (matches[index] !== "") {
                            this.extract(i, _regex, matches[index]);
                        }
                    }
                }
                break;
            // Others regex
            default:
            this.extract(i, _regex, this.content);

        }
    }

    return this.results;
};

// Extract regex strings from content and feed results object
ExtractTranslations.prototype.extract = function (regexName, regex, content) {
    var self = this;
    var r;
    if (this.verbose) {
        gutil.log('----------------------------------------------');
        gutil.log('Process extraction with regex : "' + regexName + '"');
        gutil.log(regex);
    }

    regex.lastIndex = 0;

    while ((r = regex.exec(content)) !== null) {
        if (r.length < 2) {
            return;
        }

        // Result expected [STRING, KEY, SOME_REGEX_STUF]
        // Except for plural hack [STRING, KEY, ARRAY_IN_STRING]

        var translationKey,
            evalString,
            translationDefaultValue = "";

        switch (regexName) {
            case 'HtmlDirectivePluralFirst':
                var tmp = r[1];
                r[1] = r[2];
                r[2] = tmp;
                break;
            case 'HtmlDirectivePluralLast':
                evalString = eval(r[2]);
                if (_.isArray(evalString) &&
                    evalString.length >= 2) {
                    translationDefaultValue = "{NB, plural, one{" + evalString[0] + "} other{" + evalString[1] + "}" + (evalString[2] ? ' ' + evalString[2] : '');
                }
                translationKey = r[1].trim();
                break;
            default:
            translationKey = r[1].trim();
        }

        // Avoid empty translation
        if (translationKey === "" || translationKey === undefined) {
            return;
        }

        if (this.namespace && translationKey.split('.').length < 2) {
            throw new Error(chalk.red(
                'Namespace option need tokens with "." delimiter.' +
                '\n Your token conflicts with namespaces: ') +
                translationKey + chalk.red('.\n'));
        }

        switch (regexName) {
            case "commentSimpleQuote":
            case "HtmlFilterSimpleQuote":
            case "HtmlFilterSimpleQuoteOneTimeBinding":
            case "JavascriptServiceSimpleQuote":
            case "JavascriptServiceInstantSimpleQuote":
            case "JavascriptFilterSimpleQuote":
            case "HtmlNgBindHtml":
                translationKey = translationKey.replace(/\\\'/g, "'");
                break;
            case "commentDoubleQuote":
            case "HtmlFilterDoubleQuote":
            case "HtmlFilterDoubleQuoteOneTimeBinding":
            case "JavascriptServiceDoubleQuote":
            case "JavascriptServiceInstantDoubleQuote":
            case "JavascriptFilterDoubleQuote":
                translationKey = translationKey.replace(/\\\"/g, '"');
                break;
            case "JavascriptServiceArraySimpleQuote":
            case "JavascriptServiceArrayDoubleQuote":
                var key;

                if(regexName === "JavascriptServiceArraySimpleQuote") {
                    key = translationKey.replace(/'/g, '');
                } else {
                    key = translationKey.replace(/"/g, '');
                }
                key = key.replace(/[\][]/g, '');
                key = key.split(',');

                key.forEach(function(item){
                    item = item.replace(/\\\"/g, '"').trim();
                    self.results[item] = translationDefaultValue;
                });
                break;
        }

        if( regexName !== "JavascriptServiceArraySimpleQuote" &&
        regexName !== "JavascriptServiceArrayDoubleQuote") {
            self.results[translationKey] = translationDefaultValue;
        }

    }
};

module.exports = ExtractTranslations;
<|endoftext|>


<commit_before>/* global app, _ */

// callback from twitter to embed tweet html
window.addTweet = function (data) {
	var id = data.url.substr(data.url.lastIndexOf('/') + 1);
	var element = $('.tweet_' + id);
	element.empty();
	element.append(data.html);
};

// jesus fucking christ why the god damn hell does regex have state!?
function youtubeRegexp() {
	return /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube(?:-nocookie)?\.com\S*[^\w\s-])([\w-]{11})(?=[^\w-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
}

app.directive('bunkerMessage', function ($sce, $compile, emoticons, bunkerData) {

	function replaceAll(str, find, replace) {
		return str.split(find).join(replace);
	}

	return {
		template: '',
		scope: {
			bunkerMessage: '=',
			media: '@'
		},
		link: function (scope, elem) {

			// since we are passing in a bunker message OR room, run the bunkerText on the correct property
			if (scope.bunkerMessage && scope.bunkerMessage.text) {
				scope.formatted = $sce.trustAsHtml(parseText(scope.bunkerMessage.text));
			}
			else {
				scope.$watch('bunkerMessage.topic', function (topic) {
					elem.html(parseText(topic));
				});
			}

			function parseText(text) {
				if (!text) return;

				if (scope.bunkerMessage.type == 'code') {
					text = parseCode(text);
				}
				else if (scope.bunkerMessage.type == 'hangman') {
					text = parseHangman(text);
				}
				else if (scope.bunkerMessage.type == 'fight') {
					// fight message can have custom images as well as block text
					text = parseFight(text);
				}
				else if (text.match(/
/g)) {  // unicode 10 is tabs/whitespace
					text = createQuotedBlock(text);
				}
				else {
					text = parseFormatting(text);
					if (bunkerData.userSettings.showEmoticons) {
						text = parseEmoticons(text);
					}
					text = parseMedia(text);
				}

				return text;
			}

			function createQuotedBlock(text) {
				// Scan for overtabs
				var lines = text.split('
');
				var firstLineSpacingMatch = _.first(lines).match(/^((	)*)/);

				// If we have some spacing on the first line, remove the same spacing from all other lines (detabs everything)
				if (firstLineSpacingMatch) {
					var firstLineSpacingExpression = new RegExp('^' + firstLineSpacingMatch[1] + '');
					var spacingRemoved = _.map(lines, function (line) {
						return line.replace(firstLineSpacingExpression, '');
					});
					text = spacingRemoved.join('
');
				}

				if (scope.bunkerMessage.type == 'stats') {
					text = parseEmoticons(text);
				}

				var attachedMedia = angular.element('

' + text + '

');

				angular.element(elem).append(attachedMedia);
				$compile(attachedMedia)(scope.$new());

				return '';
			}

			function parseHangman(text) {
				text = parseEmoticons(text);
				var makeDictionaryLink = /\|(.*)\|/i.exec(text);
				if (makeDictionaryLink) {
					var word = makeDictionaryLink[1].split(' ').join('').toLowerCase();
					text = text.replace(/\|(.*)\|/i, '$1');
				}
				return text.replace(/:hangman(\d):/, '');
			}

			function parseFight(text) {
				var match = /:([^0-9:]*):/.exec(text);

				while (match) {
					text = text.replace(/:([^0-9:]*):/, '');
					match = /:([^0-9:]*):/.exec(text);
				}

				// check for a fatality
				match = /:(\w*)*:/.exec(text);
				if (match) {
					var fatality = '';
					text = text.replace(/:(\w*):/, '');
					text = "
" + text + "
" + fatality;
				}
				if (text.match(/
/g)) {  // unicode 10 is tabs/whitespace
					var attachedMedia = angular.element('

' + text + '

');
					angular.element(elem).append(attachedMedia);
					$compile(attachedMedia)(scope.$new());
					return '';
				}

				return text;
			}

			function parseCode(text) {
				var attachedMedia = angular.element('

' + text + '

');
				angular.element(elem).append(attachedMedia);
				$compile(attachedMedia)(scope.$new());
				return '';
			}

			function parseEmoticons(text) {
				var replacedEmotes = {};

				// Parse emoticons
				_.each(text.match(/:\w+:/g), function (emoticonText) {
					var knownEmoticon = _.find(emoticons.all, function (known) {
						return known.file.replace(/\.\w{1,4}$/, '').toLowerCase() == emoticonText.replace(/:/g, '').toLowerCase();
					});
					if (knownEmoticon && !replacedEmotes[knownEmoticon.file]) {
						if (!knownEmoticon.isIcon) { // if an image emoticon (more common)
							text = replaceAll(text, emoticonText,
								'');
						}
						else { // font-awesome icon emoticon
							text = replaceAll(text, emoticonText,
								'');
						}
						replacedEmotes[knownEmoticon.file] = true;
					}
				});
				return text;
			}

			function parseFormatting(text) {

				var types = [
					{marker: '*', tag: 'strong'},
					{marker: '_', tag: 'em'},
					{marker: '~', tag: 'del'},
					{marker: '|', tag: 'mark'}
				];

				_.each(types, function (type) {
					var lookup = new RegExp('(?:[^A-z0-9]|^)(\\' + type.marker + '[^\\' + type.marker + ']+\\' + type.marker + ')(?:[^A-z0-9]|$)', 'g');

					var match;
					while ((match = lookup.exec(text)) !== null) {
						text = replaceAll(text, match[0].trim(), '<' + type.tag + '>' + replaceAll(match[0], type.marker, '') + '</' + type.tag + '>');
					}
				});

				return text;
			}

			function parseMedia(text) {
				var replacedLinks = {};

				var shouldParseMedia = typeof scope.media !== 'undefined' ? scope.$eval(scope.media) : true;

				// Parse links
				var attachedMedia;
				_.each(text.match(/https?:\/\/\S+/gi), function (link) {
					if (!replacedLinks[link]) {
						text = replaceAll(text, link, '' + link + '');
						replacedLinks[link] = true;
					}

					if (!shouldParseMedia) return text;

					// Only parse media (images, youtube) if asked to
					if (/imgur.com\/\w*\.(gifv|webm|mp4)$/i.test(link) && !attachedMedia) {
						var imgurLinkMpeg = link.replace('webm', 'mp4').replace('gifv', 'mp4');
						var imgurLinkWebm = link.replace('mp4', 'webm').replace('gifv', 'webm');
						attachedMedia = angular.element('' +
							'
<video class="imgur-gifv" preload="auto" autoplay muted webkit-playsinline loop><source type="video/webm" src="' + imgurLinkWebm + '"><source type="video/mp4" src="' + imgurLinkMpeg + '"></video>' +
							'
');
					}
					else if (/\.(gifv|mp4|webm)$/i.test(link) && !attachedMedia) {
						attachedMedia = angular.element('' +
							'
' +
							'<video autoplay loop muted><source type="video/mp4" src="' + link.toLowerCase().replace('gifv', 'mp4') + '"></video>' +
							'
');
					}
					else if (youtubeRegexp().test(link) && !attachedMedia) {
						attachedMedia = angular.element('' +
							'
' +
							'<youtube-video video-url="\'' + link + '\'"></youtube-video>' +
							'
');
					}
					else if (/(www\.)?(twitter\.com\/)/i.test(link) && !attachedMedia) {
						var id = link.substr(link.lastIndexOf('/') + 1);
						if (id) { /* don't embed tweet if we can't get the id from the link */
							attachedMedia = angular.element('' +
								'
' +
								'
' +
								'

');
						}
					}
					else if (/vimeo\.com(?:.*)?\/([A-z0-9]*)$/i.test(link) && !attachedMedia) {
						var match = /vimeo\.com(?:.*)?\/([a-zA-Z0-9]*)$/i.exec(link);
						attachedMedia = angular.element('' +
							'
' +
							'' +
							'
');
					}
					else if (/^https?:\/\/(?:play|open)\.spotify\.com\/(.*)/gi.test(link) && !attachedMedia) {
						var match = /^https?:\/\/(?:play|open)\.spotify\.com\/(.*)/gi.exec(link);
						var uri = "spotify%3A" + replaceAll(match[1], '/', '%3A');

						attachedMedia = angular.element('' +
							'
' +
							'' +
							'
');
					}
					else if (/gfycat\.com\/(.*)/gi.test(link) && !attachedMedia) {
						var match = /gfycat\.com\/(.*)/gi.exec(link);

						attachedMedia = angular.element('' +
							'
' +
							'

 ', +
							'
');
					}
					// run this one last since it conflicts with the gifv check above
					else if (/\.(gif|png|jpg|jpeg)/i.test(link) && !attachedMedia) {
						// Image link
						attachedMedia = angular.element('

');
					}
				});

				// If we made an image, attach it now
				if (attachedMedia) {
					angular.element(elem).append(attachedMedia);
					$compile(attachedMedia)(scope.$new());
				}

				return text;
			}
		}
	};
});
<commit_msg>title for icon emoticons<commit_after>/* global app, _ */

// callback from twitter to embed tweet html
window.addTweet = function (data) {
	var id = data.url.substr(data.url.lastIndexOf('/') + 1);
	var element = $('.tweet_' + id);
	element.empty();
	element.append(data.html);
};

// jesus fucking christ why the god damn hell does regex have state!?
function youtubeRegexp() {
	return /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube(?:-nocookie)?\.com\S*[^\w\s-])([\w-]{11})(?=[^\w-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
}

app.directive('bunkerMessage', function ($sce, $compile, emoticons, bunkerData) {

	function replaceAll(str, find, replace) {
		return str.split(find).join(replace);
	}

	return {
		template: '',
		scope: {
			bunkerMessage: '=',
			media: '@'
		},
		link: function (scope, elem) {

			// since we are passing in a bunker message OR room, run the bunkerText on the correct property
			if (scope.bunkerMessage && scope.bunkerMessage.text) {
				scope.formatted = $sce.trustAsHtml(parseText(scope.bunkerMessage.text));
			}
			else {
				scope.$watch('bunkerMessage.topic', function (topic) {
					elem.html(parseText(topic));
				});
			}

			function parseText(text) {
				if (!text) return;

				if (scope.bunkerMessage.type == 'code') {
					text = parseCode(text);
				}
				else if (scope.bunkerMessage.type == 'hangman') {
					text = parseHangman(text);
				}
				else if (scope.bunkerMessage.type == 'fight') {
					// fight message can have custom images as well as block text
					text = parseFight(text);
				}
				else if (text.match(/
/g)) {  // unicode 10 is tabs/whitespace
					text = createQuotedBlock(text);
				}
				else {
					text = parseFormatting(text);
					if (bunkerData.userSettings.showEmoticons) {
						text = parseEmoticons(text);
					}
					text = parseMedia(text);
				}

				return text;
			}

			function createQuotedBlock(text) {
				// Scan for overtabs
				var lines = text.split('
');
				var firstLineSpacingMatch = _.first(lines).match(/^((	)*)/);

				// If we have some spacing on the first line, remove the same spacing from all other lines (detabs everything)
				if (firstLineSpacingMatch) {
					var firstLineSpacingExpression = new RegExp('^' + firstLineSpacingMatch[1] + '');
					var spacingRemoved = _.map(lines, function (line) {
						return line.replace(firstLineSpacingExpression, '');
					});
					text = spacingRemoved.join('
');
				}

				if (scope.bunkerMessage.type == 'stats') {
					text = parseEmoticons(text);
				}

				var attachedMedia = angular.element('

' + text + '

');

				angular.element(elem).append(attachedMedia);
				$compile(attachedMedia)(scope.$new());

				return '';
			}

			function parseHangman(text) {
				text = parseEmoticons(text);
				var makeDictionaryLink = /\|(.*)\|/i.exec(text);
				if (makeDictionaryLink) {
					var word = makeDictionaryLink[1].split(' ').join('').toLowerCase();
					text = text.replace(/\|(.*)\|/i, '$1');
				}
				return text.replace(/:hangman(\d):/, '');
			}

			function parseFight(text) {
				var match = /:([^0-9:]*):/.exec(text);

				while (match) {
					text = text.replace(/:([^0-9:]*):/, '');
					match = /:([^0-9:]*):/.exec(text);
				}

				// check for a fatality
				match = /:(\w*)*:/.exec(text);
				if (match) {
					var fatality = '';
					text = text.replace(/:(\w*):/, '');
					text = "
" + text + "
" + fatality;
				}
				if (text.match(/
/g)) {  // unicode 10 is tabs/whitespace
					var attachedMedia = angular.element('

' + text + '

');
					angular.element(elem).append(attachedMedia);
					$compile(attachedMedia)(scope.$new());
					return '';
				}

				return text;
			}

			function parseCode(text) {
				var attachedMedia = angular.element('

' + text + '

');
				angular.element(elem).append(attachedMedia);
				$compile(attachedMedia)(scope.$new());
				return '';
			}

			function parseEmoticons(text) {
				var replacedEmotes = {};

				// Parse emoticons
				_.each(text.match(/:\w+:/g), function (emoticonText) {
					var knownEmoticon = _.find(emoticons.all, function (known) {
						return known.file.replace(/\.\w{1,4}$/, '').toLowerCase() == emoticonText.replace(/:/g, '').toLowerCase();
					});
					if (knownEmoticon && !replacedEmotes[knownEmoticon.file]) {
						if (!knownEmoticon.isIcon) { // if an image emoticon (more common)
							text = replaceAll(text, emoticonText,
								'');
						}
						else { // font-awesome icon emoticon
							text = replaceAll(text, emoticonText,
								'');
						}
						replacedEmotes[knownEmoticon.file] = true;
					}
				});
				return text;
			}

			function parseFormatting(text) {

				var types = [
					{marker: '*', tag: 'strong'},
					{marker: '_', tag: 'em'},
					{marker: '~', tag: 'del'},
					{marker: '|', tag: 'mark'}
				];

				_.each(types, function (type) {
					var lookup = new RegExp('(?:[^A-z0-9]|^)(\\' + type.marker + '[^\\' + type.marker + ']+\\' + type.marker + ')(?:[^A-z0-9]|$)', 'g');

					var match;
					while ((match = lookup.exec(text)) !== null) {
						text = replaceAll(text, match[0].trim(), '<' + type.tag + '>' + replaceAll(match[0], type.marker, '') + '</' + type.tag + '>');
					}
				});

				return text;
			}

			function parseMedia(text) {
				var replacedLinks = {};

				var shouldParseMedia = typeof scope.media !== 'undefined' ? scope.$eval(scope.media) : true;

				// Parse links
				var attachedMedia;
				_.each(text.match(/https?:\/\/\S+/gi), function (link) {
					if (!replacedLinks[link]) {
						text = replaceAll(text, link, '' + link + '');
						replacedLinks[link] = true;
					}

					if (!shouldParseMedia) return text;

					// Only parse media (images, youtube) if asked to
					if (/imgur.com\/\w*\.(gifv|webm|mp4)$/i.test(link) && !attachedMedia) {
						var imgurLinkMpeg = link.replace('webm', 'mp4').replace('gifv', 'mp4');
						var imgurLinkWebm = link.replace('mp4', 'webm').replace('gifv', 'webm');
						attachedMedia = angular.element('' +
							'
<video class="imgur-gifv" preload="auto" autoplay muted webkit-playsinline loop><source type="video/webm" src="' + imgurLinkWebm + '"><source type="video/mp4" src="' + imgurLinkMpeg + '"></video>' +
							'
');
					}
					else if (/\.(gifv|mp4|webm)$/i.test(link) && !attachedMedia) {
						attachedMedia = angular.element('' +
							'
' +
							'<video autoplay loop muted><source type="video/mp4" src="' + link.toLowerCase().replace('gifv', 'mp4') + '"></video>' +
							'
');
					}
					else if (youtubeRegexp().test(link) && !attachedMedia) {
						attachedMedia = angular.element('' +
							'
' +
							'<youtube-video video-url="\'' + link + '\'"></youtube-video>' +
							'
');
					}
					else if (/(www\.)?(twitter\.com\/)/i.test(link) && !attachedMedia) {
						var id = link.substr(link.lastIndexOf('/') + 1);
						if (id) { /* don't embed tweet if we can't get the id from the link */
							attachedMedia = angular.element('' +
								'
' +
								'
' +
								'

');
						}
					}
					else if (/vimeo\.com(?:.*)?\/([A-z0-9]*)$/i.test(link) && !attachedMedia) {
						var match = /vimeo\.com(?:.*)?\/([a-zA-Z0-9]*)$/i.exec(link);
						attachedMedia = angular.element('' +
							'
' +
							'' +
							'
');
					}
					else if (/^https?:\/\/(?:play|open)\.spotify\.com\/(.*)/gi.test(link) && !attachedMedia) {
						var match = /^https?:\/\/(?:play|open)\.spotify\.com\/(.*)/gi.exec(link);
						var uri = "spotify%3A" + replaceAll(match[1], '/', '%3A');

						attachedMedia = angular.element('' +
							'
' +
							'' +
							'
');
					}
					else if (/gfycat\.com\/(.*)/gi.test(link) && !attachedMedia) {
						var match = /gfycat\.com\/(.*)/gi.exec(link);

						attachedMedia = angular.element('' +
							'
' +
							'

 ', +
							'
');
					}
					// run this one last since it conflicts with the gifv check above
					else if (/\.(gif|png|jpg|jpeg)/i.test(link) && !attachedMedia) {
						// Image link
						attachedMedia = angular.element('

');
					}
				});

				// If we made an image, attach it now
				if (attachedMedia) {
					angular.element(elem).append(attachedMedia);
					$compile(attachedMedia)(scope.$new());
				}

				return text;
			}
		}
	};
});
<|endoftext|>