<commit_before>/*jshint expr:true, onevar:false */

/**
Provides the `Editor.Style` extension.

@module gallery-sm-editor
@submodule gallery-sm-editor-style
**/

/**
Extension for `Editor.Base` that normalizes style commands into css properties

@class Editor.Style
@constructor
@extends Base
@extensionfor Editor.Base
**/

(function() {

var ELEMENT_NODE = 1;

var EditorStyle = Y.Base.create('editorStyle', Y.Base, [], {
    // -- Public Properties ----------------------------------------------------

    /**
    Hash of style commands supported by this editor.

    Names should correspond with valid `execCommand()` command names. Values
    are properties in the following format:

    @property {Object} styleCommands
      @param {String} property The name of the CSS property in camelCase form
      @param {String} [valueOn] The `on` value of the property. eg. `bold`
      @param {String} [valueOff] The `off` value of the property. eg. `none`
    **/
    styleCommands: {
        bold: {
            property: 'fontWeight',
            valueOn: 'bold',
            valueOff: 'normal'
        },

        fontName: {
            property: 'fontFamily'
        },

        fontSize: {
            property: 'fontSize'
        },

        italic: {
            property: 'fontStyle',
            valueOn: 'italic',
            valueOff: 'normal'
        },

//        justifyCenter: {
//            property: 'textAlign',
//            value: 'center'
//        },
//
//        justifyLeft: {
//            property: 'textAlign',
//            value: 'left'
//        },
//
//        justifyRight: {
//            property: 'textAlign',
//            value: 'right'
//        },

        underline: {
            property: 'textDecoration',
            valueOn: 'underline',
            valueOff: 'none'
        }
    },

    // -- Public Methods -------------------------------------------------------

    /**
    Bolds or unbolds the current selection.

    @method bold
    @chainable
    **/
    bold: function () {
        this.command('bold', 'toggle');
        return this;
    },

    /**
    Italicizes or unitalicizes the current selection.

    @method italic
    @chainable
    **/
    italic: function () {
        this.command('italic', 'toggle');
        return this;
    },

    /**
    Gets and/or sets the values of multiple editor style commands.

    When called without an argument, the current values of all supported style
    commands will be returned. When called with a _styles_ object, the specified
    style commands will be set to their given values, and the resulting new
    values will be returned.

    @method styles
    @param {Object} [styles] Hash of style names and values to set.
    @return {Object} Hash of style names and values that were set, or all styles
        if no _styles_ parameter was specified.
    **/
    styles: function (styles) {
        var results = {},
            name;

        if (styles) {
            for (name in styles) {
                if (styles.hasOwnProperty(name)) {
                    results[name] = this.command(name, styles[name]);
                }
            }
        } else {
            var commands = this.styleCommands;

            for (name in commands) {
                if (commands.hasOwnProperty(name)) {
                    results[name] = this._queryCommandValue(name);
                }
            }
        }

        return results;
    },

    /**
    Toggles underline on the current selection.

    @method underline
    @chainable
    **/
    underline: function () {
        this.command('underline', 'toggle');
        return this;
    },

    // -- Protected Methods ----------------------------------------------------

    /**
    Traverses the children of a given node, removing empty nodes and
    clearing out any given css properties

    @method _cleanNode
    @param {Node} rootNode
    @param {String|Array} properties
    @return {Node} The cleaned node
    @protected
    **/
    _cleanNode: function(rootNode, properties) {
        var styleCommands = this.styleCommands;

        properties = Y.Array(properties);

        function clean(node) {
            if (ELEMENT_NODE !== node.get('nodeType')) {
                return;
            }

            if (node !== rootNode) {
                if ('' === node.get('text')) {
                    // the node is empty, remove it
                    return node.remove(true);
                } else {
                    // clear out the properties
                    Y.Array.each(properties, function(style) {
                        node.setStyle(style, '');
                    });
                }
            }

            node.get('children').each(clean);

            // if this node doesn't have any other valid style properties
            // on it, we can unwrap it into a text node.
            // chrome likes to randomly add line-height for example.
            var hasStyles = Y.Object.some(styleCommands, function(cmd) {
                return '' !== node._node.style[cmd.property];
            });

            if (!hasStyles) {
                var newNode = Y.Node.create(node.getHTML());
                node.replace(newNode).remove(true);
                node = newNode;
            }

            return node;
        }

        return clean(rootNode);
    },

    /**
    Duckpunch Editor.Base._execCommand to build css styled nodes instead of
    relying on spotty browser compatibility of `styleWithCSS`

    Passes through to Editor.Base for any commands not defined
    in `this.styleCommands`

    @method _execCommand
    @param {String} name Command name.
    @param {Boolean|String} value Command value.
    @protected
    **/
    _execCommand: function(name, value) {
        var command = this.styleCommands[name],
            range = this.selection.range(),
            parentNode, styleNode;

        if (!command) {
            return Y.Editor.Base.prototype._execCommand.call(this, name, value);
        }

        if (!range) {
            return;
        }

        parentNode = range.parentNode();

        if (ELEMENT_NODE !== parentNode.get('nodeType')) {
            // range.parentNode() may return a text node
            // so make sure we actually have an element
            parentNode = parentNode.ancestor();
        }

        if (parentNode !== this._inputNode &&
            parentNode.get('text') === range.toString()) {
            // if the entire text of a node is selected, just apply
            // styles to that node, as long as its not the input node
            styleNode = parentNode;
        } else {
            // otherwise, wrap the selection in a new node
            styleNode = range.wrap('<span>');
        }

        if (this.boolCommands[name]) {
            if (this._queryCommandValue(name)) {
                // already set, disable
                // determine whether an ancestor has this property set
                // or not so we can simply unset the value instead of
                // setting it to `none` or `normal` or whatever
                value = this._getStyledAncestor(styleNode, command.property) ? command.valueOff : '';
            } else {
                // need to enable, set appropriate value
                value = command.valueOn || value;
            }
        }

        styleNode.setStyle(command.property, value);

        styleNode = this._cleanNode(styleNode, command.property);

        // when cleaning nodes the selection may get lost if the styleNode
        // gets unwrapped into a text node or has children unwrapped
        // so we need to rebuild the range and select it to give the
        // appearance the selection never changed
        range.startNode(styleNode);
        range.endNode(styleNode);

        // set the endOffset based on the node type
        if (ELEMENT_NODE === styleNode.get('nodeType')) {
            range.endOffset(styleNode.get('childNodes').size());
        } else {
            range.endOffset(styleNode.get('text').length);
        }

        this.selection.select(range);
    },

    /**
    Duckpunch Editor.Base _queryCommandValue to query the css properties of nodes
    instead of relying on spotty browser compatibility of `styleWithCSS`

    Passes through to Editor.Base for any commands not defined
    in `this.styleCommands`

    @method _queryCommandValue
    @param {String} name Command name.
    @return {Boolean|String} Command value.
    @protected
    **/
    _queryCommandValue: function(name) {
        var command = this.styleCommands[name],
            range = this.selection.range(),
            parentNode, styleNode, value;

        if (!command) {
            return Y.Editor.Base.prototype._queryCommandValue.call(this, name);
        }

        if (range) {
            parentNode = range.shrink().parentNode();

            // walk the ancestor tree to find the closest
            // element node ancestor, inclusive of the parentNode
            styleNode = parentNode.ancestor(function(node) {
                return ELEMENT_NODE === node.get('nodeType');
            }, true);

            // getStyle will fall back to computedStyle if the
            // property isn't explicitly set
            value = styleNode.getStyle(command.property);

            if (this.boolCommands[name]) {
                value = (value === command.valueOn);
            }
        }

        return value;
    }
});

Y.namespace('Editor').Style = EditorStyle;

}());
<commit_msg>Editor.Style: restore _getStyledAncestor<commit_after>/*jshint expr:true, onevar:false */

/**
Provides the `Editor.Style` extension.

@module gallery-sm-editor
@submodule gallery-sm-editor-style
**/

/**
Extension for `Editor.Base` that normalizes style commands into css properties

@class Editor.Style
@constructor
@extends Base
@extensionfor Editor.Base
**/

