<commit_before>/**
 * @module transformation
 * @desc Transformation framework.
 * @author Louis-Dominique Dubeau
 */

define(/** @lends module:transformation */function (require, exports, module) {
"use strict";

var $ = require("jquery");
var util = require("./util");
var sense_refs = require("./refman").sense_refs;
var domutil = require("./domutil");
var Action = require("./action").Action;
var oop = require("./oop");

/**
 * The following is true of all transformations stored in this
 * registry. Insert, and wrap handlers get an undefined node because
 * they must query the editor for caret and selection information.
 *
 *

 *
 *
type	node is	element_name is the name of the

 *
 *
insert	undefined	element to
 * insert

 *
 *
delete-element	element to delete	element to delete

 *
 *
delete-parent	element to delete	element to delete

 *
 *
wrap	undefined	wrapping element

 *
 *
merge-with-next	element to merge
 * element to merge

 *
 *
merge-with-previous	element to
 * merge	element to merge

 *
 *
append	element after which to
 * append	element after which to append

 *
 *
prepend	element before which to
 * prepend	element before which to append

 *
 *
unwrap	node to unwrap	node to unwrap

 *
 *

 *
 * @class
 */
function TransformationRegistry() {
    this._tag_tr = {
        "insert": {},
        "delete-element": {},
        "delete-parent": {},
        "merge-with-next": {},
        "merge-with-previous": {},
        "wrap": {},
        "append": {},
        "prepend": {},
        "unwrap": {}
    };
}

/**
 * @param {String} tag Can be a tag name or "*" to match all tags.
 *
 */
TransformationRegistry.prototype.addTagTransformations = function(type, tag,
                                                                  tr) {
    if (!(tr instanceof Array))
        tr = [tr];

    this._tag_tr[type][tag] = tr;
};

TransformationRegistry.prototype.getTagTransformations = function (type, tag) {
    if (!(type instanceof Array))
        type = [type];

    var ret = [];
    for(var ix = 0; ix < type.length; ix++) {
        var val = this._tag_tr[type[ix]][tag];
        // Can't concat undefined
        if (val !== undefined)
            ret = ret.concat(val);

        val = this._tag_tr[type[ix]]["*"];
        if (val !== undefined)
            ret = ret.concat(val);
    }
    return ret;
};

/**
 * @class
 * @param {module:wed~Editor} editor The editor for which this
 * transformation must be created.
 * @param {String} desc The description of this transformation. A
 * transformation's {@link
 * module:transformation~Transformation#getDescriptionFor
 * getDescriptionFor} method will replace "<element_name>" with the
 * name of the element actually being processed. So a string like
 * "Remove &ltelement_name>" would become "Remove foo" when the
 * transformation is called for the element "foo".
 * @param {String} [abbreviated_desc] An abbreviated description of this
 * transformation.
 * @param {String} [icon] An HTML representation of the icon
 * associated with this transformation.
 * @param {Function} handler The handler to call when this
 * transformation is executed.
 */
function Transformation(editor, desc, abbreviated_desc, icon, handler) {
    switch(arguments.length) {
    case 3:
        handler = abbreviated_desc;
        abbreviated_desc = icon = undefined;
        break;
    case 4:
        handler = icon;
        icon = undefined;
        break;
    }
    Action.call(this, editor, desc, abbreviated_desc, icon);
    this.handler = handler;
}

oop.inherit(Transformation, Action);

Transformation.prototype.getDescriptionFor = function (data) {
    return this._desc.replace(/<element_name>/, data.element_name);
};

Transformation.prototype.execute = function (data) {
    data = data || {};
    this._editor.fireTransformation(this, data.node,
                                    data.element_name, data);
};

function insertElement(data_updater, parent, index, tag_name, attrs,
                       contents) {
    var $new = makeElement(
        tag_name,
        attrs,
        contents !== undefined
    );

    if (contents !== undefined)
        $new.append(contents);

    data_updater.insertAt(parent, index, $new.get(0));
    return $new;
}

function makeElement(name, attrs) {
    var $e = $("
");
    if (attrs !== undefined)
    {
        // Create attributes
        var keys = Object.keys(attrs);
        for(var keys_ix = 0, key; (key = keys[keys_ix++]) !== undefined; ) {
            $e.attr(util.encodeAttrName(key), attrs[key]);
        }
    }
    return $e;
}

function wrapTextInElement (data_updater, node, offset, end_offset,
                            element, attrs) {
    var original_text = node.nodeValue;
    var text_to_wrap = node.nodeValue.slice(offset, end_offset);

    data_updater.deleteText(node, offset, text_to_wrap.length);
    // Insert the new element
    var $new_element = makeElement(element, attrs);
    data_updater.insertAt(node, offset, $new_element.get(0));

    return $new_element;
}

function unwrap(data_updater, node) {
    var parent = node.parentNode;
    var children = Array.prototype.slice.call(node.childNodes);
    var prev = node.previousSibling;
    data_updater.insertBefore(parent, node.childNodes,
                              prev || parent.firstChild);
    data_updater.removeNode(node);
    return children;
}

/**
 * This function splits a node at the position of the caret. If the
 * caret is not inside the node or its descendants, an exception is
 * raised.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The node to split.
 */
function splitNode(editor, node) {
    var caret = editor.getDataCaret();

    if ($(caret[0]).closest(node).length === 0)
        throw new Error("caret outside node");

    var pair = editor.data_updater.splitAt(node, caret);
    // Find the deepest location at the start of the 2nd
    // element.
    editor.setDataCaret(domutil.firstDescendantOrSelf(pair[1]), 0);
}

/**
 * This function merges an element with a previous element of the same
 * name. For the operation to go forward, the element must have a
 * previous sibling and this sibling must have the same name as the
 * element being merged.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The element to merge with previous.
 */
function mergeWithPreviousHomogeneousSibling (editor, node) {
    var $node = $(node);
    var $prev = $node.prev();
    var name = util.getOriginalName(node);
    if ($prev.is(util.classFromOriginalName(name))) {
        // We need to record these to set the caret to a good position.
        var caret_pos = $prev.get(0).childNodes.length;
        var was_text = $prev.get(0).lastChild.nodeType === Node.TEXT_NODE;
        var text_len = (was_text) ?
                $prev.get(0).lastChild.nodeValue.length : 0;

        var prev = $prev.get(0);
        var insertion_point = prev.childNodes.length;
        // Reverse order
        for (var i = node.childNodes.length; i >= 0; --i)
            editor.data_updater.insertAt(prev, insertion_point,
                                         node.childNodes[i]);

        if (was_text)
            editor.setDataCaret($prev.get(0).childNodes[caret_pos - 1],
                                text_len);
        else
            editor.setDataCaret($prev.get(0), caret_pos);
        editor.data_updater.removeNode(node);
    }
}

/**
 * This function merges an element with a next element of the same
 * name. For the operation to go forward, the element must have a
 * next sibling and this sibling must have the same name as the
 * element being merged.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The element to merge with next.
 */
function mergeWithNextHomogeneousSibling(editor, node) {
    var $node = $(node);
    var $next = $node.next();
    var name = util.getOriginalName(node);
    if ($next.is(util.classFromOriginalName(name)))
        mergeWithPreviousHomogeneousSibling(editor, $next.get(0));
}

function _moveDataCaretFirst(editor, data_caret, tr, ev) {
    editor.setDataCaret(data_caret);
    tr.bound_handler(ev);
}

/**
 * This is a convenience function which returns a function that,
 * upon execution, will first move the data caret and then execute the
 * transformation passed as a parameter.
 *
 * @param {module:wed~Editor} editor The editor to which this
 * transformation applies.
 * @param {Array} data_caret The caret to which to move.
 * @param {module:transformation~Transformation} tr The transformation
 * to execute.
 * @returns {Function} The function to use.
 */
function moveDataCaretFirst(editor, data_caret, tr) {
    return _moveDataCaretFirst.bind(undefined, editor, data_caret, tr);
}

exports.TransformationRegistry = TransformationRegistry;
exports.Transformation = Transformation;
exports.wrapTextInElement = wrapTextInElement;
exports.insertElement = insertElement;
exports.makeElement = makeElement;
exports.unwrap = unwrap;
exports.splitNode = splitNode;
exports.mergeWithPreviousHomogeneousSibling =
        mergeWithPreviousHomogeneousSibling;
exports.mergeWithNextHomogeneousSibling =
        mergeWithNextHomogeneousSibling;
exports.moveDataCaretFirst = moveDataCaretFirst;
});
<commit_msg>Added functions to swap elements.<commit_after>/**
 * @module transformation
 * @desc Transformation framework.
 * @author Louis-Dominique Dubeau
 */

define(/** @lends module:transformation */function (require, exports, module) {
"use strict";

var $ = require("jquery");
var util = require("./util");
var sense_refs = require("./refman").sense_refs;
var domutil = require("./domutil");
var Action = require("./action").Action;
var oop = require("./oop");

/**
 * The following is true of all transformations stored in this
 * registry. Insert, and wrap handlers get an undefined node because
 * they must query the editor for caret and selection information.
 *
 *

 *
 *
type	node is	element_name is the name of the

 *
 *
insert	undefined	element to
 * insert

 *
 *
delete-element	element to delete	element to delete

 *
 *
delete-parent	element to delete	element to delete

 *
 *
wrap	undefined	wrapping element

 *
 *
merge-with-next	element to merge
 * element to merge

 *
 *
merge-with-previous	element to
 * merge	element to merge

 *
 *
swap-with-next	element to swap
 * element to swap

 *
 *
swap-with-previous	element to
 * swap	element to swap

 *
 *
append	element after which to
 * append	element after which to append

 *
 *
prepend	element before which to
 * prepend	element before which to append

 *
 *
unwrap	node to unwrap	node to unwrap

 *
 *

 *
 * @class
 */
function TransformationRegistry() {
    this._tag_tr = {
        "insert": {},
        "delete-element": {},
        "delete-parent": {},
        "merge-with-next": {},
        "merge-with-previous": {},
        "swap-with-next": {},
        "swap-with-previous": {},
        "wrap": {},
        "append": {},
        "prepend": {},
        "unwrap": {}
    };
}

/**
 * @param {String} tag Can be a tag name or "*" to match all tags.
 *
 */
TransformationRegistry.prototype.addTagTransformations = function(type, tag,
                                                                  tr) {
    if (!(tr instanceof Array))
        tr = [tr];

    this._tag_tr[type][tag] = tr;
};

TransformationRegistry.prototype.getTagTransformations = function (type, tag) {
    if (!(type instanceof Array))
        type = [type];

    var ret = [];
    for(var ix = 0; ix < type.length; ix++) {
        var val = this._tag_tr[type[ix]][tag];
        // Can't concat undefined
        if (val !== undefined)
            ret = ret.concat(val);

        val = this._tag_tr[type[ix]]["*"];
        if (val !== undefined)
            ret = ret.concat(val);
    }
    return ret;
};

/**
 * @class
 * @param {module:wed~Editor} editor The editor for which this
 * transformation must be created.
 * @param {String} desc The description of this transformation. A
 * transformation's {@link
 * module:transformation~Transformation#getDescriptionFor
 * getDescriptionFor} method will replace "<element_name>" with the
 * name of the element actually being processed. So a string like
 * "Remove &ltelement_name>" would become "Remove foo" when the
 * transformation is called for the element "foo".
 * @param {String} [abbreviated_desc] An abbreviated description of this
 * transformation.
 * @param {String} [icon] An HTML representation of the icon
 * associated with this transformation.
 * @param {Function} handler The handler to call when this
 * transformation is executed.
 */
function Transformation(editor, desc, abbreviated_desc, icon, handler) {
    switch(arguments.length) {
    case 3:
        handler = abbreviated_desc;
        abbreviated_desc = icon = undefined;
        break;
    case 4:
        handler = icon;
        icon = undefined;
        break;
    }
    Action.call(this, editor, desc, abbreviated_desc, icon);
    this.handler = handler;
}

oop.inherit(Transformation, Action);

Transformation.prototype.getDescriptionFor = function (data) {
    return this._desc.replace(/<element_name>/, data.element_name);
};

Transformation.prototype.execute = function (data) {
    data = data || {};
    this._editor.fireTransformation(this, data.node,
                                    data.element_name, data);
};

function insertElement(data_updater, parent, index, tag_name, attrs,
                       contents) {
    var $new = makeElement(
        tag_name,
        attrs,
        contents !== undefined
    );

    if (contents !== undefined)
        $new.append(contents);

    data_updater.insertAt(parent, index, $new.get(0));
    return $new;
}

function makeElement(name, attrs) {
    var $e = $("
");
    if (attrs !== undefined)
    {
        // Create attributes
        var keys = Object.keys(attrs);
        for(var keys_ix = 0, key; (key = keys[keys_ix++]) !== undefined; ) {
            $e.attr(util.encodeAttrName(key), attrs[key]);
        }
    }
    return $e;
}

function wrapTextInElement (data_updater, node, offset, end_offset,
                            element, attrs) {
    var original_text = node.nodeValue;
    var text_to_wrap = node.nodeValue.slice(offset, end_offset);

    data_updater.deleteText(node, offset, text_to_wrap.length);
    // Insert the new element
    var $new_element = makeElement(element, attrs);
    data_updater.insertAt(node, offset, $new_element.get(0));

    return $new_element;
}

function unwrap(data_updater, node) {
    var parent = node.parentNode;
    var children = Array.prototype.slice.call(node.childNodes);
    var prev = node.previousSibling;
    data_updater.insertBefore(parent, node.childNodes,
                              prev || parent.firstChild);
    data_updater.removeNode(node);
    return children;
}

/**
 * This function splits a node at the position of the caret. If the
 * caret is not inside the node or its descendants, an exception is
 * raised.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The node to split.
 */
function splitNode(editor, node) {
    var caret = editor.getDataCaret();

    if ($(caret[0]).closest(node).length === 0)
        throw new Error("caret outside node");

    var pair = editor.data_updater.splitAt(node, caret);
    // Find the deepest location at the start of the 2nd
    // element.
    editor.setDataCaret(domutil.firstDescendantOrSelf(pair[1]), 0);
}

/**
 * This function merges an element with a previous element of the same
 * name. For the operation to go forward, the element must have a
 * previous sibling and this sibling must have the same name as the
 * element being merged.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The element to merge with previous.
 */
function mergeWithPreviousHomogeneousSibling (editor, node) {
    var $node = $(node);
    var $prev = $node.prev();
    var name = util.getOriginalName(node);
    if ($prev.is(util.classFromOriginalName(name))) {
        // We need to record these to set the caret to a good position.
        var caret_pos = $prev.get(0).childNodes.length;
        var was_text = $prev.get(0).lastChild.nodeType === Node.TEXT_NODE;
        var text_len = (was_text) ?
                $prev.get(0).lastChild.nodeValue.length : 0;

        var prev = $prev.get(0);
        var insertion_point = prev.childNodes.length;
        // Reverse order
        for (var i = node.childNodes.length; i >= 0; --i)
            editor.data_updater.insertAt(prev, insertion_point,
                                         node.childNodes[i]);

        if (was_text)
            editor.setDataCaret($prev.get(0).childNodes[caret_pos - 1],
                                text_len);
        else
            editor.setDataCaret($prev.get(0), caret_pos);
        editor.data_updater.removeNode(node);
    }
}

/**
 * This function merges an element with a next element of the same
 * name. For the operation to go forward, the element must have a
 * next sibling and this sibling must have the same name as the
 * element being merged.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The element to merge with next.
 */
function mergeWithNextHomogeneousSibling(editor, node) {
    var $node = $(node);
    var $next = $node.next();
    var name = util.getOriginalName(node);
    if ($next.is(util.classFromOriginalName(name)))
        mergeWithPreviousHomogeneousSibling(editor, $next.get(0));
}

/**
 * This function swaps an element with a previous element of the same
 * name. For the operation to go forward, the element must have a
 * previous sibling and this sibling must have the same name as the
 * element being merged.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The element to swap with previous.
 */
function swapWithPreviousHomogeneousSibling (editor, node) {
    var prev = node.previousSibling;
    var name = util.getOriginalName(node);
    if ($(prev).is(util.classFromOriginalName(name))) {
        var parent = prev.parentNode;
        editor.data_updater.removeNode(node);
        editor.data_updater.insertBefore(parent, node, prev);
        editor.setDataCaret(parent, Array.prototype.indexOf.call(
            parent.childNodes, node));
    }
}

/**
 * This function swaps an element with a next element of the same
 * name. For the operation to go forward, the element must have a next
 * sibling and this sibling must have the same name as the element
 * being merged.
 *
 * @param {module:wed~Editor} editor The editor on which we are to
 * perform the transformation.
 * @param {Node} node The element to swap with next.
 */
function swapWithNextHomogeneousSibling(editor, node) {
    var next = node.nextSibling;
    if (next)
        swapWithPreviousHomogeneousSibling(editor, next);
}

function _moveDataCaretFirst(editor, data_caret, tr, ev) {
    editor.setDataCaret(data_caret);
    tr.bound_handler(ev);
}

/**
 * This is a convenience function which returns a function that,
 * upon execution, will first move the data caret and then execute the
 * transformation passed as a parameter.
 *
 * @param {module:wed~Editor} editor The editor to which this
 * transformation applies.
 * @param {Array} data_caret The caret to which to move.
 * @param {module:transformation~Transformation} tr The transformation
 * to execute.
 * @returns {Function} The function to use.
 */
function moveDataCaretFirst(editor, data_caret, tr) {
    return _moveDataCaretFirst.bind(undefined, editor, data_caret, tr);
}

exports.TransformationRegistry = TransformationRegistry;
exports.Transformation = Transformation;
exports.wrapTextInElement = wrapTextInElement;
exports.insertElement = insertElement;
exports.makeElement = makeElement;
exports.unwrap = unwrap;
exports.splitNode = splitNode;
exports.mergeWithPreviousHomogeneousSibling =
        mergeWithPreviousHomogeneousSibling;
exports.mergeWithNextHomogeneousSibling =
        mergeWithNextHomogeneousSibling;
exports.swapWithPreviousHomogeneousSibling =
        swapWithPreviousHomogeneousSibling;
exports.swapWithNextHomogeneousSibling =
        swapWithNextHomogeneousSibling;
exports.moveDataCaretFirst = moveDataCaretFirst;
});
<|endoftext|>


<commit_before>/*
 * winston-papertrail.js:
 *
 *          Transport for logging to Papertrail Service
 *          www.papertrailapp.com
 *
 * (C) 2013 Ken Perkins
 * MIT LICENCE
 *
 */

var os = require('os'),
    net = require('net'),
    tls = require('tls'),
    syslogProducer = require('glossy').Produce,
    util = require('util'),
    winston = require('winston');

/**
 * Papertrail class
 *
 * @description constructor for the Papertrail transport
 *
 * @param {object}      options                 options for your papertrail transport
 *
 * @param {string}      options.host            host for papertrail endpoint
 *
 * @param {Number}      options.port            port for papertrail endpoint
 *
 * @param {Boolean}     [options.disableTls]    disable TLS connections, enabled by default
 *
 * @param {string}      [options.hostname]      name for the logging hostname in Papertrail
 *
 * @param {string}      [options.program]       name for the logging program
 *
 * @param {string}      [options.facility]      syslog facility for log messages
 *
 * @param {string}      [options.level]         log level for your transport (info)
 *
 * @param {Function}    [options.logFormat]     function to format your log message before sending
 *
 * @param {Number}      [options.attemptsBeforeDecay]       how many reconnections should
 *                                                          be attempted before backing of (5)
 *
 * @param {Number}      [options.maximumAttempts]           maximum attempts before
 *                                                          disabling buffering (25)
 *
 * @param {Number}      [options.connectionDelay]           delay between
 *                                                          reconnection attempts in ms (1000)
 *
 * @param {Boolean}     [options.handleExceptions]          passed to base Transport (false)
 *
 * @param {Boolean}     [options.colorize]                  enable colors in Papertrail (false)
 *
 * @param {Number}      [options.maxDelayBetweenReconnection]   when backing off,
 *                                                              what's the max time between
 *                                                              reconnections (ms)
 *
 * @param {Boolean}     [options.inlineMeta]        inline multi-line messages (false)
 *
 * @type {Function}
 */
var Papertrail = exports.Papertrail = function (options) {

    var self = this;

    self._KEEPALIVE_INTERVAL = 15 * 1000;

    options = options || {};

    self.name = 'Papertrail';
    self.level = options.level || 'info';

    // Papertrail Service Host
    self.host = options.host;

    // Papertrail Service Port
    self.port = options.port;

    // Disable TLS connections (enabled by default)
    self.disableTls = typeof options.disableTls === 'boolean' ? options.disableTls : false;

    // Hostname of the current app
    self.hostname = options.hostname || os.hostname();

    // Program is an affordance for Papertrail to name the source of log entries
    self.program = options.program || 'default';

    // Syslog facility to log messages as to Papertrail
    self.facility = options.facility || 'daemon';

    // Send ANSI color codes through to Papertrail
    self.colorize = options.colorize || false;

    // Format your log messages prior to delivery
    self.logFormat = options.logFormat || function (level, message) {
        return level + ' ' + message;
    };

    // Number of attempts before decaying reconnection
    self.attemptsBeforeDecay = options.attemptsBeforeDecay || 5;

    // Maximum number of reconnection attempts before disabling buffer
    self.maximumAttempts = options.maximumAttempts || 25;

    // Delay between normal attempts
    self.connectionDelay = options.connectionDelay || 1000;

    // Handle Exceptions
    self.handleExceptions = options.handleExceptions || false;

    // Maximum delay between attempts
    self.maxDelayBetweenReconnection =
        options.maxDelayBetweenReconnection || 60000;

    // Maximum buffer size (default: 1MB)
    self.maxBufferSize =
        options.maxBufferSize || 1 * 1024 * 1024;

    // Inline meta flag
    self.inlineMeta = options.inlineMeta || false;

    self.producer = new syslogProducer({ facility: self.facility });

    self.currentRetries = 0;
    self.totalRetries = 0;
    self.buffer = '';
    self.loggingEnabled = true;
    self._shutdown = false;

    // Error out if we don't have a host or port
    if (!self.host || !self.port) {
        throw new Error('Missing required parameters: host and port');
    }

    // Open the connection
    connectStream();

    // Opens a connection to Papertrail
    function connectStream() {
        // don't connect on either error or shutdown
        if (self._shutdown || self._erroring) {
            return;
        }

        try {

            function wireStreams() {
                self.stream.on('error', onErrored);

                // If we have the stream end, simply reconnect
                self.stream.on('end', connectStream);
            }

            if (self.disableTls) {
                self.stream = net.createConnection(self.port, self.host, onConnected);
                self.stream.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                wireStreams();
            }
            else {
                var socket = net.createConnection(self.port, self.host, function () {
                    socket.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                    self.stream = tls.connect({
                        socket: socket,
                        rejectUnauthorized: false
                    }, onConnected);

                    wireStreams();
                });

                socket.on('error', onErrored);
            }
        }
        catch (e) {
            onErrored(e);
        }
    }

    function onErrored(err) {
        // make sure we prevent simultaneous attempts to connect and handle errors
        self._erroring = true;

        self.emit('error', err);

        // We may be disconnected from the papertrail endpoint for any number of reasons;
        // i.e. inactivity, network problems, etc, and we need to be resilient against this
        // that said, we back off reconnection attempts in case Papertrail is truly down
        setTimeout(function () {
            // Increment our retry counts
            self.currentRetries++;
            self.totalRetries++;

            // Decay the retry rate exponentially up to max between attempts
            if ((self.connectionDelay < self.maxDelayBetweenReconnection) &&
            (self.currentRetries >= self.attemptsBeforeDecay)) {
                self.connectionDelay = self.connectionDelay * 2;
                self.currentRetries = 0;
            }

            // Stop buffering messages after a fixed number of retries.
            // This is to keep the buffer from growing unbounded
            if (self.loggingEnabled &&
                (self.totalRetries >= (self.maximumAttempts))) {
                    self.loggingEnabled = false;
                    self.emit('error', new Error('Max entries eclipsed, disabling buffering'));
            }

            // continue
            self._erroring = false;
            connectStream();

        }, self.connectionDelay);
    }

    function onConnected() {
        // Reset our variables
        self.loggingEnabled = true;
        self.currentRetries = 0;
        self.totalRetries = 0;
        self.connectionDelay = options.connectionDelay || 1000;

        self.emit('connect', 'Connected to Papertrail at ' + self.host + ':' + self.port);

        // Did we get messages buffered
        if (self.buffer.length > 0) {
            self.stream.write(self.buffer);
            self.buffer = '';
        }
    }
};

//
//
// Inherit from `winston.Transport` so you can take advantage
// of the base functionality and `.handleExceptions()`.
//
util.inherits(Papertrail, winston.Transport);

//
// Define a getter so that `winston.transports.Papertrail`
// is available and thus backwards compatible.
//
winston.transports.Papertrail = Papertrail;

/**
 * Papertrail.log
 *
 * @description Core logging method exposed to Winston. Metadata is optional.
 *
 * @param {String}        level    Level at which to log the message.
 * @param {String}        msg        Message to log
 * @param {String|object|Function}        [meta]    Optional metadata to attach
 * @param {Function}    callback
 * @returns {*}
 */
Papertrail.prototype.log = function (level, msg, meta, callback) {

    var self = this;

    // make sure we handle when meta isn't provided
    if (typeof(meta) === 'function' && !callback) {
        callback = meta;
        meta = false;
    }

    if  (meta && typeof meta === 'object' && Object.keys(meta).length === 0) {
        meta = false;
    }

    // If the logging buffer is disabled, drop the message on the floor
    if (!this.loggingEnabled) {
        return callback(null, true);
    }

    var output = msg;

    // If we don't have a string for the message,
    // lets transform it before moving on
    if (typeof(output) !== 'string') {
        output = util.inspect(output, false, null, self.colorize);
    }

    if (meta) {
        if (typeof meta !== 'object') {
            output += ' ' + meta;
        }
        else if (meta) {
            if (this.inlineMeta) {
                output += ' ' + util.inspect(meta, false, null, self.colorize).replace(/[\n\t]\s*/gm, " ");
            }
            else {
                output += '\n' + util.inspect(meta, false, null, self.colorize);
            }
        }
    }

    this.sendMessage(this.hostname, this.program, level, output);

    callback(null, true);
};

/**
 * Papertrail.sendMessage
 *
 * @description sending the message to the stream, or buffering if not connected
 *
 * @param {String}    hostname    Hostname of the source application.
 * @param {String}    program     Name of the source application
 * @param {String}    level        Log level of the message
 * @param {String}    message        The message to deliver
 */
Papertrail.prototype.sendMessage = function (hostname, program, level, message) {

    var self = this,
        lines = [],
        msg = '',
        gap = '';

    // Only split if we actually have a message
    if (message) {
        lines = message.split('\n');
    }
    else {
        lines = [''];
    }

    // If the incoming message has multiple lines, break them and format each
    // line as it's own message
    for (var i = 0; i < lines.length; i++) {

        // don't send extra message if our message ends with a newline
        if ((lines[i].length === 0) && (i == lines.length - 1)) {
            break;
        }

        if (i == 1) {
            gap = '    ';
        }

        msg += self.producer.produce({
            severity: level,
            host: hostname,
            appName: program,
            date: new Date(),
            message: self.logFormat(self.colorize ? winston.config.colorize(level) : level, gap + lines[i])
        }) + '\r\n';
    }

    if (this.stream && this.stream.writable) {
        this.stream.write(msg);
    }
    else if (this.loggingEnabled && this.buffer.length < this.maxBufferSize) {
        this.buffer += msg;
    }
};

/**
 * Papertrail.close
 *
 * @description closes the underlying TLS connection and disables automatic
 * reconnection, allowing the process to exit
 */
Papertrail.prototype.close = function() {
    var self = this;

    self._shutdown = true;

    if (self.stream) {
        self.stream.end();
    }
    // if there's no stream yet, that means we're still connecting
    // lets wire a connect handler, and then invoke close again
    else {
        self.on('connect', function() {
            self.close();
        });
    }
};
<commit_msg>Call winston.Transport constructor to initialize parent fields<commit_after>/*
 * winston-papertrail.js:
 *
 *          Transport for logging to Papertrail Service
 *          www.papertrailapp.com
 *
 * (C) 2013 Ken Perkins
 * MIT LICENCE
 *
 */

var os = require('os'),
    net = require('net'),
    tls = require('tls'),
    syslogProducer = require('glossy').Produce,
    util = require('util'),
    winston = require('winston');

/**
 * Papertrail class
 *
 * @description constructor for the Papertrail transport
 *
 * @param {object}      options                 options for your papertrail transport
 *
 * @param {string}      options.host            host for papertrail endpoint
 *
 * @param {Number}      options.port            port for papertrail endpoint
 *
 * @param {Boolean}     [options.disableTls]    disable TLS connections, enabled by default
 *
 * @param {string}      [options.hostname]      name for the logging hostname in Papertrail
 *
 * @param {string}      [options.program]       name for the logging program
 *
 * @param {string}      [options.facility]      syslog facility for log messages
 *
 * @param {string}      [options.level]         log level for your transport (info)
 *
 * @param {Function}    [options.logFormat]     function to format your log message before sending
 *
 * @param {Number}      [options.attemptsBeforeDecay]       how many reconnections should
 *                                                          be attempted before backing of (5)
 *
 * @param {Number}      [options.maximumAttempts]           maximum attempts before
 *                                                          disabling buffering (25)
 *
 * @param {Number}      [options.connectionDelay]           delay between
 *                                                          reconnection attempts in ms (1000)
 *
 * @param {Boolean}     [options.handleExceptions]          passed to base Transport (false)
 *
 * @param {Boolean}     [options.colorize]                  enable colors in Papertrail (false)
 *
 * @param {Number}      [options.maxDelayBetweenReconnection]   when backing off,
 *                                                              what's the max time between
 *                                                              reconnections (ms)
 *
 * @param {Boolean}     [options.inlineMeta]        inline multi-line messages (false)
 *
 * @type {Function}
 */
var Papertrail = exports.Papertrail = function (options) {

    var self = this;
    winston.Transport.call(self, options);

    self._KEEPALIVE_INTERVAL = 15 * 1000;

    options = options || {};

    self.name = 'Papertrail';
    self.level = options.level || 'info';

    // Papertrail Service Host
    self.host = options.host;

    // Papertrail Service Port
    self.port = options.port;

    // Disable TLS connections (enabled by default)
    self.disableTls = typeof options.disableTls === 'boolean' ? options.disableTls : false;

    // Hostname of the current app
    self.hostname = options.hostname || os.hostname();

    // Program is an affordance for Papertrail to name the source of log entries
    self.program = options.program || 'default';

    // Syslog facility to log messages as to Papertrail
    self.facility = options.facility || 'daemon';

    // Send ANSI color codes through to Papertrail
    self.colorize = options.colorize || false;

    // Format your log messages prior to delivery
    self.logFormat = options.logFormat || function (level, message) {
        return level + ' ' + message;
    };

    // Number of attempts before decaying reconnection
    self.attemptsBeforeDecay = options.attemptsBeforeDecay || 5;

    // Maximum number of reconnection attempts before disabling buffer
    self.maximumAttempts = options.maximumAttempts || 25;

    // Delay between normal attempts
    self.connectionDelay = options.connectionDelay || 1000;

    // Handle Exceptions
    self.handleExceptions = options.handleExceptions || false;

    // Maximum delay between attempts
    self.maxDelayBetweenReconnection =
        options.maxDelayBetweenReconnection || 60000;

    // Maximum buffer size (default: 1MB)
    self.maxBufferSize =
        options.maxBufferSize || 1 * 1024 * 1024;

    // Inline meta flag
    self.inlineMeta = options.inlineMeta || false;

    self.producer = new syslogProducer({ facility: self.facility });

    self.currentRetries = 0;
    self.totalRetries = 0;
    self.buffer = '';
    self.loggingEnabled = true;
    self._shutdown = false;

    // Error out if we don't have a host or port
    if (!self.host || !self.port) {
        throw new Error('Missing required parameters: host and port');
    }

    // Open the connection
    connectStream();

    // Opens a connection to Papertrail
    function connectStream() {
        // don't connect on either error or shutdown
        if (self._shutdown || self._erroring) {
            return;
        }

        try {

            function wireStreams() {
                self.stream.on('error', onErrored);

                // If we have the stream end, simply reconnect
                self.stream.on('end', connectStream);
            }

            if (self.disableTls) {
                self.stream = net.createConnection(self.port, self.host, onConnected);
                self.stream.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                wireStreams();
            }
            else {
                var socket = net.createConnection(self.port, self.host, function () {
                    socket.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                    self.stream = tls.connect({
                        socket: socket,
                        rejectUnauthorized: false
                    }, onConnected);

                    wireStreams();
                });

                socket.on('error', onErrored);
            }
        }
        catch (e) {
            onErrored(e);
        }
    }

    function onErrored(err) {
        // make sure we prevent simultaneous attempts to connect and handle errors
        self._erroring = true;

        self.emit('error', err);

        // We may be disconnected from the papertrail endpoint for any number of reasons;
        // i.e. inactivity, network problems, etc, and we need to be resilient against this
        // that said, we back off reconnection attempts in case Papertrail is truly down
        setTimeout(function () {
            // Increment our retry counts
            self.currentRetries++;
            self.totalRetries++;

            // Decay the retry rate exponentially up to max between attempts
            if ((self.connectionDelay < self.maxDelayBetweenReconnection) &&
            (self.currentRetries >= self.attemptsBeforeDecay)) {
                self.connectionDelay = self.connectionDelay * 2;
                self.currentRetries = 0;
            }

            // Stop buffering messages after a fixed number of retries.
            // This is to keep the buffer from growing unbounded
            if (self.loggingEnabled &&
                (self.totalRetries >= (self.maximumAttempts))) {
                    self.loggingEnabled = false;
                    self.emit('error', new Error('Max entries eclipsed, disabling buffering'));
            }

            // continue
            self._erroring = false;
            connectStream();

        }, self.connectionDelay);
    }

    function onConnected() {
        // Reset our variables
        self.loggingEnabled = true;
        self.currentRetries = 0;
        self.totalRetries = 0;
        self.connectionDelay = options.connectionDelay || 1000;

        self.emit('connect', 'Connected to Papertrail at ' + self.host + ':' + self.port);

        // Did we get messages buffered
        if (self.buffer.length > 0) {
            self.stream.write(self.buffer);
            self.buffer = '';
        }
    }
};

//
//
// Inherit from `winston.Transport` so you can take advantage
// of the base functionality and `.handleExceptions()`.
//
util.inherits(Papertrail, winston.Transport);

//
// Define a getter so that `winston.transports.Papertrail`
// is available and thus backwards compatible.
//
winston.transports.Papertrail = Papertrail;

/**
 * Papertrail.log
 *
 * @description Core logging method exposed to Winston. Metadata is optional.
 *
 * @param {String}        level    Level at which to log the message.
 * @param {String}        msg        Message to log
 * @param {String|object|Function}        [meta]    Optional metadata to attach
 * @param {Function}    callback
 * @returns {*}
 */
Papertrail.prototype.log = function (level, msg, meta, callback) {

    var self = this;

    // make sure we handle when meta isn't provided
    if (typeof(meta) === 'function' && !callback) {
        callback = meta;
        meta = false;
    }

    if  (meta && typeof meta === 'object' && Object.keys(meta).length === 0) {
        meta = false;
    }

    // If the logging buffer is disabled, drop the message on the floor
    if (!this.loggingEnabled) {
        return callback(null, true);
    }

    var output = msg;

    // If we don't have a string for the message,
    // lets transform it before moving on
    if (typeof(output) !== 'string') {
        output = util.inspect(output, false, null, self.colorize);
    }

    if (meta) {
        if (typeof meta !== 'object') {
            output += ' ' + meta;
        }
        else if (meta) {
            if (this.inlineMeta) {
                output += ' ' + util.inspect(meta, false, null, self.colorize).replace(/[\n\t]\s*/gm, " ");
            }
            else {
                output += '\n' + util.inspect(meta, false, null, self.colorize);
            }
        }
    }

    this.sendMessage(this.hostname, this.program, level, output);

    callback(null, true);
};

/**
 * Papertrail.sendMessage
 *
 * @description sending the message to the stream, or buffering if not connected
 *
 * @param {String}    hostname    Hostname of the source application.
 * @param {String}    program     Name of the source application
 * @param {String}    level        Log level of the message
 * @param {String}    message        The message to deliver
 */
Papertrail.prototype.sendMessage = function (hostname, program, level, message) {

    var self = this,
        lines = [],
        msg = '',
        gap = '';

    // Only split if we actually have a message
    if (message) {
        lines = message.split('\n');
    }
    else {
        lines = [''];
    }

    // If the incoming message has multiple lines, break them and format each
    // line as it's own message
    for (var i = 0; i < lines.length; i++) {

        // don't send extra message if our message ends with a newline
        if ((lines[i].length === 0) && (i == lines.length - 1)) {
            break;
        }

        if (i == 1) {
            gap = '    ';
        }

        msg += self.producer.produce({
            severity: level,
            host: hostname,
            appName: program,
            date: new Date(),
            message: self.logFormat(self.colorize ? winston.config.colorize(level) : level, gap + lines[i])
        }) + '\r\n';
    }

    if (this.stream && this.stream.writable) {
        this.stream.write(msg);
    }
    else if (this.loggingEnabled && this.buffer.length < this.maxBufferSize) {
        this.buffer += msg;
    }
};

/**
 * Papertrail.close
 *
 * @description closes the underlying TLS connection and disables automatic
 * reconnection, allowing the process to exit
 */
Papertrail.prototype.close = function() {
    var self = this;

    self._shutdown = true;

    if (self.stream) {
        self.stream.end();
    }
    // if there's no stream yet, that means we're still connecting
    // lets wire a connect handler, and then invoke close again
    else {
        self.on('connect', function() {
            self.close();
        });
    }
};
<|endoftext|>


<commit_before>'use strict';

(function(window) {
    /******************** private variables ********************/
    var _isDeviceMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
        _dataAttributes = {
            accountBlockTrigger: 'ptn-account-block-trigger',
            dropdownTrigger: 'ptn-dropdown-trigger'
        },
        _selectors = {
            pageLoader: '#page-loader',
            header: '#header',
            aside: '#aside',
            main: '#main',
            asideTrigger: '#aside-trigger',
            listItem: '.ptn-list__item',
            listItemActive: '.ptn-list__item_active',
            listLink: '.ptn-list__link',
            sublist: '.ptn-list_sublist',
            sublistTrigger: '.ptn-list__item_sublist > .ptn-list__link',
            dropdown: '.ptn-dropdown',
            dropdownTrigger: '.ptn-dropdown__trigger',
            dropdownTriggerGeneral: '[' + _dataAttributes.dropdownTrigger + ']',
            accountBlockTrigger: '[' + _dataAttributes.accountBlockTrigger + ']',
            accountBlock: '.ptn-account-block',
            accountBlockSubmit: '.ptn-account-block__submit',
            inputBlock: '.ptn-input-block',
            inputBlockInputText: '.ptn-input-block__input-text'
        },
        // Disabled because localStorage doesn't work in private browsing.
        // _localStorageKeys = {
        //     statusAside: 'ptn-status-aside'
        // },
        _classes = {
            triggerOn: 'ptn-util__trigger_on',
            widgetOn: 'ptn-util__widget_on',
            htmlMobileDevice: 'ptn-html_mobile-device',
            bodyAsideOn: 'ptn-body_aside-on'
        },
        _actions = {
            show: 'show',
            hide: 'hide'
        },
        _statuses = {
            on: 'on',
            off: 'off'
        },
        _animationDurations = {
            slide: 200,
            click: 500,
            fade: 500
        },
        _delay = {
            pageLoader: 500
        },
        _$window = $(window),
        _$document = $(document),
        _$html = $('html'),
        _$body = $('body');

    /******************** mobile device class ********************/
    if (_isDeviceMobile) {
        _$html.addClass(_classes.htmlMobileDevice);
    }

    /******************** event: DOM ready ********************/
    $(function() {
        /******************** disable pinch-to-zoom for iOS safari ********************/
        _$document.on('gesturestart', function(e) {
            e.preventDefault();
        });

        /******************** widget: waves ********************/
        (function() {
            Waves.attach(_selectors.listLink, ['waves-light']);
            Waves.attach(_selectors.accountBlockSubmit);

            Waves.init({
                duration: _animationDurations.click
            });
        })();

        /******************** widget: scrollbar ********************/
        (function() {
            _addScrollBar($(_selectors.aside), 'minimal-dark', 'y');
        })();

        /******************** widget: aside | toggle ********************/
        // Disabled because localStorage doesn't work in private browsing.
        // (function() {
        //     var asideStatus = localStorage.getItem(_localStorageKeys.statusAside);
        //
        //     if (asideStatus === _statuses.on) {
        //         _toggleAsideStatelessly(_actions.show);
        //     }
        //
        //     _$body.on('click touchend', _selectors.asideTrigger, function(e) {
        //         e.preventDefault();
        //
        //         _toggleAside();
        //     });
        // })();

        (function() {
            _$body.on('click touchend', _selectors.asideTrigger, function(e) {
                e.preventDefault();

                _toggleAside();
            });
        })();

        /******************** widget: sublist | init ********************/
        (function() {
            _resetSublists();
        })();

        /******************** widget: sublist | toggle ********************/
        (function() {
            _$body.on('click touchend', _selectors.sublistTrigger, function(e) {
                e.preventDefault();

                _toggleSublist($(this));
            });

            function _toggleSublist($trigger) {
                var isSublistOn = $trigger.hasClass(_classes.triggerOn),
                    $listItemClosest = $trigger.closest(_selectors.listItem);

                if (isSublistOn) {
                    $listItemClosest.find(_selectors.sublist).slideUp(_animationDurations.slide);
                    $listItemClosest.find(_selectors.listLink).removeClass(_classes.triggerOn);
                } else {
                    $trigger.next(_selectors.sublist).slideDown(_animationDurations.slide);
                    $trigger.addClass(_classes.triggerOn);
                }
            }
        })();

        /******************** widget: dropdown | toggle ********************/
        (function() {
            _$body.on('click touchend', _selectors.dropdownTrigger, function(e) {
                e.preventDefault();

                var $dropdownTrigger = $(this),
                    $dropdown = $dropdownTrigger.closest(_selectors.dropdown);

                $dropdownTrigger.toggleClass(_classes.triggerOn);
                $dropdown.toggleClass(_classes.widgetOn);
            });

            _$body.on('click touchend', _selectors.dropdownTriggerGeneral, function(e) {
                e.preventDefault();
                e.stopPropagation();

                var dropdownId = $(this).attr(_dataAttributes.dropdownTrigger),
                    $dropdown = $('#' + dropdownId),
                    $dropdownTrigger = $dropdown.find(_selectors.dropdownTrigger);

                $dropdownTrigger.addClass(_classes.triggerOn);
                $dropdown.addClass(_classes.widgetOn);
            });

            _$body.on('click touchend', function(e) {
                var $target = $(e.target),
                    isTargetDropdown = $target.closest(_selectors.dropdown).length > 0;

                if (isTargetDropdown) {
                    return;
                }

                $(_selectors.dropdownTrigger).removeClass(_classes.triggerOn);
                $(_selectors.dropdown).removeClass(_classes.widgetOn);
            });
        })();

        /******************** widget: account block | switch ********************/
        (function() {
            _switchAccountBlock('account-block-sign-up');
            _switchAccountBlock('account-block-update');

            _$body.on('click touchend', _selectors.accountBlockTrigger, function(e) {
                e.preventDefault();

                var accountBlockId = $(this).attr(_dataAttributes.accountBlockTrigger);

                _switchAccountBlock(accountBlockId);
            });
        })();

        /******************** widget: input block | focus ********************/
        (function() {
            _$body.on('focus', _selectors.inputBlockInputText, function(e) {
                e.preventDefault();

                $(this).closest(_selectors.inputBlock).addClass(_classes.widgetOn);
            }).on('blur', _selectors.inputBlockInputText, function(e) {
                e.preventDefault();

                $(this).closest(_selectors.inputBlock).removeClass(_classes.widgetOn);
            });
        })();
    });

    /******************** event: page load ********************/
    _$window.on('load', function() {
        /******************** widget: page loader ********************/
        var $pageLoader = $(_selectors.pageLoader);

        if (_isElementExistent($pageLoader)) {
            window.setTimeout(function() {
                $pageLoader.fadeOut(_animationDurations.fade);
                $(_selectors.header).fadeIn(_animationDurations.fade);
                $(_selectors.aside).fadeIn(_animationDurations.fade);
                $(_selectors.main).fadeIn(_animationDurations.fade);
            }, _delay.pageLoader);
        }
    });

    /******************** private functions ********************/
    function _isElementExistent($element) {
        return !!$element.length;
    }

    function _addScrollBar($element, theme, mouseWheelAxis) {
        if (!_isElementExistent($element)) {
            return;
        }

        $element.mCustomScrollbar({
            theme: theme,
            scrollInertia: 100,
            axis: 'y',
            mouseWheel: {
                preventDefault: true,
                enable: true,
                axis: mouseWheelAxis
            }
        });
    }

    function _scrollTo($element, position) {
        $element.mCustomScrollbar('scrollTo', position, {
            scrollInertia: 0
        });
    }

    // Disabled because localStorage doesn't work in private browsing.
    // function _toggleAside() {
    //     var statusOld = localStorage.getItem(_localStorageKeys.statusAside),
    //         statusNew = statusOld === _statuses.on ? _statuses.off : _statuses.on,
    //         $asideTrigger = $(_selectors.asideTrigger),
    //         $aside = $(_selectors.aside);
    //
    //     localStorage.setItem(_localStorageKeys.statusAside, statusNew);
    //
    //     _$body.toggleClass(_classes.bodyAsideOn);
    //     $asideTrigger.toggleClass(_classes.triggerOn);
    //     $aside.toggleClass(_classes.widgetOn);
    //
    //     statusNew === _statuses.on && _resetSublists();
    //     _scrollTo($aside, 'top');
    // }

    function _toggleAside() {
        var $asideTrigger = $(_selectors.asideTrigger),
            $aside = $(_selectors.aside);

        _$body.toggleClass(_classes.bodyAsideOn);
        $asideTrigger.toggleClass(_classes.triggerOn);
        $aside.toggleClass(_classes.widgetOn);

        _$body.hasClass(_classes.bodyAsideOn) && _resetSublists();
        _scrollTo($aside, 'top');
    }

    function _toggleAsideStatelessly(action) {
        var $asideTrigger = $(_selectors.asideTrigger),
            $aside = $(_selectors.aside);

        if (action === _actions.show) {
            _$body.addClass(_classes.bodyAsideOn);
            $asideTrigger.addClass(_classes.triggerOn);
            $aside.addClass(_classes.widgetOn);
        } else if (action === _actions.hide) {
            _$body.removeClass(_classes.bodyAsideOn);
            $asideTrigger.removeClass(_classes.triggerOn);
            $aside.removeClass(_classes.widgetOn);
        }

        _resetSublists();
        _scrollTo($aside, 'top');
    }

    function _resetSublists() {
        var $activeListItemAncestors = $(_selectors.listItemActive).parents();

        $(_selectors.sublist).hide();
        $(_selectors.sublistTrigger).removeClass(_classes.triggerOn);

        $activeListItemAncestors.each(function() {
            var $listItem = $(this);

            $listItem.children(_selectors.sublist).show();
            $listItem.children(_selectors.sublistTrigger).addClass(_classes.triggerOn);
        });
    }

    function _switchAccountBlock(accountBlockId) {
        var $accountBlocks = $(_selectors.accountBlock),
            $accountBlock = $('#' + accountBlockId);

        if (!_isElementExistent($accountBlock)) {
            return;
        }

        $accountBlocks.removeClass(_classes.widgetOn);
        $accountBlock.addClass(_classes.widgetOn);
    }
})(window);
<commit_msg>[ASIDE] Enabled closing the "aside" by clicking anywhere outside of the "aside" and "aside trigger" in medium and smaller screen sizes.<commit_after>'use strict';

(function(window) {
    /******************** private variables ********************/
    var _isDeviceMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
        _screenWidths = {
            lg: 1200
        },
        _dataAttributes = {
            accountBlockTrigger: 'ptn-account-block-trigger',
            dropdownTrigger: 'ptn-dropdown-trigger'
        },
        _selectors = {
            pageLoader: '#page-loader',
            header: '#header',
            main: '#main',
            aside: '#aside',
            asideTrigger: '#aside-trigger',
            listItem: '.ptn-list__item',
            listItemActive: '.ptn-list__item_active',
            listLink: '.ptn-list__link',
            sublist: '.ptn-list_sublist',
            sublistTrigger: '.ptn-list__item_sublist > .ptn-list__link',
            dropdown: '.ptn-dropdown',
            dropdownTrigger: '.ptn-dropdown__trigger',
            dropdownTriggerGeneral: '[' + _dataAttributes.dropdownTrigger + ']',
            accountBlockTrigger: '[' + _dataAttributes.accountBlockTrigger + ']',
            accountBlock: '.ptn-account-block',
            accountBlockSubmit: '.ptn-account-block__submit',
            inputBlock: '.ptn-input-block',
            inputBlockInputText: '.ptn-input-block__input-text'
        },
        // Disabled because localStorage doesn't work in private browsing.
        // _localStorageKeys = {
        //     statusAside: 'ptn-status-aside'
        // },
        _classes = {
            triggerOn: 'ptn-util__trigger_on',
            widgetOn: 'ptn-util__widget_on',
            htmlMobileDevice: 'ptn-html_mobile-device',
            bodyAsideOn: 'ptn-body_aside-on'
        },
        _actions = {
            show: 'show',
            hide: 'hide'
        },
        _statuses = {
            on: 'on',
            off: 'off'
        },
        _animationDurations = {
            slide: 200,
            click: 500,
            fade: 500
        },
        _delay = {
            pageLoader: 500
        },
        _$window = $(window),
        _$document = $(document),
        _$html = $('html'),
        _$body = $('body');

    /******************** mobile device class ********************/
    if (_isDeviceMobile) {
        _$html.addClass(_classes.htmlMobileDevice);
    }

    /******************** event: DOM ready ********************/
    $(function() {
        /******************** disable pinch-to-zoom for iOS safari ********************/
        _$document.on('gesturestart', function(e) {
            e.preventDefault();
        });

        /******************** widget: waves ********************/
        (function() {
            Waves.attach(_selectors.listLink, ['waves-light']);
            Waves.attach(_selectors.accountBlockSubmit);

            Waves.init({
                duration: _animationDurations.click
            });
        })();

        /******************** widget: scrollbar ********************/
        (function() {
            _addScrollBar($(_selectors.aside), 'minimal-dark', 'y');
        })();

        /******************** widget: aside | toggle ********************/
        // Disabled because localStorage doesn't work in private browsing.
        // (function() {
        //     var asideStatus = localStorage.getItem(_localStorageKeys.statusAside);
        //
        //     if (asideStatus === _statuses.on) {
        //         _toggleAsideStatelessly(_actions.show);
        //     }
        //
        //     _$body.on('click touchend', _selectors.asideTrigger, function(e) {
        //         e.preventDefault();
        //
        //         _toggleAside();
        //     });
        // })();

        (function() {
            _$body.on('click touchend', _selectors.asideTrigger, function(e) {
                e.preventDefault();

                _toggleAside();
            });

            _$body.on('click touchend', function(e) {
                var $target = $(e.target),
                    isTargetInAside = $target.closest(_selectors.aside).length > 0
                        || $target.closest(_selectors.asideTrigger).length > 0;

                if (isTargetInAside || _$document.width() >= _screenWidths.lg) {
                    return;
                }

                _closeAside();
            });
        })();

        /******************** widget: sublist | init ********************/
        (function() {
            _resetSublists();
        })();

        /******************** widget: sublist | toggle ********************/
        (function() {
            _$body.on('click touchend', _selectors.sublistTrigger, function(e) {
                e.preventDefault();

                _toggleSublist($(this));
            });

            function _toggleSublist($trigger) {
                var isSublistOn = $trigger.hasClass(_classes.triggerOn),
                    $listItemClosest = $trigger.closest(_selectors.listItem);

                if (isSublistOn) {
                    $listItemClosest.find(_selectors.sublist).slideUp(_animationDurations.slide);
                    $listItemClosest.find(_selectors.listLink).removeClass(_classes.triggerOn);
                } else {
                    $trigger.next(_selectors.sublist).slideDown(_animationDurations.slide);
                    $trigger.addClass(_classes.triggerOn);
                }
            }
        })();

        /******************** widget: dropdown | toggle ********************/
        (function() {
            _$body.on('click touchend', _selectors.dropdownTrigger, function(e) {
                e.preventDefault();

                var $dropdownTrigger = $(this),
                    $dropdown = $dropdownTrigger.closest(_selectors.dropdown);

                $dropdownTrigger.toggleClass(_classes.triggerOn);
                $dropdown.toggleClass(_classes.widgetOn);
            });

            _$body.on('click touchend', _selectors.dropdownTriggerGeneral, function(e) {
                e.preventDefault();
                e.stopPropagation();

                var dropdownId = $(this).attr(_dataAttributes.dropdownTrigger),
                    $dropdown = $('#' + dropdownId),
                    $dropdownTrigger = $dropdown.find(_selectors.dropdownTrigger);

                $dropdownTrigger.addClass(_classes.triggerOn);
                $dropdown.addClass(_classes.widgetOn);
            });

            _$body.on('click touchend', function(e) {
                var $target = $(e.target),
                    isTargetInDropdown = $target.closest(_selectors.dropdown).length > 0;

                if (isTargetInDropdown) {
                    return;
                }

                $(_selectors.dropdownTrigger).removeClass(_classes.triggerOn);
                $(_selectors.dropdown).removeClass(_classes.widgetOn);
            });
        })();

        /******************** widget: account block | switch ********************/
        (function() {
            _switchAccountBlock('account-block-sign-up');
            _switchAccountBlock('account-block-update');

            _$body.on('click touchend', _selectors.accountBlockTrigger, function(e) {
                e.preventDefault();

                var accountBlockId = $(this).attr(_dataAttributes.accountBlockTrigger);

                _switchAccountBlock(accountBlockId);
            });
        })();

        /******************** widget: input block | focus ********************/
        (function() {
            _$body.on('focus', _selectors.inputBlockInputText, function(e) {
                e.preventDefault();

                $(this).closest(_selectors.inputBlock).addClass(_classes.widgetOn);
            }).on('blur', _selectors.inputBlockInputText, function(e) {
                e.preventDefault();

                $(this).closest(_selectors.inputBlock).removeClass(_classes.widgetOn);
            });
        })();
    });

    /******************** event: page load ********************/
    _$window.on('load', function() {
        /******************** widget: page loader ********************/
        var $pageLoader = $(_selectors.pageLoader);

        if (_isElementExistent($pageLoader)) {
            window.setTimeout(function() {
                $pageLoader.fadeOut(_animationDurations.fade);
                $(_selectors.header).fadeIn(_animationDurations.fade);
                $(_selectors.aside).fadeIn(_animationDurations.fade);
                $(_selectors.main).fadeIn(_animationDurations.fade);
            }, _delay.pageLoader);
        }
    });

    /******************** private functions ********************/
    function _isElementExistent($element) {
        return !!$element.length;
    }

    function _addScrollBar($element, theme, mouseWheelAxis) {
        if (!_isElementExistent($element)) {
            return;
        }

        $element.mCustomScrollbar({
            theme: theme,
            scrollInertia: 100,
            axis: 'y',
            mouseWheel: {
                preventDefault: true,
                enable: true,
                axis: mouseWheelAxis
            }
        });
    }

    function _scrollTo($element, position) {
        $element.mCustomScrollbar('scrollTo', position, {
            scrollInertia: 0
        });
    }

    // Disabled because localStorage doesn't work in private browsing.
    // function _toggleAside() {
    //     var statusOld = localStorage.getItem(_localStorageKeys.statusAside),
    //         statusNew = statusOld === _statuses.on ? _statuses.off : _statuses.on,
    //         $asideTrigger = $(_selectors.asideTrigger),
    //         $aside = $(_selectors.aside);
    //
    //     localStorage.setItem(_localStorageKeys.statusAside, statusNew);
    //
    //     _$body.toggleClass(_classes.bodyAsideOn);
    //     $asideTrigger.toggleClass(_classes.triggerOn);
    //     $aside.toggleClass(_classes.widgetOn);
    //
    //     statusNew === _statuses.on && _resetSublists();
    //     _scrollTo($aside, 'top');
    // }

    function _toggleAside() {
        var $asideTrigger = $(_selectors.asideTrigger),
            $aside = $(_selectors.aside);

        _$body.toggleClass(_classes.bodyAsideOn);
        $asideTrigger.toggleClass(_classes.triggerOn);
        $aside.toggleClass(_classes.widgetOn);

        _$body.hasClass(_classes.bodyAsideOn) && _resetSublists();
        _scrollTo($aside, 'top');
    }

    function _closeAside() {
        var $asideTrigger = $(_selectors.asideTrigger),
            $aside = $(_selectors.aside);

        _$body.removeClass(_classes.bodyAsideOn);
        $asideTrigger.removeClass(_classes.triggerOn);
        $aside.removeClass(_classes.widgetOn);
    }

    function _toggleAsideStatelessly(action) {
        var $asideTrigger = $(_selectors.asideTrigger),
            $aside = $(_selectors.aside);

        if (action === _actions.show) {
            _$body.addClass(_classes.bodyAsideOn);
            $asideTrigger.addClass(_classes.triggerOn);
            $aside.addClass(_classes.widgetOn);
        } else if (action === _actions.hide) {
            _$body.removeClass(_classes.bodyAsideOn);
            $asideTrigger.removeClass(_classes.triggerOn);
            $aside.removeClass(_classes.widgetOn);
        }

        _resetSublists();
        _scrollTo($aside, 'top');
    }

    function _resetSublists() {
        var $activeListItemAncestors = $(_selectors.listItemActive).parents();

        $(_selectors.sublist).hide();
        $(_selectors.sublistTrigger).removeClass(_classes.triggerOn);

        $activeListItemAncestors.each(function() {
            var $listItem = $(this);

            $listItem.children(_selectors.sublist).show();
            $listItem.children(_selectors.sublistTrigger).addClass(_classes.triggerOn);
        });
    }

    function _switchAccountBlock(accountBlockId) {
        var $accountBlocks = $(_selectors.accountBlock),
            $accountBlock = $('#' + accountBlockId);

        if (!_isElementExistent($accountBlock)) {
            return;
        }

        $accountBlocks.removeClass(_classes.widgetOn);
        $accountBlock.addClass(_classes.widgetOn);
    }
})(window);
<|endoftext|>


<commit_before>// @flow
/**
 * When using streaming rendering, style blocks are emitted in chunks directly.
 * This method used to be necessary to move all style elements before the
 * rehydration kicks in. However the rehydration now takes all SSR style tags,
 * combines them into a single tag, and adds it to the head.
 * This renders the consolidation useless
 */
export default function consolidateStreamedStyles() {
  /* noop */
}
<commit_msg>Add deprecation note and warning to consolidateStreamedStyles<commit_after>// @flow

/* This function is DEPRECATED and will be removed on the next major version release.
 * It was needed to rehydrate all style blocks prepended to chunks before React
 * tries to rehydrate its HTML stream. Since the master StyleSheet will now detect
 * the use of streamed style tags and will perform the rehydration earlier when needed
 * this function will not be needed anymore */
export default function consolidateStreamedStyles() {
  if (process.env.NODE_ENV !== 'production') {
    // eslint-disable-next-line no-console
    console.warn(
      'The consolidateStreamedStyles function for streamed SSR rehydration was removed.\n' +
        'The rehydration will now be automatically performed at an earlier point in time ' +
        'on the client so that this function is now not needed anymore and is thus obsolete.\n' +
        '- Consider removing this call on the client and use React + styled-components as you would expect'
    )
  }
}
<|endoftext|>


<commit_before>/**
 * 年份展示面板
 *
 * Created by GUY on 2015/9/2.
 * @class BI.YearPopup
 * @extends BI.Trigger
 */
BI.YearPopup = BI.inherit(BI.Widget, {

    _defaultConfig: function () {
        return BI.extend(BI.YearPopup.superclass._defaultConfig.apply(this, arguments), {
            baseCls: "bi-year-popup",
            behaviors: {},
            min: "1900-01-01", // 最小日期
            max: "2099-12-31" // 最大日期
        });
    },

    _createYearCalendar: function (v) {
        var o = this.options, y = this._year;

        var calendar = BI.createWidget({
            type: "bi.year_calendar",
            behaviors: o.behaviors,
            min: o.min,
            max: o.max,
            logic: {
                dynamic: true
            },
            year: y + v * 12
        });
        calendar.setValue(this._year);
        return calendar;
    },

    _init: function () {
        BI.YearPopup.superclass._init.apply(this, arguments);
        var self = this, o = this.options;

        this.selectedYear = this._year = BI.getDate().getFullYear();

        this.backBtn = BI.createWidget({
            type: "bi.icon_button",
            cls: "pre-page-h-font",
            width: 24,
            height: 24,
            value: -1
        });

        this.preBtn = BI.createWidget({
            type: "bi.icon_button",
            cls: "next-page-h-font",
            width: 24,
            height: 24,
            value: 1
        });

        this.navigation = BI.createWidget({
            type: "bi.navigation",
            element: this,
            single: true,
            logic: {
                dynamic: true
            },
            tab: {
                cls: "year-popup-navigation bi-high-light bi-split-top",
                height: 24,
                items: [this.backBtn, this.preBtn]
            },
            cardCreator: BI.bind(this._createYearCalendar, this),

            afterCardShow: function () {
                this.setValue(self.selectedYear);
                var calendar = this.getSelectedCard();
                calendar && self.backBtn.setEnable(!calendar.isFrontYear());
                calendar && self.preBtn.setEnable(!calendar.isFinalYear());
            }
        });

        this.navigation.on(BI.Navigation.EVENT_CHANGE, function () {
            self.selectedYear = this.getValue();
            self.fireEvent(BI.Controller.EVENT_CHANGE, arguments);
            self.fireEvent(BI.YearPopup.EVENT_CHANGE, self.selectedYear);
        });

        if(BI.isKey(o.value)){
            this.setValue(o.value);
        }
    },

    _checkMin: function () {
        var calendar = this.navigation.getSelectedCard();
        if (BI.isNotNull(calendar)) {
            calendar.setMinDate(this.options.min);
            this.backBtn.setEnable(!calendar.isFrontYear());
            this.preBtn.setEnable(!calendar.isFinalYear());
        }
    },

    _checkMax: function () {
        var calendar = this.navigation.getSelectedCard();
        if (BI.isNotNull(calendar)) {
            calendar.setMaxDate(this.options.max);
            this.backBtn.setEnable(!calendar.isFrontYear());
            this.preBtn.setEnable(!calendar.isFinalYear());
        }
    },

    setMinDate: function (minDate) {
        if (BI.isNotEmptyString(this.options.min)) {
            this.options.min = minDate;
            this._checkMin();
        }
    },

    setMaxDate: function (maxDate) {
        if (BI.isNotEmptyString(this.options.max)) {
            this.options.max = maxDate;
            this._checkMax();
        }
    },

    getValue: function () {
        return this.selectedYear;
    },

    setValue: function (v) {
        var o = this.options;
        v = BI.parseInt(v);
        // 对于年控件来说，只要传入的minDate和maxDate的year区间包含v就是合法的
        var startDate = BI.parseDateTime(o.min, "%Y-%X-%d");
        var endDate = BI.parseDateTime(o.max, "%Y-%X-%d");
        if (BI.checkDateVoid(v, 1, 1, BI.print(BI.getDate(startDate.getFullYear(), 0, 1), "%Y-%X-%d"), BI.print(BI.getDate(endDate.getFullYear(), 0, 1), "%Y-%X-%d"))[0]) {
            v = BI.getDate().getFullYear();
            this.selectedYear = "";
            this.navigation.setSelect(BI.YearCalendar.getPageByYear(v));
            this.navigation.setValue("");
        } else {
            this.selectedYear = v;
            this.navigation.setSelect(BI.YearCalendar.getPageByYear(v));
            this.navigation.setValue(v);
        }
    }
});
BI.YearPopup.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.year_popup", BI.YearPopup);<commit_msg>REPORT-44278 fix: 初始化而不仅仅切换时就要计算灰化<commit_after>/**
 * 年份展示面板
 *
 * Created by GUY on 2015/9/2.
 * @class BI.YearPopup
 * @extends BI.Trigger
 */
BI.YearPopup = BI.inherit(BI.Widget, {

    _defaultConfig: function () {
        return BI.extend(BI.YearPopup.superclass._defaultConfig.apply(this, arguments), {
            baseCls: "bi-year-popup",
            behaviors: {},
            min: "1900-01-01", // 最小日期
            max: "2099-12-31" // 最大日期
        });
    },

    _createYearCalendar: function (v) {
        var o = this.options, y = this._year;

        var calendar = BI.createWidget({
            type: "bi.year_calendar",
            behaviors: o.behaviors,
            min: o.min,
            max: o.max,
            logic: {
                dynamic: true
            },
            year: y + v * 12
        });
        calendar.setValue(this._year);
        return calendar;
    },

    _init: function () {
        BI.YearPopup.superclass._init.apply(this, arguments);
        var self = this, o = this.options;

        this.selectedYear = this._year = BI.getDate().getFullYear();

        this.backBtn = BI.createWidget({
            type: "bi.icon_button",
            cls: "pre-page-h-font",
            width: 24,
            height: 24,
            value: -1
        });

        this.preBtn = BI.createWidget({
            type: "bi.icon_button",
            cls: "next-page-h-font",
            width: 24,
            height: 24,
            value: 1
        });

        this.navigation = BI.createWidget({
            type: "bi.navigation",
            element: this,
            single: true,
            logic: {
                dynamic: true
            },
            tab: {
                cls: "year-popup-navigation bi-high-light bi-split-top",
                height: 24,
                items: [this.backBtn, this.preBtn]
            },
            cardCreator: BI.bind(this._createYearCalendar, this),
            afterCardCreated: function () {
                this.setValue(self.selectedYear);
                var calendar = this.getSelectedCard();
                calendar && self.backBtn.setEnable(!calendar.isFrontYear());
                calendar && self.preBtn.setEnable(!calendar.isFinalYear());
            }
        });

        this.navigation.on(BI.Navigation.EVENT_CHANGE, function () {
            self.selectedYear = this.getValue();
            self.fireEvent(BI.Controller.EVENT_CHANGE, arguments);
            self.fireEvent(BI.YearPopup.EVENT_CHANGE, self.selectedYear);
        });

        if(BI.isKey(o.value)){
            this.setValue(o.value);
        }
    },

    _checkMin: function () {
        var calendar = this.navigation.getSelectedCard();
        if (BI.isNotNull(calendar)) {
            calendar.setMinDate(this.options.min);
            this.backBtn.setEnable(!calendar.isFrontYear());
            this.preBtn.setEnable(!calendar.isFinalYear());
        }
    },

    _checkMax: function () {
        var calendar = this.navigation.getSelectedCard();
        if (BI.isNotNull(calendar)) {
            calendar.setMaxDate(this.options.max);
            this.backBtn.setEnable(!calendar.isFrontYear());
            this.preBtn.setEnable(!calendar.isFinalYear());
        }
    },

    setMinDate: function (minDate) {
        if (BI.isNotEmptyString(this.options.min)) {
            this.options.min = minDate;
            this._checkMin();
        }
    },

    setMaxDate: function (maxDate) {
        if (BI.isNotEmptyString(this.options.max)) {
            this.options.max = maxDate;
            this._checkMax();
        }
    },

    getValue: function () {
        return this.selectedYear;
    },

    setValue: function (v) {
        var o = this.options;
        v = BI.parseInt(v);
        // 对于年控件来说，只要传入的minDate和maxDate的year区间包含v就是合法的
        var startDate = BI.parseDateTime(o.min, "%Y-%X-%d");
        var endDate = BI.parseDateTime(o.max, "%Y-%X-%d");
        if (BI.checkDateVoid(v, 1, 1, BI.print(BI.getDate(startDate.getFullYear(), 0, 1), "%Y-%X-%d"), BI.print(BI.getDate(endDate.getFullYear(), 0, 1), "%Y-%X-%d"))[0]) {
            v = BI.getDate().getFullYear();
            this.selectedYear = "";
            this.navigation.setSelect(BI.YearCalendar.getPageByYear(v));
            this.navigation.setValue("");
        } else {
            this.selectedYear = v;
            this.navigation.setSelect(BI.YearCalendar.getPageByYear(v));
            this.navigation.setValue(v);
        }
    }
});
BI.YearPopup.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.year_popup", BI.YearPopup);<|endoftext|>


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

  angular
    .module('dopplerRelay')
    .controller('BillingCtrl', BillingCtrl);

  BillingCtrl.$inject = [
    '$scope',
    '$location',
    '$rootScope',
    'auth',
    '$translate',
    '$timeout',
    'settings',
    'utils',
    'resources',
    'ModalService'
  ];

  var secCodeMasksByBrand = {
    'mastercard': '999',
    'visa': '999',
    'amex': '9999',
    'unknown': '999'
  };
  var creditCardMasksByBrand = {
     'mastercard': '9999 9999 9999 9999',
     'visa': '9999 9999 9999 9999',
     'amex': '9999 999999 99999',
     'unknown': '9999 9999 9999 9999'
  };
  function BillingCtrl($scope, $location, $rootScope, auth, $translate, $timeout, settings, utils, resources, ModalService) {
    var vm = this;
    $rootScope.setSubmenues([
      { text: 'submenu_my_profile', url: 'settings/my-profile', active: false },
      { text: 'submenu_my_plan', url: 'settings/my-plan', active: false }
    ]);
    var queryParams = $location.search();
    var planName = queryParams['plan'];
    vm.activationPromise = activate();
    vm.redirectToPlanSelection = redirectToPlanSelection;
    vm.cancelAction = cancelAction;

    function activate() {

      resources.ensureCountries();
      vm.resources = resources.data;

      if (!planName) {
        return redirectToPlanSelection();
      }
      vm.planName = planName;
      return settings.getPlansAvailable().then(function(response){
        var planSelected = response.data.items.find(function(obj){
          return obj.name == planName;
        });
        if (!planSelected) {
          return redirectToPlanSelection();
        }
        vm.currentCurrency = planSelected.currency;
        vm.planPrice = planSelected.fee + (planSelected.ips_count * planSelected.cost_by_ip || 0);

        return settings.getCurrentPlanInfo().then(function(response) {

          var currentPlanPrice = response.data.fee + (response.data.ips_count * response.data.cost_by_ip || 0);
          if(currentPlanPrice >= vm.planPrice){
            vm.showConfirmation = true;
            vm.downgrade = true;
          }

          if(response.data.billingInformation){
            vm.name = response.data.billingInformation.name;
            vm.company = response.data.billingInformation.companyName;
            vm.address = response.data.billingInformation.address;
            vm.city = response.data.billingInformation.city;
            vm.zCode = response.data.billingInformation.zipCode;
            resources.ensureCountries().then(function(){
              vm.country = vm.resources.countries.find(function(obj){
                return obj.code == response.data.billingInformation.countryCode;
              });
            });

          }
        });

      });
    }
    vm.checkExpDate = checkExpDate;
    vm.submitBilling = submitBilling;
    vm.submitBillingPayment = submitBillingPayment;

    vm.cc = {number: '', brand: {}, mask: ''};
    vm.secCode = {number: '', mask: ''};
    vm.maskOptions = {
      allowInvalidValue: true, //allows us to watch the value
      clearOnBlur: false
    };

    function redirectToPlanSelection() {
      $location.path('/settings/my-plan');
    }

    $scope.$watch('vm.cc.number', fillCreditCardProperties);

    function fillCreditCardProperties(newNumber) {
      vm.cc.brand = getCreditCardBrand(newNumber);
      vm.cc.mask = getMaskByBrand(vm.cc.brand);
      vm.secCode.mask = getMaskSecCodByBrand(vm.cc.brand);
    }

    function getMaskSecCodByBrand(cardBrand){
      return secCodeMasksByBrand[cardBrand];
    }

    function getMaskByBrand(cardBrand){
      return creditCardMasksByBrand[cardBrand];
    }

    function getCreditCardBrand(creditCardNumber) {
    	// start without knowing the credit card brand
    	var result = "unknown";

    	// first check for MasterCard
    	if (/^5[1-5]/.test(creditCardNumber)) {
        	result = "mastercard";
    	}
    	// then check for Visa
    	else if (/^4/.test(creditCardNumber)) {
    		result = "visa";
    	}
    	// then check for AmEx
    	else if (/^3[47]/.test(creditCardNumber)) {
    		result = "amex";
    	}
    	return result;
    }

    function checkExpDate(input) {
      var currentDate = new Date();
      var currentYear = currentDate.getFullYear().toString().slice(2);
      var currentMonth = currentDate.getMonth() + 1;
      if (input.$modelValue && input.$modelValue.length == 4) {
        var year = input.$modelValue.slice(2);
        var month = input.$modelValue.slice(0,2);
        if (year < currentYear || month > 12) {
          utils.setServerValidationToField($scope, $scope.form.expDate, 'ilegal_date');
        }
        if (year == currentYear && month < currentMonth) {
          utils.setServerValidationToField($scope, $scope.form.expDate, 'ilegal_date');
        }
      }
    }

    var onExpectedError = function (rejectionData) {
      vm.paymentFailure = true;
      return true;
    };

    function submitBilling(form) {
      vm.submitted = true;
      if (!utils.validateCreditCard(vm.cc.number)) {
        utils.setServerValidationToField($scope, $scope.form.cardNumber, 'invalid_card_number');
      }
      if (!form.$valid) {
        return;
      }

      vm.showConfirmation = true;
      vm.cc.parsedCcNumber = utils.replaceAllCharsExceptLast4(vm.cc.number);
      vm.secCode.ParsedNumber = utils.replaceAllCharsExceptLast4(vm.secCode.number);
      vm.viewExpDate = form.expDate.$viewValue;
    }
    function submitBillingPayment() {
      var agreement = {
        planName: planName,
        paymentMethod: {
          creditCard: {
            cardNumber: vm.cc.number,
            verificationCode: vm.secCode.number,
            expiryDate: vm.expDate,
            cardHoldersName: vm.cardHolder,
            cardBrand: vm.cc.brand
          }
        },
        billingInformation: {
          name: vm.name,
          companyName:vm.company,
          address: vm.address,
          city: vm.city,
          zipCode: vm.zCode,
          countryCode: vm.country.code
        }
     };

      if (vm.downgrade) {
        downgrade(agreement);
        return;
      }

      return settings.billingPayment(agreement, onExpectedError)
      .then(function() {
        return ModalService.showModal({
        templateUrl: 'partials/modals/general-template.html',
        controller: 'GeneralTemplateCtrl',
        controllerAs: 'vm',
        inputs: {
          title: "success_upgrade_title",
          mainText: "success_upgrade_text",
          buttonText: "success_upgrade_button",
          action: null
        }
        })
        .then(function (modal) {
          modal.close.then(redirectToPlanSelection);
        });
      });
    }

    function downgrade(agreement) {
      ModalService.showModal({
        templateUrl: 'partials/modals/confirm-input-template.html',
        controller: 'ConfirmInputTemplate',
        controllerAs: 'vm',
        inputs: {
          title: "downgrade_popup_title",
          mainText: "downgrade_popup_main_text",
          descriptionInput: "downgrade_popup_confirm_text",
          confirmationWord: "downgrade_popup_confirm_word",
          actionSuccess: downgradeAction,
          cancelButtonText: "downgrade_popup_cancel_button",
          buttonText: "confirm_text"
        }
      })
      .then(function (modal) {
        modal.close.then(redirectToPlanSelection);
      });
    }
    function downgradeAction(agreement) {
      return settings.downgrade(agreement, onExpectedError);
    }
    function cancelAction() {
      if (!vm.downgrade) {
        vm.showConfirmation = false;
        vm.paymentFailure = false;
      }
      redirectToPlanSelection();
    }
  }

})();
<commit_msg>[DR-1405] Fix issue with cancel action when its not downgrade<commit_after>(function() {
  'use strict';

  angular
    .module('dopplerRelay')
    .controller('BillingCtrl', BillingCtrl);

  BillingCtrl.$inject = [
    '$scope',
    '$location',
    '$rootScope',
    'auth',
    '$translate',
    '$timeout',
    'settings',
    'utils',
    'resources',
    'ModalService'
  ];

  var secCodeMasksByBrand = {
    'mastercard': '999',
    'visa': '999',
    'amex': '9999',
    'unknown': '999'
  };
  var creditCardMasksByBrand = {
     'mastercard': '9999 9999 9999 9999',
     'visa': '9999 9999 9999 9999',
     'amex': '9999 999999 99999',
     'unknown': '9999 9999 9999 9999'
  };
  function BillingCtrl($scope, $location, $rootScope, auth, $translate, $timeout, settings, utils, resources, ModalService) {
    var vm = this;
    $rootScope.setSubmenues([
      { text: 'submenu_my_profile', url: 'settings/my-profile', active: false },
      { text: 'submenu_my_plan', url: 'settings/my-plan', active: false }
    ]);
    var queryParams = $location.search();
    var planName = queryParams['plan'];
    vm.activationPromise = activate();
    vm.redirectToPlanSelection = redirectToPlanSelection;
    vm.cancelAction = cancelAction;

    function activate() {

      resources.ensureCountries();
      vm.resources = resources.data;

      if (!planName) {
        return redirectToPlanSelection();
      }
      vm.planName = planName;
      return settings.getPlansAvailable().then(function(response){
        var planSelected = response.data.items.find(function(obj){
          return obj.name == planName;
        });
        if (!planSelected) {
          return redirectToPlanSelection();
        }
        vm.currentCurrency = planSelected.currency;
        vm.planPrice = planSelected.fee + (planSelected.ips_count * planSelected.cost_by_ip || 0);

        return settings.getCurrentPlanInfo().then(function(response) {

          var currentPlanPrice = response.data.fee + (response.data.ips_count * response.data.cost_by_ip || 0);
          if(currentPlanPrice >= vm.planPrice){
            vm.showConfirmation = true;
            vm.downgrade = true;
          }

          if(response.data.billingInformation){
            vm.name = response.data.billingInformation.name;
            vm.company = response.data.billingInformation.companyName;
            vm.address = response.data.billingInformation.address;
            vm.city = response.data.billingInformation.city;
            vm.zCode = response.data.billingInformation.zipCode;
            resources.ensureCountries().then(function(){
              vm.country = vm.resources.countries.find(function(obj){
                return obj.code == response.data.billingInformation.countryCode;
              });
            });

          }
        });

      });
    }
    vm.checkExpDate = checkExpDate;
    vm.submitBilling = submitBilling;
    vm.submitBillingPayment = submitBillingPayment;

    vm.cc = {number: '', brand: {}, mask: ''};
    vm.secCode = {number: '', mask: ''};
    vm.maskOptions = {
      allowInvalidValue: true, //allows us to watch the value
      clearOnBlur: false
    };

    function redirectToPlanSelection() {
      $location.path('/settings/my-plan');
    }

    $scope.$watch('vm.cc.number', fillCreditCardProperties);

    function fillCreditCardProperties(newNumber) {
      vm.cc.brand = getCreditCardBrand(newNumber);
      vm.cc.mask = getMaskByBrand(vm.cc.brand);
      vm.secCode.mask = getMaskSecCodByBrand(vm.cc.brand);
    }

    function getMaskSecCodByBrand(cardBrand){
      return secCodeMasksByBrand[cardBrand];
    }

    function getMaskByBrand(cardBrand){
      return creditCardMasksByBrand[cardBrand];
    }

    function getCreditCardBrand(creditCardNumber) {
    	// start without knowing the credit card brand
    	var result = "unknown";

    	// first check for MasterCard
    	if (/^5[1-5]/.test(creditCardNumber)) {
        	result = "mastercard";
    	}
    	// then check for Visa
    	else if (/^4/.test(creditCardNumber)) {
    		result = "visa";
    	}
    	// then check for AmEx
    	else if (/^3[47]/.test(creditCardNumber)) {
    		result = "amex";
    	}
    	return result;
    }

    function checkExpDate(input) {
      var currentDate = new Date();
      var currentYear = currentDate.getFullYear().toString().slice(2);
      var currentMonth = currentDate.getMonth() + 1;
      if (input.$modelValue && input.$modelValue.length == 4) {
        var year = input.$modelValue.slice(2);
        var month = input.$modelValue.slice(0,2);
        if (year < currentYear || month > 12) {
          utils.setServerValidationToField($scope, $scope.form.expDate, 'ilegal_date');
        }
        if (year == currentYear && month < currentMonth) {
          utils.setServerValidationToField($scope, $scope.form.expDate, 'ilegal_date');
        }
      }
    }

    var onExpectedError = function (rejectionData) {
      vm.paymentFailure = true;
      return true;
    };

    function submitBilling(form) {
      vm.submitted = true;
      if (!utils.validateCreditCard(vm.cc.number)) {
        utils.setServerValidationToField($scope, $scope.form.cardNumber, 'invalid_card_number');
      }
      if (!form.$valid) {
        return;
      }

      vm.showConfirmation = true;
      vm.cc.parsedCcNumber = utils.replaceAllCharsExceptLast4(vm.cc.number);
      vm.secCode.ParsedNumber = utils.replaceAllCharsExceptLast4(vm.secCode.number);
      vm.viewExpDate = form.expDate.$viewValue;
    }
    function submitBillingPayment() {
      var agreement = {
        planName: planName,
        paymentMethod: {
          creditCard: {
            cardNumber: vm.cc.number,
            verificationCode: vm.secCode.number,
            expiryDate: vm.expDate,
            cardHoldersName: vm.cardHolder,
            cardBrand: vm.cc.brand
          }
        },
        billingInformation: {
          name: vm.name,
          companyName:vm.company,
          address: vm.address,
          city: vm.city,
          zipCode: vm.zCode,
          countryCode: vm.country.code
        }
     };

      if (vm.downgrade) {
        downgrade(agreement);
        return;
      }

      return settings.billingPayment(agreement, onExpectedError)
      .then(function() {
        return ModalService.showModal({
        templateUrl: 'partials/modals/general-template.html',
        controller: 'GeneralTemplateCtrl',
        controllerAs: 'vm',
        inputs: {
          title: "success_upgrade_title",
          mainText: "success_upgrade_text",
          buttonText: "success_upgrade_button",
          action: null
        }
        })
        .then(function (modal) {
          modal.close.then(redirectToPlanSelection);
        });
      });
    }

    function downgrade(agreement) {
      ModalService.showModal({
        templateUrl: 'partials/modals/confirm-input-template.html',
        controller: 'ConfirmInputTemplate',
        controllerAs: 'vm',
        inputs: {
          title: "downgrade_popup_title",
          mainText: "downgrade_popup_main_text",
          descriptionInput: "downgrade_popup_confirm_text",
          confirmationWord: "downgrade_popup_confirm_word",
          actionSuccess: downgradeAction,
          cancelButtonText: "downgrade_popup_cancel_button",
          buttonText: "confirm_text"
        }
      })
      .then(function (modal) {
        modal.close.then(redirectToPlanSelection);
      });
    }
    function downgradeAction(agreement) {
      return settings.downgrade(agreement, onExpectedError);
    }
    function cancelAction() {
      if (!vm.downgrade) {
        vm.showConfirmation = false;
        vm.paymentFailure = false;
        return;
      }
      redirectToPlanSelection();
    }
  }

})();
<|endoftext|>