(function() {

var ELEMENT_NODE = 1;

var EditorStyle = Y.Base.create('editorStyle', Y.Base, [], {
    // -- Public Properties ----------------------------------------------------

    /**
    Hash of style commands supported by this editor.

    Names should correspond with valid `execCommand()` command names. Values
    are properties in the following format:

    @property {Object} styleCommands
      @param {String} property The name of the CSS property in camelCase form
      @param {String} [valueOn] The `on` value of the property. eg. `bold`
      @param {String} [valueOff] The `off` value of the property. eg. `none`
    **/
    styleCommands: {
        bold: {
            property: 'fontWeight',
            valueOn: 'bold',
            valueOff: 'normal'
        },

        fontName: {
            property: 'fontFamily'
        },

        fontSize: {
            property: 'fontSize'
        },

        italic: {
            property: 'fontStyle',
            valueOn: 'italic',
            valueOff: 'normal'
        },

//        justifyCenter: {
//            property: 'textAlign',
//            value: 'center'
//        },
//
//        justifyLeft: {
//            property: 'textAlign',
//            value: 'left'
//        },
//
//        justifyRight: {
//            property: 'textAlign',
//            value: 'right'
//        },

        underline: {
            property: 'textDecoration',
            valueOn: 'underline',
            valueOff: 'none'
        }
    },

    // -- Public Methods -------------------------------------------------------

    /**
    Bolds or unbolds the current selection.

    @method bold
    @chainable
    **/
    bold: function () {
        this.command('bold', 'toggle');
        return this;
    },

    /**
    Italicizes or unitalicizes the current selection.

    @method italic
    @chainable
    **/
    italic: function () {
        this.command('italic', 'toggle');
        return this;
    },

    /**
    Gets and/or sets the values of multiple editor style commands.

    When called without an argument, the current values of all supported style
    commands will be returned. When called with a _styles_ object, the specified
    style commands will be set to their given values, and the resulting new
    values will be returned.

    @method styles
    @param {Object} [styles] Hash of style names and values to set.
    @return {Object} Hash of style names and values that were set, or all styles
        if no _styles_ parameter was specified.
    **/
    styles: function (styles) {
        var results = {},
            name;

        if (styles) {
            for (name in styles) {
                if (styles.hasOwnProperty(name)) {
                    results[name] = this.command(name, styles[name]);
                }
            }
        } else {
            var commands = this.styleCommands;

            for (name in commands) {
                if (commands.hasOwnProperty(name)) {
                    results[name] = this._queryCommandValue(name);
                }
            }
        }

        return results;
    },

    /**
    Toggles underline on the current selection.

    @method underline
    @chainable
    **/
    underline: function () {
        this.command('underline', 'toggle');
        return this;
    },

    // -- Protected Methods ----------------------------------------------------

    /**
    Traverses the children of a given node, removing empty nodes and
    clearing out any given css properties

    @method _cleanNode
    @param {Node} rootNode
    @param {String|Array} properties
    @return {Node} The cleaned node
    @protected
    **/
    _cleanNode: function(rootNode, properties) {
        var styleCommands = this.styleCommands;

        properties = Y.Array(properties);

        function clean(node) {
            if (ELEMENT_NODE !== node.get('nodeType')) {
                return;
            }

            if (node !== rootNode) {
                if ('' === node.get('text')) {
                    // the node is empty, remove it
                    return node.remove(true);
                } else {
                    // clear out the properties
                    Y.Array.each(properties, function(style) {
                        node.setStyle(style, '');
                    });
                }
            }

            node.get('children').each(clean);

            // if this node doesn't have any other valid style properties
            // on it, we can unwrap it into a text node.
            // chrome likes to randomly add line-height for example.
            var hasStyles = Y.Object.some(styleCommands, function(cmd) {
                return '' !== node._node.style[cmd.property];
            });

            if (!hasStyles) {
                var newNode = Y.Node.create(node.getHTML());
                node.replace(newNode).remove(true);
                node = newNode;
            }

            return node;
        }

        return clean(rootNode);
    },

    /**
    Duckpunch Editor.Base._execCommand to build css styled nodes instead of
    relying on spotty browser compatibility of `styleWithCSS`

    Passes through to Editor.Base for any commands not defined
    in `this.styleCommands`

    @method _execCommand
    @param {String} name Command name.
    @param {Boolean|String} value Command value.
    @protected
    **/
    _execCommand: function(name, value) {
        var command = this.styleCommands[name],
            range = this.selection.range(),
            parentNode, styleNode;

        if (!command) {
            return Y.Editor.Base.prototype._execCommand.call(this, name, value);
        }

        if (!range) {
            return;
        }

        parentNode = range.parentNode();

        if (ELEMENT_NODE !== parentNode.get('nodeType')) {
            // range.parentNode() may return a text node
            // so make sure we actually have an element
            parentNode = parentNode.ancestor();
        }

        if (parentNode !== this._inputNode &&
            parentNode.get('text') === range.toString()) {
            // if the entire text of a node is selected, just apply
            // styles to that node, as long as its not the input node
            styleNode = parentNode;
        } else {
            // otherwise, wrap the selection in a new node
            styleNode = range.wrap('<span>');
        }

        if (this.boolCommands[name]) {
            if (this._queryCommandValue(name)) {
                // already set, disable
                // determine whether an ancestor has this property set
                // or not so we can simply unset the value instead of
                // setting it to `none` or `normal` or whatever
                value = this._getStyledAncestor(styleNode, command.property) ? command.valueOff : '';
            } else {
                // need to enable, set appropriate value
                value = command.valueOn || value;
            }
        }

        styleNode.setStyle(command.property, value);

        styleNode = this._cleanNode(styleNode, command.property);

        // when cleaning nodes the selection may get lost if the styleNode
        // gets unwrapped into a text node or has children unwrapped
        // so we need to rebuild the range and select it to give the
        // appearance the selection never changed
        range.startNode(styleNode);
        range.endNode(styleNode);

        // set the endOffset based on the node type
        if (ELEMENT_NODE === styleNode.get('nodeType')) {
            range.endOffset(styleNode.get('childNodes').size());
        } else {
            range.endOffset(styleNode.get('text').length);
        }

        this.selection.select(range);
    },

    /**
    Walks the ancestor tree of a given node until a node that has
    the css property set is found

    @method _getStyledAncestor
    @param {Node} startNode
    @param {String} property
    @param {Boolean} [self] Whether or not to include `startNode` in the scan
    @return {Node} The node having `property` set, or null if no node was found
    @protected
    **/
    _getStyledAncestor: function(startNode, property, self) {
        return startNode.ancestor(function(node) {
            if (ELEMENT_NODE !== node._node.nodeType) {
                return false;
            }

            // don't use node.getStyle() because it will return
            // computedStyle for empty string values like `property: ""`
            // https://github.com/yui/yui3/blob/master/src/dom/js/dom-style.js#L106
            return !!node._node.style[property];
        }, self, this.selectors.input);
    },

    /**
    Duckpunch Editor.Base _queryCommandValue to query the css properties of nodes
    instead of relying on spotty browser compatibility of `styleWithCSS`

    Passes through to Editor.Base for any commands not defined
    in `this.styleCommands`

    @method _queryCommandValue
    @param {String} name Command name.
    @return {Boolean|String} Command value.
    @protected
    **/
    _queryCommandValue: function(name) {
        var command = this.styleCommands[name],
            range = this.selection.range(),
            parentNode, styleNode, value;

        if (!command) {
            return Y.Editor.Base.prototype._queryCommandValue.call(this, name);
        }

        if (range) {
            parentNode = range.shrink().parentNode();

            // walk the ancestor tree to find the closest
            // element node ancestor, inclusive of the parentNode
            styleNode = parentNode.ancestor(function(node) {
                return ELEMENT_NODE === node.get('nodeType');
            }, true);

            // getStyle will fall back to computedStyle if the
            // property isn't explicitly set
            value = styleNode.getStyle(command.property);

            if (this.boolCommands[name]) {
                value = (value === command.valueOn);
            }
        }

        return value;
    }
});

Y.namespace('Editor').Style = EditorStyle;

}());
<|endoftext|>


<commit_before>"use strict";

$.require([
    'node!events',
    'core!bash',
    'hyperion!/lib/docker/format.js'
], function(
    events,
    bash,
    _
) {

    var obj = function() {
        this._staticRegistry = {
            login: false,
            local: false
        };
        this._pull = {};
    };
    obj.prototype = $.extends('hyperion!/lib/docker/env.js', {
        /**
         * are we skipping the us of registry?
         *
         * @returns {boolean}
         */
        isLocal: function() {
            return (this._staticRegistry.local);
        },

        getBacklog: function() {
            var out = {};
            for (var i in this._pull) {
                out[i] = {
                    image: this._pull[i].image,
                    log: this._pull[i].log,
                    date: this._pull[i].date,
                    status: this._pull[i].status
                }
            }
            return (out);
        },

        /**
         * Login into private registry to pull or push images
         *
         * @param force
         * @returns {*}
         */
        login: function(force) {
            var self = this, registry = $.schema.copy($.config.get('docker.registry')) || {};
            registry = ($.is.array(registry)) ? registry : [registry];

            if (this._staticRegistry.login) {
                return ($.promise().resolve(true));
            }

            if ((!$.defined(registry) || registry.isLocal) && !force)  {
                this._staticRegistry.login = true;
                this._staticRegistry.local = true;
                console.log('no registry given will work with a local version.');
                return ($.promise().resolve(true));
            }

            return (this._loadEnv().then(function(env) {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker login -u ' + registry[i].user + ' -p ' + registry[i].password + ' ' + registry[i].address, env, true).then((res) => {
                        if (res.out.length == 0 || res.out.join('\n').indexOf('Login Succeeded') == -1) {
                            return (false);
                        }
                        return (true);
                    }));
                }
                return $.all(wait);
            }).then(function(res) {
                let loggedin = true;
                for (let i in res) {
                    if (!res[i]) {
                        loggedin = false;
                    }
                }
                if (!loggedin) {
                    self._staticRegistry.local = true;
                    self._staticRegistry.login = false;
                    registry.password = '****';
                    return (false);
                }
                self._staticRegistry.local = false;
                self._staticRegistry.login = true;
                return (true);
            }));
        },

        /**
         * Logout of a registry (remove the auth from docker)
         * @returns {*|obj|Promise}
         */
        logout: function() {
            var self = this, registry = $.config.get('docker.registry');
            registry = ($.is.array(registry)) ? registry : [registry];

            return (this._loadEnv().then(function(env) {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker logout ' + registry.address, env, true));
                }
                return $.all(wait);
            }).then(function() {
                self._staticRegistry.login = false;
                return (true);
            }));
        },

        /**
         * Push image from system to remote registry
         *
         * @param image
         * @returns {*|obj}
         */
        push: function(image) {
            var self = this, registry = $.config.get('docker.registry'), env = null;
            registry = ($.is.array(registry)) ? registry : [registry];

            return (this._loadEnv().then(function(e) {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker tag ' + image + ' ' +  registry[i].address + '/' + image, (env = e), true));
                }
                return $.all(wait);
            }).then(function() {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker push ' +  registry[i].address + '/' + _.version(image), env, true));
                }
                return $.all(wait);
            }).then(function(res) {
                for (let i in res) {
                    if (res[i].err.length != 0) {
                        console.log('Failed to pull image building from local.');
                        return ($.promise().reject());
                    }
                }
                return (true);
            }));
        },

        /**
         * Pull image from remote (they then after need to be re-tagged for a clean local use)
         *
         * @param image
         * @param cd
         * @returns {*|obj|Promise}
         */
        pull: function(image, cd) { // TODO: bug here does not pull
            var self = this, registry = $.config.get('docker.registry'), env, out = {};
            registry = ($.is.array(registry)) ? registry[0] : registry;

            var path = registry.address + '/' + _.version(image), key = $.key.random();
            cd = ($.is.function(cd)) ? cd : function() {};

            if (self._pull[path] && self._pull[path].status == 'running') {
                var p = new $.promise();
                cd({info: 'waiting for image to be pulled "' + image + '"'});
                self._pull[path].emitter.once('pull', function(res) {
                    cd({info: 'finished pulling image "' + image + '"'});
                    p.resolve(res);
                });
                return (p);
            }

            console.log('trying to pull', image);
            return (this._loadEnv().then(function(e) {
                cd({info: 'pulling image "' + path + '" pull progress key "' + key + '"'});
                self._pull[path] = {
                    image: path,
                    log: [],
                    emitter: new events(),
                    date: {start: $.time.now().get, update: null, end: null},
                    status: 'running'
                };

                return (bash.run('docker pull ' + path, (env = e), function(msg) {
                    console.log(msg);
                    self._pull[path].log.push(msg);
                    self._pull[path].date.update = $.time.now().get;
                    cd({info: msg});
                }));
            }).then(function(res) {
                if (res.err.length != 0) {
                    console.log('Failed to pull image building from local.');
                    return ($.promise().reject(res.error));
                }

                out = $.schema.merge().array(out, res);
                return (bash.run('docker tag ' + path + ' ' + _.version(image), env, function(msg) {
                    cd({info: msg});
                }));
            }).then(function(res) {
                out = $.schema.merge().array(out, res);
                self._pull[path].status = 'finished';
                self._pull[path].date.end = $.time.now().get;
                self._pull[path].emitter.emit('pull', out);

                cd({info: 'finished pulling image "' + image + '"'});
                return (out);
            }, function(err) {
                return ($.promise().reject(err));
            }));
        }
    });

    module.exports = obj;
});
<commit_msg>changed hype config<commit_after>"use strict";

$.require([
    'node!events',
    'core!bash',
    'hyperion!/lib/docker/format.js'
], function(
    events,
    bash,
    _
) {

    var obj = function() {
        this._staticRegistry = {
            login: false,
            local: false
        };
        this._pull = {};
    };
    obj.prototype = $.extends('hyperion!/lib/docker/env.js', {
        /**
         * are we skipping the us of registry?
         *
         * @returns {boolean}
         */
        isLocal: function() {
            return (this._staticRegistry.local);
        },

        getBacklog: function() {
            var out = {};
            for (var i in this._pull) {
                out[i] = {
                    image: this._pull[i].image,
                    log: this._pull[i].log,
                    date: this._pull[i].date,
                    status: this._pull[i].status
                }
            }
            return (out);
        },

        /**
         * Login into private registry to pull or push images
         *
         * @param force
         * @returns {*}
         */
        login: function(force) {
            var self = this, registry = $.schema.copy($.config.get('docker.registry')) || {};
            registry = ($.is.array(registry)) ? registry : [registry];

            if (this._staticRegistry.login) {
                return ($.promise().resolve(true));
            }

            if ($.config.get('docker.env.isLocal'))  {
                this._staticRegistry.login = true;
                this._staticRegistry.local = true;
                console.log('no registry given will work with a local version.');
                return ($.promise().resolve(true));
            }

            return (this._loadEnv().then(function(env) {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker login -u ' + registry[i].user + ' -p ' + registry[i].password + ' ' + registry[i].address, env, true).then((res) => {
                        if (res.out.length == 0 || res.out.join('\n').indexOf('Login Succeeded') == -1) {
                            return (false);
                        }
                        return (true);
                    }));
                }
                return $.all(wait);
            }).then(function(res) {
                let loggedin = true;
                for (let i in res) {
                    if (!res[i]) {
                        loggedin = false;
                    }
                }
                if (!loggedin) {
                    self._staticRegistry.local = true;
                    self._staticRegistry.login = false;
                    registry.password = '****';
                    return (false);
                }
                self._staticRegistry.local = false;
                self._staticRegistry.login = true;
                return (true);
            }));
        },

        /**
         * Logout of a registry (remove the auth from docker)
         * @returns {*|obj|Promise}
         */
        logout: function() {
            var self = this, registry = $.config.get('docker.registry');
            registry = ($.is.array(registry)) ? registry : [registry];

            return (this._loadEnv().then(function(env) {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker logout ' + registry.address, env, true));
                }
                return $.all(wait);
            }).then(function() {
                self._staticRegistry.login = false;
                return (true);
            }));
        },

        /**
         * Push image from system to remote registry
         *
         * @param image
         * @returns {*|obj}
         */
        push: function(image) {
            var self = this, registry = $.config.get('docker.registry'), env = null;
            registry = ($.is.array(registry)) ? registry : [registry];

            return (this._loadEnv().then(function(e) {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker tag ' + image + ' ' +  registry[i].address + '/' + image, (env = e), true));
                }
                return $.all(wait);
            }).then(function() {
                let wait = [];
                for (let i in registry) {
                    wait.push(bash.run('docker push ' +  registry[i].address + '/' + _.version(image), env, true));
                }
                return $.all(wait);
            }).then(function(res) {
                for (let i in res) {
                    if (res[i].err.length != 0) {
                        console.log('Failed to pull image building from local.');
                        return ($.promise().reject());
                    }
                }
                return (true);
            }));
        },

        /**
         * Pull image from remote (they then after need to be re-tagged for a clean local use)
         *
         * @param image
         * @param cd
         * @returns {*|obj|Promise}
         */
        pull: function(image, cd) { // TODO: bug here does not pull
            var self = this, registry = $.config.get('docker.registry'), env, out = {};
            registry = ($.is.array(registry)) ? registry[0] : registry;

            var path = registry.address + '/' + _.version(image), key = $.key.random();
            cd = ($.is.function(cd)) ? cd : function() {};

            if (self._pull[path] && self._pull[path].status == 'running') {
                var p = new $.promise();
                cd({info: 'waiting for image to be pulled "' + image + '"'});
                self._pull[path].emitter.once('pull', function(res) {
                    cd({info: 'finished pulling image "' + image + '"'});
                    p.resolve(res);
                });
                return (p);
            }

            console.log('trying to pull', image);
            return (this._loadEnv().then(function(e) {
                cd({info: 'pulling image "' + path + '" pull progress key "' + key + '"'});
                self._pull[path] = {
                    image: path,
                    log: [],
                    emitter: new events(),
                    date: {start: $.time.now().get, update: null, end: null},
                    status: 'running'
                };

                return (bash.run('docker pull ' + path, (env = e), function(msg) {
                    console.log(msg);
                    self._pull[path].log.push(msg);
                    self._pull[path].date.update = $.time.now().get;
                    cd({info: msg});
                }));
            }).then(function(res) {
                if (res.err.length != 0) {
                    console.log('Failed to pull image building from local.');
                    return ($.promise().reject(res.error));
                }

                out = $.schema.merge().array(out, res);
                return (bash.run('docker tag ' + path + ' ' + _.version(image), env, function(msg) {
                    cd({info: msg});
                }));
            }).then(function(res) {
                out = $.schema.merge().array(out, res);
                self._pull[path].status = 'finished';
                self._pull[path].date.end = $.time.now().get;
                self._pull[path].emitter.emit('pull', out);

                cd({info: 'finished pulling image "' + image + '"'});
                return (out);
            }, function(err) {
                return ($.promise().reject(err));
            }));
        }
    });

    module.exports = obj;
});
<|endoftext|>


<commit_before>'use strict';

var configs = {
  PING_INTERVAL: 1000 * 60 * 60, // ms * s * h every hour
  IP_PROVIDER_URL: 'http://canihazip.com/s',
  DEST_URL: 'https://[your Heroku URL].com'
};

module.exports = configs;
<commit_msg>updating sample<commit_after>'use strict';

var configs = {
  PING_INTERVAL: 1000 * 60 * 60, // ms * s * h = every hour
  IP_PROVIDER_URL: 'http://canihazip.com/s',
  DEST_URL: 'https://<your heroku app name>.herokuapp.com'
};

module.exports = configs;
<|endoftext|>


<commit_before>;(function(root) {
'use strict';

var Backbone = root.Backbone || require('backbone');
var Cord = Backbone.Cord;
var extendObj = Cord.extendObj;
var isPlainObj = Cord.isPlainObj;
var mixProto = Cord.mixProto;
var ForceValue = Cord.ForceValue;

var convertToString = Cord.convertToString;
var encodeValue = Cord.encodeValue;
var decodeValue = Cord.decodeValue;
var randomGUID = Cord.randomGUID;
var setImmediate = Cord.setImmediate;
var regex = Cord.regex;

var react = root.preact || root.react || require('preact') || require('react');
var Component = react.Component;
var options = react.options;

var _currentComponent = null;
var _currentBinding = null;
var _DATA_BINDING_ATTR = 'data-binding-guid';
var _DATA_FEEDBACK_ATTR = 'data-binding-feedback';

function _bindGUID(uid, proxy) {
	var guid = null;
	if(uid) {
		guid = _currentComponent._boundGUIDs[uid] || (_currentComponent._boundGUIDs[uid] = randomGUID());
		_currentComponent._boundGUIDProxies[guid] = proxy;
	}
	return guid;
}

var bind = Cord.bind = function(key, guid) {
	if(!_currentComponent)
		return;
	var boundKey = guid || key;
	if(!_currentComponent._boundKeys[boundKey]) {
		_currentComponent.observe(key, function(key, value) {
			// Wrapped in a function to remove the key, value arguments from passing to forceUpdate
			if(guid && this._boundGUIDProxies[guid])
				this._boundGUIDProxies[guid].call(this, guid, key, value);
			else
				this.forceUpdate();
		});
		_currentComponent._boundKeys[boundKey] = true;
	}
	return _currentComponent.getValueForKey(key);
};

var computed = Cord.computed = function(func) {
	var args = Cord.getFunctionArgs(func);
	if(!args.length)
		return func;
	var compFunc = function(compKey) {
		var i, values = [], state = {};
		compKey = compKey.split('@')[0];
		for(i = 0; i < args.length; ++i)
			values.push(this.getValueForKey(args[i]));
		state[compKey] = func.apply(this, values);
		this.setState(state);
	};
	compFunc._args = args;
	compFunc._computed = true;
	return compFunc;
};

// Add the default scope and have it mirror this.state
Cord.scopes.this = {
	observe: function() {},
	unobserve: function() {},
	getValue: function(key) {
		return this.state[key];
	},
	setValue: function(key, value) {
		var change = {};
		// For compatibility for some older mixins with readonly BB properties
		if(typeof value === 'object' && Object.getPrototypeOf(value) === ForceValue.prototype)
			value = value.value;
		change[key] = value;
		this.setState(change);
	}
};

function _normalizeMixin(mixin) {
	var methods = {};
	var state = {};
	var attr, value;
	var lifecycleMap = {
		initialize: 'componentWillMount',
		remove: 'componentWillUnmount'
	};
	if(mixin.properties) {
		for(attr in mixin.properties) {
			if(mixin.properties.hasOwnProperty(attr)) {
				value = mixin.properties[attr];
				if(typeof value === 'function')
					state[attr] = computed(value);
				else if(isPlainObj(value))
					state[attr] = value.get ? computed(value.get) : value.value;
				else state[attr] = value;
			}
		}
	}
	for(attr in mixin) {
		if(mixin.hasOwnProperty(attr)) {
			value = mixin[attr];
			if(typeof value === 'function')
				methods[(lifecycleMap[attr] || attr)] = value;
		}
	}
	mixin.methods = methods;
	mixin.state = state;
	mixin._normalized = true;
}

// Inherit from react's Component so render can be wrapped and mixins applied
Cord.Component = function() {
	Component.apply(this, arguments);

	// Init observers
	this._observers = {};

	// Keys is true/false is a bound key is used in the render pipeline
	// GUIDs is mapping of vnode uids to GUIDs
	// Proxies maps GUIDs to a callback function
	this._boundKeys = {};
	this._boundGUIDs = {};
	this._boundGUIDProxies = {};

	// Apply any mixins
	var proto = Object.getPrototypeOf(this);
	if(proto.mixins && !proto._mixinsApplied) {
		var i, mixin, state = {};
		for(i = 0; i < proto.mixins.length; ++i) {
			mixin = Cord.mixins[proto.mixins[i]];
			if(!mixin._normalized)
				_normalizeMixin(mixin);
			extendObj(proto, mixProto(proto, mixin.methods));
			extendObj(state, mixin.state);
		}
		proto._mixinState = state;
		proto._mixinsApplied = true;
	}

	// Wrap render to allow bind() function to work without a context
	var __render = this.render;
	this.render = function() {
		var ret, prevComponent = _currentComponent;
		_currentComponent = this;
		try {
			ret = __render.apply(this, arguments);
		}
		finally {
			_currentComponent = prevComponent;
		}
		return ret;
	};

	var __componentWillMount = this.componentWillMount;
	this.componentWillMount = function() {
		var i, attr, args, value, prevComponent, guid, state = this.state;
		// Initialize computed state and any initial state from mixins
		if(this._mixinState)
			extendObj(state, this._mixinState);
		for(attr in state) {
			if(state.hasOwnProperty(attr)) {
				value = state[attr];
				if(typeof value === 'function' && value._computed) {
					args = value._args;
					prevComponent = _currentComponent;
					_currentComponent = this;
					try {
						for(i = 0; i < args.length; ++i) {
							// Setup as proxy setting the name of the (property @ arg number) as the guid for each bound argument
							guid = attr + '@' + i;
							this._boundGUIDProxies[guid] = value;
							bind(args[i], guid);
						}
						// Run once to set the initial state
						value.call(this, attr);
					}
					finally {
						_currentComponent = prevComponent;
					}
				}
			}
		}
		if(__componentWillMount)
			__componentWillMount.apply(this, arguments);
	};
};
Cord.Component.prototype = Object.create(Component.prototype);
Cord.Component.prototype.constructor = Cord.Component;

Cord.Component.prototype.setState = function(state, callback) {
	if(typeof state === 'function')
		state = state(this.state, this.props);
	for(var key in state) {
		if(state.hasOwnProperty(key) && this._hasObservers('this', key))
			this._invokeObservers('this', key, state[key]);
	}
	Component.prototype.setState.call(this, state, callback);
};

// Extend Component with data binding methods without backwards compatibility Event functions (bind/unbind)
extendObj(Cord.Component.prototype, Cord.Binding, Backbone.Events);
delete Cord.Component.prototype.bind;
delete Cord.Component.prototype.unbind;

// Cleanup code for all components
var __beforeUnmount = options.beforeUnmount;
options.beforeUnmount = function(component) {
	// Observer cleanup
	if(component.stopObserving)
		component.stopObserving();
	// BB event cleanup
	if(component.stopListening)
		component.stopListening();
	if(__beforeUnmount)
		__beforeUnmount.apply(this, arguments);
};

function _testBindingFeedback(el) {
	// Prevent two-way data binding from creating an infinite feedback loop through dispatching events
	if(!el.hasAttribute(_DATA_FEEDBACK_ATTR))
		return false;
	var bindingUid = el.getAttribute(_DATA_BINDING_ATTR);
	if(bindingUid && bindingUid === _currentBinding) {
		_currentBinding = null;
		return true;
	}
	else {
		_currentBinding = bindingUid;
		return false;
	}
}

function _bindingProxy(guid, key, value) {
	var el = document.body.querySelector('[' + _DATA_BINDING_ATTR + '="' + guid + '"]');
	if(!el || _testBindingFeedback(el))
		return;
	encodeValue(el, value);
}

function _createValueListener(key, wrapped) {
	return function(e) {
		var el = e.currentTarget;
		if(_testBindingFeedback(el))
			return;
		this.setValueForKey(key, decodeValue(el));
		if(wrapped)
			wrapped(e);
	}.bind(_currentComponent);
}

var __vnode = options.vnode;
options.vnode = function(vnode) {
	// vnode contains: nodeName, children (one item or array), attributes, key
	var i, j, child, strings, matches, spliceArgs;
	var children = Array.isArray(vnode.children) ? vnode.children : [vnode.children];
	var attrs = vnode.attributes || {};

	if(!attrs.raw) {
		for(i = children.length - 1; i >= 0; --i) {
			child = children[i];
			if(typeof child === 'string') {
				strings = child.split(regex.variableSearch);
				if(strings.length > 1) {
					spliceArgs = [i, 1];
					matches = child.match(regex.variableSearch);
					for(j = 0; j < matches.length; ++j) {
						if(strings[j].length)
							spliceArgs.push(strings[j]);
						// Force to a string because it is what preact expects
						spliceArgs.push(convertToString(bind(regex.variableValue.exec(matches[j])[1])));
					}
					if(strings[j].length)
						spliceArgs.push(strings[j]);
					Array.prototype.splice.apply(children, spliceArgs);
				}
			}
		}
		vnode.children = children;
	}

	// The attr bind is shorthand for both observe and change
	if(attrs.bind) {
		attrs.observe = attrs.bind;
		attrs.change = attrs.bind;
		delete attrs.bind;
	}

	if(attrs.observe || attrs.change || attrs.input) {
		var guid = _bindGUID(vnode.key || attrs.name, _bindingProxy);

		if(!guid) {
			delete attrs.observe;
			delete attrs.change;
			delete attrs.input;
		}
		else {
			attrs[_DATA_BINDING_ATTR] = guid;

			// Observer binding to set the value
			if(attrs.observe) {
				var value = bind(attrs.observe, guid);
				delete attrs.observe;
				// Set the initial value on a delayed callback
				if(guid)
					setImmediate(_bindingProxy.bind(null, guid, null, value));
				// If two-way this element is sensitive to binding feedback
				if(attrs.observe === attrs.change || attrs.observe === attrs.input)
					attrs[_DATA_FEEDBACK_ATTR] = true;
			}

			// Reverse binding on change or input events to listen to changes in the value
			if(attrs.change) {
				attrs.onChange = _createValueListener(attrs.change, attrs.onChange || attrs.onchange);
				delete attrs.change;
			}
			if(attrs.input) {
				attrs.onInput = _createValueListener(attrs.input, attrs.onInput || attrs.oninput);
				delete attrs.input;
			}
		}
	}

	if(__vnode)
		__vnode.apply(this, arguments);
};

// Export onto the react object
react.Cord = Cord;
if(!root.cordCompatibilityMode)
	react.Component = Component;

if(typeof exports === 'object')
	module.exports = Cord;

})(((typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global)));
<commit_msg>Fixed order of mixObj for Component mixins.<commit_after>;(function(root) {
'use strict';

var Backbone = root.Backbone || require('backbone');
var Cord = Backbone.Cord;
var extendObj = Cord.extendObj;
var isPlainObj = Cord.isPlainObj;
var mixObj = Cord.mixObj;
var ForceValue = Cord.ForceValue;

var convertToString = Cord.convertToString;
var encodeValue = Cord.encodeValue;
var decodeValue = Cord.decodeValue;
var randomGUID = Cord.randomGUID;
var setImmediate = Cord.setImmediate;
var regex = Cord.regex;

var react = root.preact || root.react || require('preact') || require('react');
var Component = react.Component;
var options = react.options;

var _currentComponent = null;
var _currentBinding = null;
var _DATA_BINDING_ATTR = 'data-binding-guid';
var _DATA_FEEDBACK_ATTR = 'data-binding-feedback';

function _bindGUID(uid, proxy) {
	var guid = null;
	if(uid) {
		guid = _currentComponent._boundGUIDs[uid] || (_currentComponent._boundGUIDs[uid] = randomGUID());
		_currentComponent._boundGUIDProxies[guid] = proxy;
	}
	return guid;
}

var bind = Cord.bind = function(key, guid) {
	if(!_currentComponent)
		return;
	var boundKey = guid || key;
	if(!_currentComponent._boundKeys[boundKey]) {
		_currentComponent.observe(key, function(key, value) {
			// Wrapped in a function to remove the key, value arguments from passing to forceUpdate
			if(guid && this._boundGUIDProxies[guid])
				this._boundGUIDProxies[guid].call(this, guid, key, value);
			else
				this.forceUpdate();
		});
		_currentComponent._boundKeys[boundKey] = true;
	}
	return _currentComponent.getValueForKey(key);
};

var computed = Cord.computed = function(func) {
	var args = Cord.getFunctionArgs(func);
	if(!args.length)
		return func;
	var compFunc = function(compKey) {
		var i, values = [], state = {};
		compKey = compKey.split('@')[0];
		for(i = 0; i < args.length; ++i)
			values.push(this.getValueForKey(args[i]));
		state[compKey] = func.apply(this, values);
		this.setState(state);
	};
	compFunc._args = args;
	compFunc._computed = true;
	return compFunc;
};

// Add the default scope and have it mirror this.state
Cord.scopes.this = {
	observe: function() {},
	unobserve: function() {},
	getValue: function(key) {
		return this.state[key];
	},
	setValue: function(key, value) {
		var change = {};
		// For compatibility for some older mixins with readonly BB properties
		if(typeof value === 'object' && Object.getPrototypeOf(value) === ForceValue.prototype)
			value = value.value;
		change[key] = value;
		this.setState(change);
	}
};

function _normalizeMixin(mixin) {
	var methods = {};
	var state = {};
	var attr, value;
	var lifecycleMap = {
		initialize: 'componentWillMount',
		remove: 'componentWillUnmount'
	};
	if(mixin.properties) {
		for(attr in mixin.properties) {
			if(mixin.properties.hasOwnProperty(attr)) {
				value = mixin.properties[attr];
				if(typeof value === 'function')
					state[attr] = computed(value);
				else if(isPlainObj(value))
					state[attr] = value.get ? computed(value.get) : value.value;
				else state[attr] = value;
			}
		}
	}
	for(attr in mixin) {
		if(mixin.hasOwnProperty(attr)) {
			value = mixin[attr];
			if(typeof value === 'function')
				methods[(lifecycleMap[attr] || attr)] = value;
		}
	}
	mixin.methods = methods;
	mixin.state = state;
	mixin._normalized = true;
}

// Inherit from react's Component so render can be wrapped and mixins applied
Cord.Component = function() {
	Component.apply(this, arguments);

	// Init observers
	this._observers = {};

	// Keys is true/false is a bound key is used in the render pipeline
	// GUIDs is mapping of vnode uids to GUIDs
	// Proxies maps GUIDs to a callback function
	this._boundKeys = {};
	this._boundGUIDs = {};
	this._boundGUIDProxies = {};

	// Apply any mixins
	var proto = Object.getPrototypeOf(this);
	if(proto.mixins && !proto._mixinsApplied) {
		var i, mixin, state = {};
		for(i = 0; i < proto.mixins.length; ++i) {
			mixin = Cord.mixins[proto.mixins[i]];
			if(!mixin._normalized)
				_normalizeMixin(mixin);
			extendObj(proto, mixObj(mixin.methods, proto));
			extendObj(state, mixin.state);
		}
		proto._mixinState = state;
		proto._mixinsApplied = true;
	}

	// Wrap render to allow bind() function to work without a context
	var __render = this.render;
	this.render = function() {
		var ret, prevComponent = _currentComponent;
		_currentComponent = this;
		try {
			ret = __render.apply(this, arguments);
		}
		finally {
			_currentComponent = prevComponent;
		}
		return ret;
	};

	var __componentWillMount = this.componentWillMount;
	this.componentWillMount = function() {
		var i, attr, args, value, prevComponent, guid, state = this.state;
		// Initialize computed state and any initial state from mixins
		if(this._mixinState)
			extendObj(state, this._mixinState);
		for(attr in state) {
			if(state.hasOwnProperty(attr)) {
				value = state[attr];
				if(typeof value === 'function' && value._computed) {
					args = value._args;
					prevComponent = _currentComponent;
					_currentComponent = this;
					try {
						for(i = 0; i < args.length; ++i) {
							// Setup as proxy setting the name of the (property @ arg number) as the guid for each bound argument
							guid = attr + '@' + i;
							this._boundGUIDProxies[guid] = value;
							bind(args[i], guid);
						}
						// Run once to set the initial state
						value.call(this, attr);
					}
					finally {
						_currentComponent = prevComponent;
					}
				}
			}
		}
		if(__componentWillMount)
			__componentWillMount.apply(this, arguments);
	};
};
Cord.Component.prototype = Object.create(Component.prototype);
Cord.Component.prototype.constructor = Cord.Component;

Cord.Component.prototype.setState = function(state, callback) {
	if(typeof state === 'function')
		state = state(this.state, this.props);
	for(var key in state) {
		if(state.hasOwnProperty(key) && this._hasObservers('this', key))
			this._invokeObservers('this', key, state[key]);
	}
	Component.prototype.setState.call(this, state, callback);
};

// Extend Component with data binding methods without backwards compatibility Event functions (bind/unbind)
extendObj(Cord.Component.prototype, Cord.Binding, Backbone.Events);
delete Cord.Component.prototype.bind;
delete Cord.Component.prototype.unbind;

// Cleanup code for all components
var __beforeUnmount = options.beforeUnmount;
options.beforeUnmount = function(component) {
	// Observer cleanup
	if(component.stopObserving)
		component.stopObserving();
	// BB event cleanup
	if(component.stopListening)
		component.stopListening();
	if(__beforeUnmount)
		__beforeUnmount.apply(this, arguments);
};

function _testBindingFeedback(el) {
	// Prevent two-way data binding from creating an infinite feedback loop through dispatching events
	if(!el.hasAttribute(_DATA_FEEDBACK_ATTR))
		return false;
	var bindingUid = el.getAttribute(_DATA_BINDING_ATTR);
	if(bindingUid && bindingUid === _currentBinding) {
		_currentBinding = null;
		return true;
	}
	else {
		_currentBinding = bindingUid;
		return false;
	}
}

function _bindingProxy(guid, key, value) {
	var el = document.body.querySelector('[' + _DATA_BINDING_ATTR + '="' + guid + '"]');
	if(!el || _testBindingFeedback(el))
		return;
	encodeValue(el, value);
}

function _createValueListener(key, wrapped) {
	return function(e) {
		var el = e.currentTarget;
		if(_testBindingFeedback(el))
			return;
		this.setValueForKey(key, decodeValue(el));
		if(wrapped)
			wrapped(e);
	}.bind(_currentComponent);
}

var __vnode = options.vnode;
options.vnode = function(vnode) {
	// vnode contains: nodeName, children (one item or array), attributes, key
	var i, j, child, strings, matches, spliceArgs;
	var children = Array.isArray(vnode.children) ? vnode.children : [vnode.children];
	var attrs = vnode.attributes || {};

	if(!attrs.raw) {
		for(i = children.length - 1; i >= 0; --i) {
			child = children[i];
			if(typeof child === 'string') {
				strings = child.split(regex.variableSearch);
				if(strings.length > 1) {
					spliceArgs = [i, 1];
					matches = child.match(regex.variableSearch);
					for(j = 0; j < matches.length; ++j) {
						if(strings[j].length)
							spliceArgs.push(strings[j]);
						// Force to a string because it is what preact expects
						spliceArgs.push(convertToString(bind(regex.variableValue.exec(matches[j])[1])));
					}
					if(strings[j].length)
						spliceArgs.push(strings[j]);
					Array.prototype.splice.apply(children, spliceArgs);
				}
			}
		}
		vnode.children = children;
	}

	// The attr bind is shorthand for both observe and change
	if(attrs.bind) {
		attrs.observe = attrs.bind;
		attrs.change = attrs.bind;
		delete attrs.bind;
	}

	if(attrs.observe || attrs.change || attrs.input) {
		var guid = _bindGUID(vnode.key || attrs.name, _bindingProxy);

		if(!guid) {
			delete attrs.observe;
			delete attrs.change;
			delete attrs.input;
		}
		else {
			attrs[_DATA_BINDING_ATTR] = guid;

			// Observer binding to set the value
			if(attrs.observe) {
				var value = bind(attrs.observe, guid);
				delete attrs.observe;
				// Set the initial value on a delayed callback
				if(guid)
					setImmediate(_bindingProxy.bind(null, guid, null, value));
				// If two-way this element is sensitive to binding feedback
				if(attrs.observe === attrs.change || attrs.observe === attrs.input)
					attrs[_DATA_FEEDBACK_ATTR] = true;
			}

			// Reverse binding on change or input events to listen to changes in the value
			if(attrs.change) {
				attrs.onChange = _createValueListener(attrs.change, attrs.onChange || attrs.onchange);
				delete attrs.change;
			}
			if(attrs.input) {
				attrs.onInput = _createValueListener(attrs.input, attrs.onInput || attrs.oninput);
				delete attrs.input;
			}
		}
	}

	if(__vnode)
		__vnode.apply(this, arguments);
};

// Export onto the react object
react.Cord = Cord;
if(!root.cordCompatibilityMode)
	react.Component = Component;

if(typeof exports === 'object')
	module.exports = Cord;

})(((typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global)));
<|endoftext|>


<commit_before>/*!
 * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014
 * @version 2.0.0
 * bootstrap-fileinput
 * For more JQuery Plugins visit http://plugins.krajee.com
 */!function(e){var i='{preview}\n
\n   {caption}\n
\n       {remove}\n       {upload}\n       {browse}\n
\n
',t="{preview}\n{remove}\n{upload}\n{browse}\n",n='
\n

\n
×
\n

\n

',a='
\n

\n
',l='
\n
\n
\n
\n        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>\n
Detailed Preview {title}
\n
\n
\n        <textarea class="form-control" style="font-family:Monaco,Consolas,monospace; height: {height}px;" readonly>{body}</textarea>\n
\n
\n
\n
\n',r='
\n   {content}\n
\n',o='
\n
\n       {strText}\n
\n
\n',s='
\n
\n

\n           {caption}\n
\n
',p=function(i,t){return null===i||void 0===i||i==[]||""===i||t&&""===e.trim(i)},c=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},d=function(i,t,n){return p(i)||p(i[t])?n:e(i[t])},v=function(e,i){return"undefined"!=typeof e?e.match("image.*"):i.match(/\.(gif|png|jpe?g)$/i)},m=function(e,i){return"undefined"!=typeof e?e.match("text.*"):i.match(/\.(txt|md|csv|htm|html|php|ini)$/i)},f=function(){return Math.round((new Date).getTime()+100*Math.random())},u=function(i,t){this.$element=e(i),this.init(t),this.listen()};u.prototype={constructor:u,init:function(e){var n=this;n.showCaption=e.showCaption,n.showPreview=e.showPreview,n.maxFileSize=e.maxFileSize,n.msgSizeTooLarge=e.msgSizeTooLarge,n.msgErrorClass=e.msgErrorClass,n.initialDelimiter=e.initialDelimiter,n.initialPreview=e.initialPreview,n.initialCaption=e.initialCaption,n.overwriteInitial=e.overwriteInitial,n.showRemove=e.showRemove,n.showUpload=e.showUpload,n.captionClass=e.captionClass,n.previewClass=e.previewClass,n.mainClass=e.mainClass,n.mainTemplate=p(e.mainTemplate)?n.showCaption?i:t:e.mainTemplate,n.previewTemplate=n.showPreview?e.previewTemplate:"",n.previewGenericTemplate=e.previewGenericTemplate,n.previewImageTemplate=e.previewImageTemplate,n.previewTextTemplate=e.previewTextTemplate,n.previewOtherTemplate=e.previewOtherTemplate,n.captionTemplate=e.captionTemplate,n.browseLabel=e.browseLabel,n.browseIcon=e.browseIcon,n.browseClass=e.browseClass,n.removeLabel=e.removeLabel,n.removeIcon=e.removeIcon,n.removeClass=e.removeClass,n.uploadLabel=e.uploadLabel,n.uploadIcon=e.uploadIcon,n.uploadClass=e.uploadClass,n.uploadUrl=e.uploadUrl,n.msgLoading=e.msgLoading,n.msgProgress=e.msgProgress,n.msgSelected=e.msgSelected,n.previewFileType=e.previewFileType,n.wrapTextLength=e.wrapTextLength,n.wrapIndicator=e.wrapIndicator,n.isDisabled=n.$element.attr("disabled")||n.$element.attr("readonly"),p(n.$element.attr("id"))&&n.$element.attr("id",f()),"undefined"==typeof n.$container?n.$container=n.createContainer():n.refreshContainer(),n.$captionContainer=d(e,"elCaptionContainer",n.$container.find(".file-caption")),n.$caption=d(e,"elCaptionText",n.$container.find(".file-caption-name")),n.$previewContainer=d(e,"elPreviewContainer",n.$container.find(".file-preview")),n.$preview=d(e,"elPreviewImage",n.$container.find(".file-preview-thumbnails")),n.$previewStatus=d(e,"elPreviewStatus",n.$container.find(".file-preview-status")),n.initPreview(),n.original={preview:n.$preview.html(),caption:n.$caption.html()},this.options=e,n.$element.removeClass("file-loading")},listen:function(){var i=this;i.$element.on("change",e.proxy(i.change,i)),e(i.$element[0].form).on("reset",e.proxy(i.reset,i)),i.$container.on("click",".fileinput-remove:not([disabled])",e.proxy(i.clear,i))},refresh:function(i){var t=this,n=arguments.length?e.extend(t.options,i):t.options;t.init(n)},initPreview:function(){var e=this,i="",t=e.initialPreview,n=c(t)?t.length:t.length>0?t.split(e.initialDelimiter).length:0,a=e.initialCaption.length,l="preview-"+f(),r=a>0?e.initialCaption:e.msgSelected.replace("{n}",n);if(c(t)&&n>0){for(var o=0;n>o;o++)l+="-"+o,i+=e.previewGenericTemplate.replace("{previewId}",l).replace("{content}",t[o]);n>1&&0==a&&(r=e.msgSelected.replace("{n}",n))}else{if(!(n>0))return a>0?(e.$caption.html(r),e.$captionContainer.attr("title",r),void e.$captionContainer.find(".kv-caption-icon").show()):void 0;for(var s=t.split(e.initialDelimiter),o=0;n>o;o++)l+="-"+o,i+=e.previewGenericTemplate.replace("{previewId}",l).replace("{content}",s[o]);n>1&&0==a&&(r=e.msgSelected.replace("{n}",n))}e.initialPreviewCount=n,e.initialPreviewContent=i,e.$preview.html(i),e.$caption.html(r),e.$captionContainer.attr("title",r),e.$container.removeClass("file-input-new")},clear:function(e){var i=this;if(e&&e.preventDefault(),i.$element.val(""),i.$previewContainer.find(".kv-fileinput-error").fadeOut("slow"),e!==!1&&(i.$element.trigger("change"),i.$element.trigger("fileclear")),i.overwriteInitial||p(i.initialPreviewContent)){i.$preview.html("");var t=!i.overwriteInitial&&i.initialCaption.length>0?i.original.caption:"";i.$caption.html(t),i.$captionContainer.attr("title",""),i.$container.removeClass("file-input-new").addClass("file-input-new")}else i.$preview.html(i.original.preview),i.$caption.html(i.original.caption),i.$container.removeClass("file-input-new");i.overwriteInitial&&i.$captionContainer.find(".kv-caption-icon").hide(),i.$element.trigger("filecleared")},reset:function(){var e=this;e.clear(!1),e.$preview.html(e.original.preview),e.$caption.html(e.original.caption),e.$container.find(".fileinput-filename").text(""),e.$element.trigger("filereset"),e.initialPreview.length>0&&e.$container.removeClass("file-input-new")},disable:function(){var e=this;e.isDisabled=!0,e.$element.attr("disabled","disabled"),e.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled"),e.$container.find(".btn-file, .fileinput-remove, .kv-fileinput-upload").attr("disabled",!0)},enable:function(){var e=this;e.isDisabled=!1,e.$element.removeAttr("disabled"),e.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled"),e.$container.find(".btn-file, .fileinput-remove, .kv-fileinput-upload").removeAttr("disabled")},change:function(e){var i,t=this,n=t.$element,a=n.get(0).files,r=a?a.length+t.initialPreviewCount:1,o=n.val().replace(/\\/g,"/").replace(/.*\//,""),s=t.$preview,p=t.$previewContainer,c=t.$previewStatus,d=t.msgLoading,u=t.msgProgress,w=t.msgSelected,h=t.previewFileType,g=parseInt(t.wrapTextLength),b=t.wrapIndicator,C="preview-"+f(),$=!1;if(i=void 0===e.target.files?e.target&&e.target.value?[{name:e.target.value.replace(/^.+\\/,"")}]:[]:e.target.files,0!==i.length){s.html(""),t.overwriteInitial||s.html(t.initialPreviewContent);for(var y=i.length,T=0;y>T;T++)!function(e){C+="-"+T;var i=e.name,a=v(e.type,e.name),r=m(e.type,e.name),o=(e.size?e.size:0)/1e3;if(o=o.toFixed(2),t.$previewContainer.find(".kv-fileinput-error").remove(),t.maxFileSize>0&&o>t.maxFileSize){var w=t.msgSizeTooLarge.replace("{name}",i).replace("{size}",o).replace("{maxSize}",t.maxFileSize),x='
'+w+"
";t.$element.val(""),t.$previewContainer.append(x);var I=t.$previewContainer.find(".kv-fileinput-error");return I.hide(),I.fadeIn(800),t.$element.trigger("fileerror",[e,C]),void($=!0)}if(s.length>0&&("any"==h?a||r:"text"==h?r:a)&&"undefined"!=typeof FileReader){var P=new FileReader;c.html(d),p.addClass("loading"),P.onload=function(a){var o="",d="";if(r){var v=a.target.result;if(v.length>g){var m=f(),u=.75*window.innerHeight,d=l.replace("{id}",m).replace("{title}",i).replace("{body}",v).replace("{height}",u);b=b.replace("{title}",i).replace("{dialog}","$('#"+m+"').modal('show')"),v=v.substring(0,g-1)+b}o=t.previewTextTemplate.replace("{previewId}",C).replace("{caption}",i).replace("{strText}",v)+d}else o=t.previewImageTemplate.replace("{previewId}",C).replace("{content}",'');s.append("\n"+o);var w=s.find("#"+C+" img");w.width()>s.width()&&(w.width("100%"),w.height("auto")),T>=y-1&&(p.removeClass("loading"),c.html("")),n.trigger("fileloaded",[e,C])},P.onprogress=function(i){if(i.lengthComputable){var t=parseInt(i.loaded/i.total*100,10),n=u.replace("{percent}",t).replace("{file}",e.name);c.html(n)}},r?P.readAsText(e):P.readAsDataURL(e)}else s.append("\n"+t.previewOtherTemplate.replace("{previewId}",C).replace("{caption}",i)),n.trigger("fileloaded",[e,C])}(i[T]);var x=r>1?w.replace("{n}",r):o;$&&(x=r>1?w.replace("{n}",r-1):" "),t.$caption.html(x),t.$captionContainer.attr("title",x),t.$container.removeClass("file-input-new"),n.trigger("fileselect",[r,o])}},createContainer:function(){var i=this,t=e(document.createElement("div")).attr({"class":"file-input file-input-new"}).html(i.renderMain());return i.$element.before(t),t.find(".btn-file").append(i.$element),t},refreshContainer:function(){var e=this;e.$container.before(e.$element),e.$container.html(e.renderMain()),e.$container.find(".btn-file").append(e.$element)},renderMain:function(){var e=this,i=e.previewTemplate.replace("{class}",e.previewClass),t=e.isDisabled?e.captionClass+" file-caption-disabled":e.captionClass,n=e.captionTemplate.replace("{class}",t+" kv-fileinput-caption");return e.mainTemplate.replace("{class}",e.mainClass).replace("{preview}",i).replace("{caption}",n).replace("{upload}",e.renderUpload()).replace("{remove}",e.renderRemove()).replace("{browse}",e.renderBrowse())},renderBrowse:function(){var e=this,i=e.browseClass+" btn-file",t="";return e.isDisabled&&(t=" disabled "),'
 "+e.browseIcon+e.browseLabel+"
"},renderRemove:function(){var e=this,i=e.removeClass+" fileinput-remove fileinput-remove-button",t="";return e.showRemove?(e.isDisabled&&(t=" disabled "),'<button type="button" class="'+i+'"'+t+">"+e.removeIcon+e.removeLabel+"</button>"):""},renderUpload:function(){var e=this,i=e.uploadClass+" kv-fileinput-upload",t="",n="";return e.showUpload?(e.isDisabled&&(n=" disabled "),t=p(e.uploadUrl)?'<button type="submit" class="'+i+'"'+n+">"+e.uploadIcon+e.uploadLabel+"</button>":'"+e.uploadIcon+e.uploadLabel+""):""}},e.fn.fileinput=function(i){return this.each(function(){var t=e(this),n=t.data("fileinput");n||t.data("fileinput",n=new u(this,i)),"string"==typeof i&&n[i]()})},e.fn.fileinput=function(i){var t=Array.apply(null,arguments);return t.shift(),this.each(function(){var n=e(this),a=n.data("fileinput"),l="object"==typeof i&&i;a||n.data("fileinput",a=new u(this,e.extend({},e.fn.fileinput.defaults,l,e(this).data()))),"string"==typeof i&&a[i].apply(a,t)})},e.fn.fileinput.defaults={showCaption:!0,showPreview:!0,showRemove:!0,showUpload:!0,captionClass:"",previewClass:"",mainClass:"",mainTemplate:null,initialDelimiter:"*$$*",initialPreview:"",initialCaption:"",initialPreviewCount:0,initialPreviewContent:"",overwriteInitial:!0,previewTemplate:n,previewGenericTemplate:r,previewImageTemplate:r,previewTextTemplate:o,previewOtherTemplate:s,captionTemplate:a,browseLabel:"Browse …",browseIcon:'  ',browseClass:"btn btn-primary",removeLabel:"Remove",removeIcon:' ',removeClass:"btn btn-default",uploadLabel:"Upload",uploadIcon:' ',uploadClass:"btn btn-default",uploadUrl:null,maxFileSize:0,msgSizeTooLarge:'File "{name}" ({size} KB) exceeds maximum allowed upload size of {maxSize} KB. Please retry your upload!',msgErrorClass:"file-error-message",msgLoading:"Loading …",msgProgress:"Loaded {percent}% of {file}",msgSelected:"{n} files selected",previewFileType:"image",wrapTextLength:250,wrapIndicator:' […]',elCaptionContainer:null,elCaptionText:null,elPreviewContainer:null,elPreviewImage:null,elPreviewStatus:null},e(document).ready(function(){var i=e("input.file[type=file]"),t=Object.keys(i).length;t>0&&i.fileinput()})}(window.jQuery);<commit_msg>Delete fileinput.min.js<commit_after><|endoftext|>


<commit_before>/*globals casper, __utils__, url */

CasperTest.begin("Settings screen is correct", 18, function suite(test) {
    casper.thenOpen(url + "ghost/settings/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.then(function testViews() {
        test.assertExists(".wrapper", "Settings main view is present");
        test.assertExists(".settings-sidebar", "Settings sidebar view is present");
        test.assertExists(".settings-menu", "Settings menu is present");
        test.assertExists(".settings-menu .general", "General tab is present");
        test.assertExists(".settings-menu .users", "Users tab is present");
        test.assertExists(".settings-menu .apps", "Apps is present");
        test.assertExists(".wrapper", "Settings main view is present");
        test.assertExists(".settings-content", "Settings content view is present");
        test.assertEval(function testGeneralIsActive() {
            return document.querySelector('.settings-menu .general').classList.contains('active');
        }, "general tab is marked active");
        test.assertEval(function testContentIsGeneral() {
            return document.querySelector('.settings-content').id === 'general';
        }, "loaded content is general screen");
    });

    // test the user tab
    casper.thenClick('.settings-menu .users');
    casper.waitForSelector('#user', function then() {
        test.assertEval(function testGeneralIsNotActive() {
            return !document.querySelector('.settings-menu .general').classList.contains('active');
        }, "general tab is not marked active");
        test.assertEval(function testUserIsActive() {
            return document.querySelector('.settings-menu .users').classList.contains('active');
        }, "user tab is marked active");
        test.assertEval(function testContentIsUser() {
            return document.querySelector('.settings-content').id === 'user';
        }, "loaded content is user screen");
    }, function onTimeOut() {
        test.fail('User screen failed to load');
    });

    function handleUserRequest(requestData, request) {
        // make sure we only get requests from the user pane
        if (requestData.url.indexOf('settings/') !== -1) {
            test.fail("Saving the user pane triggered another settings pane to save");
        }
    }

    function handleSettingsRequest(requestData, request) {
        // make sure we only get requests from the user pane
        if (requestData.url.indexOf('users/') !== -1) {
            test.fail("Saving a settings pane triggered the user pane to save");
        }
    }

    casper.then(function listenForRequests() {
        casper.on('resource.requested', handleUserRequest);
    });

    casper.thenClick('#user .button-save');
    casper.waitFor(function successNotification() {
        return this.evaluate(function () {
            return document.querySelectorAll('.js-bb-notification section').length > 0;
        });
    }, function doneWaiting() {

    }, function waitTimeout() {
        test.fail("Saving the user pane did not result in a notification");
    });

    casper.then(function checkUserWasSaved() {
        casper.removeListener('resource.requested', handleUserRequest);
    });

    casper.waitForSelector('.notification-success', function onSuccess() {
        test.assert(true, 'Got success notification');
    }, function onTimeout() {
        test.assert(false, 'No success notification :(');
    });

    casper.thenClick('#main-menu .settings a').then(function testOpeningSettingsTwice() {
        casper.on('resource.requested', handleSettingsRequest);
        test.assertEval(function testUserIsActive() {
            return document.querySelector('.settings-menu .general').classList.contains('active');
        }, "general tab is marked active");

    });

    casper.thenClick('#general .button-save').waitFor(function successNotification() {
        return this.evaluate(function () {
            return document.querySelectorAll('.js-bb-notification section').length > 0;
        });
    }, function doneWaiting() {

    }, function waitTimeout() {
        test.fail("Saving the general pane did not result in a notification");
    });

    casper.then(function checkSettingsWereSaved() {
        casper.removeListener('resource.requested', handleSettingsRequest);
    });

    casper.waitForSelector('.notification-success', function onSuccess() {
        test.assert(true, 'Got success notification');
    }, function onTimeout() {
        test.assert(false, 'No success notification :(');
    });

    CasperTest.beforeDone(function () {
        casper.removeListener('resource.requested', handleUserRequest);
        casper.removeListener('resource.requested', handleSettingsRequest);
    });
});

CasperTest.begin('Ensure general blog title field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[title]': new Array(152).join('a')
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'too long');
    }, function onTimeout() {
        test.fail('Blog title length error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure general blog description field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fillSelectors("form#settings-general", {
            '#blog-description': new Array(202).join('a')
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'too long');
    }, function onTimeout() {
        test.fail('Blog description length error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure postsPerPage number field form validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[postsPerPage]': 'notaninteger'
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a number');
    }, function onTimeout() {
        test.fail('postsPerPage error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure postsPerPage max of 1000', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[postsPerPage]': '1001'
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a number less than 1000');
    }, function onTimeout() {
        test.fail('postsPerPage max error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure postsPerPage min of 0', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[postsPerPage]': '-1'
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a number greater than 0');
    }, function onTimeout() {
        test.fail('postsPerPage min error did not appear');
    }, 2000);
});

CasperTest.begin("User settings screen validates email", 6, function suite(test) {
    var email, brokenEmail;

    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.then(function setEmailToInvalid() {
        email = casper.getElementInfo('#user-email').attributes.value;
        brokenEmail = email.replace('.', '-');

        casper.fillSelectors('.user-profile', {
            '#user-email': brokenEmail
        }, false);
    });

    casper.thenClick('#user .button-save');

    casper.waitForResource('/users/');

    casper.waitForSelector('.notification-error', function onSuccess() {
        test.assert(true, 'Got error notification');
        test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
    }, function onTimeout() {
        test.assert(false, 'No error notification :(');
    });

    casper.then(function resetEmailToValid() {
        casper.fillSelectors('.user-profile', {
            '#user-email': email
        }, false);
    });

    casper.thenClick('#user .button-save');

    casper.waitForResource(/users/);

    casper.waitForSelector('.notification-success', function onSuccess() {
        test.assert(true, 'Got success notification');
        test.assertSelectorDoesntHaveText('.notification-success', '[object Object]');
    }, function onTimeout() {
        test.assert(false, 'No success notification :(');
    });
});

CasperTest.begin("User settings screen shows remaining characters for Bio properly", 4, function suite(test) {

    function getRemainingBioCharacterCount() {
        return casper.getHTML('.word-count');
    }

    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.then(function checkCharacterCount() {
        test.assert(getRemainingBioCharacterCount() === '200', 'Bio remaining characters is 200');
    });

    casper.then(function setBioToValid() {
        casper.fillSelectors('.user-profile', {
                '#user-bio': 'asdf\n' // 5 characters
            }, false);
    });

    casper.then(function checkCharacterCount() {
        test.assert(getRemainingBioCharacterCount() === '195', 'Bio remaining characters is 195');
    });
});

CasperTest.begin('Ensure user bio field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#user', function then() {
        this.fillSelectors("form.user-profile", {
            '#user-bio': new Array(202).join('a')
        });
    });

    casper.thenClick('#user .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'is too long');
    }, function onTimeout() {
        test.fail('Bio field length error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure user url field validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#user', function then() {
        this.fillSelectors("form.user-profile", {
            '#user-website': 'notaurl'
        });
    });

    casper.thenClick('#user .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a valid url');
    }, function onTimeout() {
        test.fail('Url validation error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure user location field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#user', function then() {
        this.fillSelectors("form.user-profile", {
            '#user-location': new Array(1002).join('a')
        });
    });

    casper.thenClick('#user .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'is too long');
    }, function onTimeout() {
        test.fail('Location field length error did not appear');
    }, 2000);
});
<commit_msg>Added functional test for uploading image/cover in settings<commit_after>/*globals casper, __utils__, url */

CasperTest.begin("Settings screen is correct", 18, function suite(test) {
    casper.thenOpen(url + "ghost/settings/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.then(function testViews() {
        test.assertExists(".wrapper", "Settings main view is present");
        test.assertExists(".settings-sidebar", "Settings sidebar view is present");
        test.assertExists(".settings-menu", "Settings menu is present");
        test.assertExists(".settings-menu .general", "General tab is present");
        test.assertExists(".settings-menu .users", "Users tab is present");
        test.assertExists(".settings-menu .apps", "Apps is present");
        test.assertExists(".wrapper", "Settings main view is present");
        test.assertExists(".settings-content", "Settings content view is present");
        test.assertEval(function testGeneralIsActive() {
            return document.querySelector('.settings-menu .general').classList.contains('active');
        }, "general tab is marked active");
        test.assertEval(function testContentIsGeneral() {
            return document.querySelector('.settings-content').id === 'general';
        }, "loaded content is general screen");
    });

    // test the user tab
    casper.thenClick('.settings-menu .users');
    casper.waitForSelector('#user', function then() {
        test.assertEval(function testGeneralIsNotActive() {
            return !document.querySelector('.settings-menu .general').classList.contains('active');
        }, "general tab is not marked active");
        test.assertEval(function testUserIsActive() {
            return document.querySelector('.settings-menu .users').classList.contains('active');
        }, "user tab is marked active");
        test.assertEval(function testContentIsUser() {
            return document.querySelector('.settings-content').id === 'user';
        }, "loaded content is user screen");
    }, function onTimeOut() {
        test.fail('User screen failed to load');
    });

    function handleUserRequest(requestData, request) {
        // make sure we only get requests from the user pane
        if (requestData.url.indexOf('settings/') !== -1) {
            test.fail("Saving the user pane triggered another settings pane to save");
        }
    }

    function handleSettingsRequest(requestData, request) {
        // make sure we only get requests from the user pane
        if (requestData.url.indexOf('users/') !== -1) {
            test.fail("Saving a settings pane triggered the user pane to save");
        }
    }

    casper.then(function listenForRequests() {
        casper.on('resource.requested', handleUserRequest);
    });

    casper.thenClick('#user .button-save');
    casper.waitFor(function successNotification() {
        return this.evaluate(function () {
            return document.querySelectorAll('.js-bb-notification section').length > 0;
        });
    }, function doneWaiting() {

    }, function waitTimeout() {
        test.fail("Saving the user pane did not result in a notification");
    });

    casper.then(function checkUserWasSaved() {
        casper.removeListener('resource.requested', handleUserRequest);
    });

    casper.waitForSelector('.notification-success', function onSuccess() {
        test.assert(true, 'Got success notification');
    }, function onTimeout() {
        test.assert(false, 'No success notification :(');
    });

    casper.thenClick('#main-menu .settings a').then(function testOpeningSettingsTwice() {
        casper.on('resource.requested', handleSettingsRequest);
        test.assertEval(function testUserIsActive() {
            return document.querySelector('.settings-menu .general').classList.contains('active');
        }, "general tab is marked active");

    });

    casper.thenClick('#general .button-save').waitFor(function successNotification() {
        return this.evaluate(function () {
            return document.querySelectorAll('.js-bb-notification section').length > 0;
        });
    }, function doneWaiting() {

    }, function waitTimeout() {
        test.fail("Saving the general pane did not result in a notification");
    });

    casper.then(function checkSettingsWereSaved() {
        casper.removeListener('resource.requested', handleSettingsRequest);
    });

    casper.waitForSelector('.notification-success', function onSuccess() {
        test.assert(true, 'Got success notification');
    }, function onTimeout() {
        test.assert(false, 'No success notification :(');
    });

    CasperTest.beforeDone(function () {
        casper.removeListener('resource.requested', handleUserRequest);
        casper.removeListener('resource.requested', handleSettingsRequest);
    });
});

CasperTest.begin('Ensure general blog title field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[title]': new Array(152).join('a')
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'too long');
    }, function onTimeout() {
        test.fail('Blog title length error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure general blog description field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fillSelectors("form#settings-general", {
            '#blog-description': new Array(202).join('a')
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'too long');
    }, function onTimeout() {
        test.fail('Blog description length error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure image upload modals display correctly', 6, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    function assertImageUploaderModalThenClose() {
        test.assertExists('.js-drop-zone.image-uploader', 'Image drop zone modal renders correctly');
        this.click('#modal-container .js-button-accept');
        casper.waitForSelector('.notification-success', function onSuccess() {
            test.assert(true, 'Got success notification');
        }, function onTimeout() {
            test.fail('No success notification');
        }, 1000);
    };

    // Test Blog Logo Upload Button
    casper.waitForSelector('#general', function then() {
        this.click('#general .js-modal-logo');
    });

    casper.waitForSelector('#modal-container .modal-content', assertImageUploaderModalThenClose,
        function onTimeout() {
            test.fail('No upload logo modal container appeared');
        }, 1000);

    // Test Blog Cover Upload Button
    casper.then(function() {
        this.click('#general .js-modal-cover');
    });

    casper.waitForSelector('#modal-container .modal-content', assertImageUploaderModalThenClose,
        function onTimeout() {
            test.fail('No upload cover modal container appeared');
        }, 1000);
});

CasperTest.begin("User settings screen validates email", 6, function suite(test) {
    var email, brokenEmail;

    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.then(function setEmailToInvalid() {
        email = casper.getElementInfo('#user-email').attributes.value;
        brokenEmail = email.replace('.', '-');

        casper.fillSelectors('.user-profile', {
            '#user-email': brokenEmail
        }, false);
    });

    casper.thenClick('#user .button-save');

    casper.waitForResource('/users/');

    casper.waitForSelector('.notification-error', function onSuccess() {
        test.assert(true, 'Got error notification');
        test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
    }, function onTimeout() {
        test.assert(false, 'No error notification :(');
    });

    casper.then(function resetEmailToValid() {
        casper.fillSelectors('.user-profile', {
            '#user-email': email
        }, false);
    });

    casper.thenClick('#user .button-save');

    casper.waitForResource(/users/);

    casper.waitForSelector('.notification-success', function onSuccess() {
        test.assert(true, 'Got success notification');
        test.assertSelectorDoesntHaveText('.notification-success', '[object Object]');
    }, function onTimeout() {
        test.assert(false, 'No success notification :(');
    });
});

CasperTest.begin('Ensure postsPerPage number field form validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[postsPerPage]': 'notaninteger'
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a number');
    }, function onTimeout() {
        test.fail('postsPerPage error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure postsPerPage max of 1000', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[postsPerPage]': '1001'
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a number less than 1000');
    }, function onTimeout() {
        test.fail('postsPerPage max error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure postsPerPage min of 0', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#general', function then() {
        this.fill("form#settings-general", {
            'general[postsPerPage]': '-1'
        });
    });

    casper.thenClick('#general .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a number greater than 0');
    }, function onTimeout() {
        test.fail('postsPerPage min error did not appear');
    }, 2000);
});

CasperTest.begin("User settings screen shows remaining characters for Bio properly", 4, function suite(test) {

    function getRemainingBioCharacterCount() {
        return casper.getHTML('.word-count');
    }

    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.then(function checkCharacterCount() {
        test.assert(getRemainingBioCharacterCount() === '200', 'Bio remaining characters is 200');
    });

    casper.then(function setBioToValid() {
        casper.fillSelectors('.user-profile', {
                '#user-bio': 'asdf\n' // 5 characters
            }, false);
    });

    casper.then(function checkCharacterCount() {
        test.assert(getRemainingBioCharacterCount() === '195', 'Bio remaining characters is 195');
    });
});

CasperTest.begin('Ensure user bio field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#user', function then() {
        this.fillSelectors("form.user-profile", {
            '#user-bio': new Array(202).join('a')
        });
    });

    casper.thenClick('#user .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'is too long');
    }, function onTimeout() {
        test.fail('Bio field length error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure user url field validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#user', function then() {
        this.fillSelectors("form.user-profile", {
            '#user-website': 'notaurl'
        });
    });

    casper.thenClick('#user .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'use a valid url');
    }, function onTimeout() {
        test.fail('Url validation error did not appear');
    }, 2000);
});

CasperTest.begin('Ensure user location field length validation', 3, function suite(test) {
    casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
        test.assertTitle("Ghost Admin", "Ghost admin has no title");
        test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
    });

    casper.waitForSelector('#user', function then() {
        this.fillSelectors("form.user-profile", {
            '#user-location': new Array(1002).join('a')
        });
    });

    casper.thenClick('#user .button-save');

    casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
        test.assertSelectorHasText('.notification-error', 'is too long');
    }, function onTimeout() {
        test.fail('Location field length error did not appear');
    }, 2000);
});
<|endoftext|>