<commit_before>/*
 * ***** BEGIN LICENSE BLOCK *****
 * OpenPGP Zimbra Secure is the open source digital signature and encrypt for Zimbra Collaboration Open Source Edition software
 * Copyright (C) 2016-present OpenPGP Zimbra Secure

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 * ***** END LICENSE BLOCK *****
 *
 * OpenPGP MIME Secure Email Zimlet
 *
 * Written by nguyennv1981@gmail.com
 */

OpenPGPUtils = function() {};

OpenPGPUtils.BASE64_TABLE_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
OpenPGPUtils.BASE64_TABLE = OpenPGPUtils.BASE64_TABLE_STRING.split("");

OpenPGPUtils.SIGNED_MESSAGE_CONTENT_TYPE = 'multipart/signed';
OpenPGPUtils.ENCRYPTED_MESSAGE_CONTENT_TYPE = 'multipart/encrypted';

OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE = 'application/pgp-signature';
OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE = 'application/pgp-encrypted';
OpenPGPUtils.OPENPGP_KEYS_CONTENT_TYPE = 'application/pgp-keys';

OpenPGPUtils.OPENPGP_CONTENT_TYPES = [
    OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE,
    OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE,
    OpenPGPUtils.OPENPGP_KEYS_CONTENT_TYPE
];

OpenPGPUtils.isSignedMessage = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.SIGNED_MESSAGE_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.isEncryptedMessage = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.ENCRYPTED_MESSAGE_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.hasSignatureContentType = function(msg) {
    return msg.hasContentType(OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE);
};

OpenPGPUtils.hasEncryptedContentType = function(msg) {
    return msg.hasContentType(OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE);
};

OpenPGPUtils.hasOPENPGPContentType = function(msg) {
    var has = false;
    OpenPGPUtils.OPENPGP_CONTENT_TYPES.forEach(function(ct) {
        has = msg.hasContentType(ct);
        if (has) {
            return;
        }
    });
    return has;
};

OpenPGPUtils.isOPENPGPContentType = function(cType) {
    return AjxUtil.indexOf(OpenPGPUtils.OPENPGP_CONTENT_TYPES, cType) !== -1;
};

OpenPGPUtils.isSignatureContentType = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.isEncryptedContentType = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.localStorageSave = function(key, pwd, data) {
    var opts = {
        data: data,
        passwords: [pwd]
    };
    return openpgp.encrypt(opts).then(function(encrypted) {
        localStorage[key] = encrypted.data;
        return encrypted.data;
    });
}

OpenPGPUtils.localStorageRead = function(key, pwd) {
    var sequence = Promise.resolve();
    return sequence.then(function() {
        if (localStorage[key]) {
            var encrypted = localStorage[key];
            var opts = {
                message: openpgp.message.readArmored(encrypted),
                password: pwd
            };
            return openpgp.decrypt(opts).then(function(plaintext) {
                return plaintext.data;
            });
        }
        else {
            return false;
        }
    });
}

OpenPGPUtils.base64Encode = function(bin) {
    if (!window.btoa) {
        for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) {
          var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++);
          if ((a | b | c) > 255) throw new Error("String contains an invalid character");
          base64[base64.length] = OpenPGPUtils.BASE64_TABLE[a >> 2] + OpenPGPUtils.BASE64_TABLE[((a << 4) & 63) | (b >> 4)] +
                                  (isNaN(b) ? "=" : OpenPGPUtils.BASE64_TABLE[((b << 2) & 63) | (c >> 6)]) +
                                  (isNaN(b + c) ? "=" : OpenPGPUtils.BASE64_TABLE[c & 63]);
        }
        return base64.join("");
    }
    else {
        return window.btoa(bin);
    }
};

OpenPGPUtils.base64Decode = function(base64) {
    if (!window.atob) {
        if (/(=[^=]+|={3,})$/.test(base64)) throw new Error("String contains an invalid character");
        base64 = base64.replace(/=/g, "");
        var n = base64.length & 3;
        if (n === 1) throw new Error("String contains an invalid character");
        for (var i = 0, j = 0, len = base64.length / 4, bin = []; i < len; ++i) {
            var a = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A"), b = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A");
            var c = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A"), d = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A");
            if ((a | b | c | d) < 0) throw new Error("String contains an invalid character");
            bin[bin.length] = ((a << 2) | (b >> 4)) & 255;
            bin[bin.length] = ((b << 4) | (c >> 2)) & 255;
            bin[bin.length] = ((c << 6) | d) & 255;
        };
        return String.fromCharCode.apply(null, bin).substr(0, bin.length + n - 4);
    }
    else {
        return window.atob(base64);
    }
};

OpenPGPUtils.hexToBase64 = function(hex) {
    return OpenPGPUtils.base64Encode(String.fromCharCode.apply(null,
        hex.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
    );
};

OpenPGPUtils.hexToBin = function(hex) {
    var bytes = [];

    for(var i = 0; i < hex.length - 1; i += 2) {
        bytes.push(parseInt(hex.substr(i, 2), 16));
    }
    return String.fromCharCode.apply(String, bytes);;
};

OpenPGPUtils.fetchPart = function(part, baseUrl) {
    var url, cType, cDisposition, iType;
    var addTimestamp = true;

    if (part.id) {
        url = [baseUrl, '&id=', part.id].join('');
    } else if (part.mid && part.part) {
        url = [baseUrl, '&id=', part.mid, '&part=', part.part].join('');
    } else if (part.path) {
        url = [baseUrl.replace(/\?.*/,''), part.path].join('');
        addTimestamp = false;
    } else {
        OpenPGPZimbraSecure.popupErrorDialog('attach-convert-error');
    }

    if (url) {
        var rpcReq = new AjxRpcRequest();

        if (addTimestamp) {
            //add a timestamp param so that browser will not cache the request
            url += "&timestamp=" + new Date().getTime();
        }
        var response = rpcReq.invoke('', url,
                                     { 'X-Zimbra-Encoding': 'x-base64' },
                                     null, true);
        cType = rpcReq.__httpReq.getResponseHeader('Content-Type');
        cDisposition = rpcReq.__httpReq.getResponseHeader('Content-Disposition');

        iType = rpcReq.__httpReq.getResponseHeader('X-Zimbra-ItemType');
        if (iType == 'message') {
            //this is a attached message
            cDisposition = 'attachment';
            //now set the file name for this attachment.
            var m =  appCtxt.cacheGet(part.id); //get the attached message from cache
            if (m && m.subject) {
                cDisposition = 'attachment; filename="' + m.subject + '"';
            }
        }
        if (iType == 'document' && !cDisposition) {
            var iName = rpcReq.__httpReq.getResponseHeader('X-Zimbra-ItemName');
            if (iName) {
                cDisposition = 'attachment; filename="' + iName + '"';
            }
        }

        if (response.success) {
            return { data: response.text, ct: cType, cd: cDisposition};
        }
    }
};

OpenPGPUtils.mimeMessageToZmMimePart = function(message, withAttachment) {
    var deep = 0;
    var partIndexes = [];
    withAttachment = withAttachment | false;

    function visitMessage(message) {
        deep++;
        var cd = message.header('Content-Disposition');
        if (!withAttachment && cd === 'attachment') {
            deep--;
            return false;
        }

        var part = {};
        var cType = message.contentType();
        part.ct = cType.fulltype;
        if (partIndexes.length == 0) {
            part.part = 'TEXT';
        }
        else {
            part.part = partIndexes.join('.');
        }
        if (typeof message._body === 'string') {
            var content = '';
            var encode = message.header('Content-Transfer-Encoding');
            if (encode === 'quoted-printable') {
                content = utf8.decode(quotedPrintable.decode(message._body));
            }
            else {
                content = message._body;
            }
            if (part.ct == 'text/html' || part.ct == 'text/plain') {
                part.content = content;
            }
            if (encode === 'base64') {
                part.s = OpenPGPUtils.base64Decode(message._body).length;
            }
            else {
                part.s = content.length;
            }
        }
        if (cType.params.name) {
            part.filename = cType.params.name;
        }
        if (cd) {
            part.cd = cd;
        }
        if (Array.isArray(message._body)) {
            part.mp = [];
            message._body.forEach(function(entity, index) {
                partIndexes[deep - 1] = index + 1;
                var mp = visitMessage(entity);
                if (mp) {
                    part.mp.push(mp);
                }
            });
        }
        deep--;
        partIndexes.pop();
        return part;
    }

    function findBody(cType, part) {
        var bodyFound = false;
        if (part.mp) {
            findBody(cType, part.mp);
        } else if (part.ct) {
            if (part.ct == cType) {
                part.body = bodyFound = true;
            } else if (part.cd == 'inline') {
                part.body = true;
            }
        } else {
            for (var i = 0; !bodyFound && i < part.length; i++) {
                bodyFound = findBody(cType, part[i]);
            }
        }
        return bodyFound;
    }

    var msg = visitMessage(message);
    if (!findBody('text/html', msg))
        findBody('text/plain', msg);

    return msg;
};

OpenPGPUtils.getDefaultSenderAddress = function() {
    var account = (appCtxt.accountList.defaultAccount ||
                   appCtxt.accountList.activeAccount ||
                   appCtxt.accountList.mainAccount);
    var identity = appCtxt.getIdentityCollection(account).defaultIdentity;

    return new AjxEmailAddress(identity.sendFromAddress, AjxEmailAddress.FROM, identity.sendFromDisplay);
};

OpenPGPUtils.prop = function(key) {
    return openpgp_zimbra_secure[key];
};

/*
 * random-string
 * https://github.com/valiton/node-random-string
 *
 * Copyright (c) 2013 Valiton GmbH, Bastian 'hereandnow' Behrens
 * Licensed under the MIT license.
 */
OpenPGPUtils.randomString = function(opts) {
    var numbers = '0123456789',
        letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        specials = '!$%^&*()_+|~-=`{}[]:;<>?,./';

    function _defaults(opts) {
        opts || (opts = {});
        return {
            length: opts.length || 32,
            numeric: typeof opts.numeric === 'boolean' ? opts.numeric : true,
            letters: typeof opts.letters === 'boolean' ? opts.letters : true,
            special: typeof opts.special === 'boolean' ? opts.special : false
        };
    }

    function _buildChars(opts) {
        var chars = '';
        if (opts.numeric) { chars += numbers; }
        if (opts.letters) { chars += letters; }
        if (opts.special) { chars += specials; }
        return chars;
    }

    opts = _defaults(opts);
    var i, rn,
        rnd = '',
        len = opts.length,
        randomChars = _buildChars(opts);
    for (i = 1; i <= len; i++) {
        rnd += randomChars.substring(rn = Math.floor(Math.random() * randomChars.length), rn + 1);
    }
    return rnd;
};
<commit_msg>Fix find body<commit_after>/*
 * ***** BEGIN LICENSE BLOCK *****
 * OpenPGP Zimbra Secure is the open source digital signature and encrypt for Zimbra Collaboration Open Source Edition software
 * Copyright (C) 2016-present OpenPGP Zimbra Secure

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 * ***** END LICENSE BLOCK *****
 *
 * OpenPGP MIME Secure Email Zimlet
 *
 * Written by nguyennv1981@gmail.com
 */

OpenPGPUtils = function() {};

OpenPGPUtils.BASE64_TABLE_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
OpenPGPUtils.BASE64_TABLE = OpenPGPUtils.BASE64_TABLE_STRING.split("");

OpenPGPUtils.SIGNED_MESSAGE_CONTENT_TYPE = 'multipart/signed';
OpenPGPUtils.ENCRYPTED_MESSAGE_CONTENT_TYPE = 'multipart/encrypted';

OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE = 'application/pgp-signature';
OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE = 'application/pgp-encrypted';
OpenPGPUtils.OPENPGP_KEYS_CONTENT_TYPE = 'application/pgp-keys';

OpenPGPUtils.OPENPGP_CONTENT_TYPES = [
    OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE,
    OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE,
    OpenPGPUtils.OPENPGP_KEYS_CONTENT_TYPE
];

OpenPGPUtils.isSignedMessage = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.SIGNED_MESSAGE_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.isEncryptedMessage = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.ENCRYPTED_MESSAGE_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.hasSignatureContentType = function(msg) {
    return msg.hasContentType(OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE);
};

OpenPGPUtils.hasEncryptedContentType = function(msg) {
    return msg.hasContentType(OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE);
};

OpenPGPUtils.hasOPENPGPContentType = function(msg) {
    var has = false;
    OpenPGPUtils.OPENPGP_CONTENT_TYPES.forEach(function(ct) {
        has = msg.hasContentType(ct);
        if (has) {
            return;
        }
    });
    return has;
};

OpenPGPUtils.isOPENPGPContentType = function(cType) {
    return AjxUtil.indexOf(OpenPGPUtils.OPENPGP_CONTENT_TYPES, cType) !== -1;
};

OpenPGPUtils.isSignatureContentType = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.OPENPGP_SIGNATURE_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.isEncryptedContentType = function(cType) {
    return AjxUtil.indexOf([OpenPGPUtils.OPENPGP_ENCRYPTED_CONTENT_TYPE], cType) !== -1;
};

OpenPGPUtils.localStorageSave = function(key, pwd, data) {
    var opts = {
        data: data,
        passwords: [pwd]
    };
    return openpgp.encrypt(opts).then(function(encrypted) {
        localStorage[key] = encrypted.data;
        return encrypted.data;
    });
}

OpenPGPUtils.localStorageRead = function(key, pwd) {
    var sequence = Promise.resolve();
    return sequence.then(function() {
        if (localStorage[key]) {
            var encrypted = localStorage[key];
            var opts = {
                message: openpgp.message.readArmored(encrypted),
                password: pwd
            };
            return openpgp.decrypt(opts).then(function(plaintext) {
                return plaintext.data;
            });
        }
        else {
            return false;
        }
    });
}

OpenPGPUtils.base64Encode = function(bin) {
    if (!window.btoa) {
        for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) {
          var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++);
          if ((a | b | c) > 255) throw new Error("String contains an invalid character");
          base64[base64.length] = OpenPGPUtils.BASE64_TABLE[a >> 2] + OpenPGPUtils.BASE64_TABLE[((a << 4) & 63) | (b >> 4)] +
                                  (isNaN(b) ? "=" : OpenPGPUtils.BASE64_TABLE[((b << 2) & 63) | (c >> 6)]) +
                                  (isNaN(b + c) ? "=" : OpenPGPUtils.BASE64_TABLE[c & 63]);
        }
        return base64.join("");
    }
    else {
        return window.btoa(bin);
    }
};

OpenPGPUtils.base64Decode = function(base64) {
    if (!window.atob) {
        if (/(=[^=]+|={3,})$/.test(base64)) throw new Error("String contains an invalid character");
        base64 = base64.replace(/=/g, "");
        var n = base64.length & 3;
        if (n === 1) throw new Error("String contains an invalid character");
        for (var i = 0, j = 0, len = base64.length / 4, bin = []; i < len; ++i) {
            var a = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A"), b = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A");
            var c = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A"), d = OpenPGPUtils.BASE64_TABLE_STRING.indexOf(base64[j++] || "A");
            if ((a | b | c | d) < 0) throw new Error("String contains an invalid character");
            bin[bin.length] = ((a << 2) | (b >> 4)) & 255;
            bin[bin.length] = ((b << 4) | (c >> 2)) & 255;
            bin[bin.length] = ((c << 6) | d) & 255;
        };
        return String.fromCharCode.apply(null, bin).substr(0, bin.length + n - 4);
    }
    else {
        return window.atob(base64);
    }
};

OpenPGPUtils.hexToBase64 = function(hex) {
    return OpenPGPUtils.base64Encode(String.fromCharCode.apply(null,
        hex.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
    );
};

OpenPGPUtils.hexToBin = function(hex) {
    var bytes = [];

    for(var i = 0; i < hex.length - 1; i += 2) {
        bytes.push(parseInt(hex.substr(i, 2), 16));
    }
    return String.fromCharCode.apply(String, bytes);;
};

OpenPGPUtils.fetchPart = function(part, baseUrl) {
    var url, cType, cDisposition, iType;
    var addTimestamp = true;

    if (part.id) {
        url = [baseUrl, '&id=', part.id].join('');
    } else if (part.mid && part.part) {
        url = [baseUrl, '&id=', part.mid, '&part=', part.part].join('');
    } else if (part.path) {
        url = [baseUrl.replace(/\?.*/,''), part.path].join('');
        addTimestamp = false;
    } else {
        OpenPGPZimbraSecure.popupErrorDialog('attach-convert-error');
    }

    if (url) {
        var rpcReq = new AjxRpcRequest();

        if (addTimestamp) {
            //add a timestamp param so that browser will not cache the request
            url += "&timestamp=" + new Date().getTime();
        }
        var response = rpcReq.invoke('', url,
                                     { 'X-Zimbra-Encoding': 'x-base64' },
                                     null, true);
        cType = rpcReq.__httpReq.getResponseHeader('Content-Type');
        cDisposition = rpcReq.__httpReq.getResponseHeader('Content-Disposition');

        iType = rpcReq.__httpReq.getResponseHeader('X-Zimbra-ItemType');
        if (iType == 'message') {
            //this is a attached message
            cDisposition = 'attachment';
            //now set the file name for this attachment.
            var m =  appCtxt.cacheGet(part.id); //get the attached message from cache
            if (m && m.subject) {
                cDisposition = 'attachment; filename="' + m.subject + '"';
            }
        }
        if (iType == 'document' && !cDisposition) {
            var iName = rpcReq.__httpReq.getResponseHeader('X-Zimbra-ItemName');
            if (iName) {
                cDisposition = 'attachment; filename="' + iName + '"';
            }
        }

        if (response.success) {
            return { data: response.text, ct: cType, cd: cDisposition};
        }
    }
};

OpenPGPUtils.mimeMessageToZmMimePart = function(message, withAttachment) {
    var deep = 0;
    var partIndexes = [];
    withAttachment = withAttachment | false;

    function visitMessage(message) {
        deep++;
        var cd = message.header('Content-Disposition');
        if (!withAttachment && cd === 'attachment') {
            deep--;
            return false;
        }

        var part = {};
        var cType = message.contentType();
        part.ct = cType.fulltype;
        if (partIndexes.length == 0) {
            part.part = 'TEXT';
        }
        else {
            part.part = partIndexes.join('.');
        }
        if (typeof message._body === 'string') {
            var content = '';
            var encode = message.header('Content-Transfer-Encoding');
            if (encode === 'quoted-printable') {
                content = utf8.decode(quotedPrintable.decode(message._body));
            }
            else {
                content = message._body;
            }
            if (part.ct == 'text/html' || part.ct == 'text/plain') {
                part.content = content;
            }
            if (encode === 'base64') {
                part.s = OpenPGPUtils.base64Decode(message._body).length;
            }
            else {
                part.s = content.length;
            }
        }
        if (cType.params.name) {
            part.filename = cType.params.name;
        }
        if (cd) {
            part.cd = cd;
        }
        if (Array.isArray(message._body)) {
            part.mp = [];
            message._body.forEach(function(entity, index) {
                partIndexes[deep - 1] = index + 1;
                var mp = visitMessage(entity);
                if (mp) {
                    part.mp.push(mp);
                }
            });
        }
        deep--;
        partIndexes.pop();
        return part;
    }

    function findBody(cType, part) {
        var bodyFound = false;
        if (part.mp) {
            bodyFound = findBody(cType, part.mp);
        } else if (part.ct) {
            if (part.ct == cType) {
                part.body = bodyFound = true;
            } else if (part.cd == 'inline') {
                part.body = true;
            }
        } else {
            for (var i = 0; !bodyFound && i < part.length; i++) {
                bodyFound = findBody(cType, part[i]);
            }
        }
        return bodyFound;
    }

    var msg = visitMessage(message);
    if (!findBody('text/html', msg))
        findBody('text/plain', msg);

    return msg;
};

OpenPGPUtils.getDefaultSenderAddress = function() {
    var account = (appCtxt.accountList.defaultAccount ||
                   appCtxt.accountList.activeAccount ||
                   appCtxt.accountList.mainAccount);
    var identity = appCtxt.getIdentityCollection(account).defaultIdentity;

    return new AjxEmailAddress(identity.sendFromAddress, AjxEmailAddress.FROM, identity.sendFromDisplay);
};

OpenPGPUtils.prop = function(key) {
    return openpgp_zimbra_secure[key];
};

/*
 * random-string
 * https://github.com/valiton/node-random-string
 *
 * Copyright (c) 2013 Valiton GmbH, Bastian 'hereandnow' Behrens
 * Licensed under the MIT license.
 */
OpenPGPUtils.randomString = function(opts) {
    var numbers = '0123456789',
        letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        specials = '!$%^&*()_+|~-=`{}[]:;<>?,./';

    function _defaults(opts) {
        opts || (opts = {});
        return {
            length: opts.length || 32,
            numeric: typeof opts.numeric === 'boolean' ? opts.numeric : true,
            letters: typeof opts.letters === 'boolean' ? opts.letters : true,
            special: typeof opts.special === 'boolean' ? opts.special : false
        };
    }

    function _buildChars(opts) {
        var chars = '';
        if (opts.numeric) { chars += numbers; }
        if (opts.letters) { chars += letters; }
        if (opts.special) { chars += specials; }
        return chars;
    }

    opts = _defaults(opts);
    var i, rn,
        rnd = '',
        len = opts.length,
        randomChars = _buildChars(opts);
    for (i = 1; i <= len; i++) {
        rnd += randomChars.substring(rn = Math.floor(Math.random() * randomChars.length), rn + 1);
    }
    return rnd;
};
<|endoftext|>


<commit_before>/* global window */

/**
 * Axis utils
 * @module
 */
import {
  every,
  find,
  get,
  isEmpty,
  map,
  mapValues,
  mergeWith,
  reduce,
  set as setValue
} from 'lodash';

import {
  takeSkipping
} from './array';

import {
  getRenderedStringWidth,
  sizeOfLongestRotatedString,
} from './strings';

export const DEFAULT_AXIS_PROPERTIES = {
  axisLabelFontSize: 11,
  height: 100,
  tickHeight: 10,
  tickLabelFontFamily: 'Helvetica',
  tickLabelFontSize: 10,
  tickLabelFormat: null,
  width: 230,
};

export const ALLOWED_SCALE_TYPES_FOR_AUTOFORMAT = ['point', 'ordinal', 'band'];

const AXIS_ORIENTATION_OPTIONS = ['top', 'right', 'bottom', 'left'];

const ADDITIONAL_LABEL_PADDING = '20px';

const TICK_LABEL_ROTATION_ANGLE = -45;

/**
 * Determine if auto-formatting can be applied based on d3 scale type(s).
 * Returns true if at least one scale passed in can be auto-formatted.
 * @param {...string} scaleTypes - d3 scale type(s)
 * @returns {Boolean}
 */
export function canAutoFormatAxes(...scaleTypes) {
  return scaleTypes.some(scaleType => ALLOWED_SCALE_TYPES_FOR_AUTOFORMAT.includes(scaleType));
}

/**
 * Calculate translation based on axis orientation.
 * @param {string} orientation - Orientation of the axis. One of ['top', 'bottom', 'left', 'right']
 * @param {number} width - Width of axis container.
 * @param {number} height - Height of axis container.
 * @returns {{x: number, y: number}}
 */
export function calcAxisTranslate(orientation, width = 0, height = 0) {
  if (orientation === 'bottom') {
    return {
      x: 0,
      y: height,
    };
  } else if (orientation === 'right') {
    return {
      x: width,
      y: 0,
    };
  }
  return {
    x: 0,
    y: 0,
  };
}

/**
 * Calculate label position and rotation based on orientation.
 * @param {string} orientation - Orientation of the axis. One of ['top', 'bottom', 'left', 'right']
 * @param {{x: number, y: number}} translate - Supplied translate for the axis.
 * @param {{top: number, bottom: number, left: number, right: number}} padding - Supplied padding for the axis.
 * @param {number} center - Calculated center of the axis.
 * @returns {{x: number, y: number, dX: number, dY: number, rotate: number }} Position and rotation of label. Shape: { x, y, dx, dy, rotate }
 */
export function calcLabelPosition(orientation, translate, padding, center) {
  switch (orientation) {
    case 'top':
      return {
        x: translate.x,
        y: translate.y - padding.top,
        dX: center,
        dY: '1em',
      };
    case 'bottom':
      return {
        x: translate.x,
        y: translate.y + padding.bottom,
        dX: center,
        dY: '-0.2em',
      };
    case 'left':
      return {
        x: translate.y,
        y: translate.x - padding.left,
        dX: -center,
        dY: '1em',
        rotate: 270,
      };
    case 'right':
      return {
        x: translate.y,
        y: -(translate.x + padding.right),
        dX: center,
        dY: '1em',
        rotate: 90,
      };
    default:
      return {
        x: translate.x,
        y: translate.y,
        dX: 0,
        dY: 0,
      };
  }
}

/**
 * Calculate the widest string length from supplied array of strings.
 * @param {Array} ticks - Array of tick values.
 * @param {Object} styles - Object consisting of axis container width and font styles.
 * @param {Number} styles.tickFontSize - Supplied tick font size.
 * @param {String} styles.tickFontFamily - Supplied tick font family.
 * @param {Function} styles.tickFormat - Supplied callback for tick string formatting.
 * @returns {Number} Number of ticks that fit (without overlap) into available width.
 */
function calcLengthOfLongestTickLabel(ticks, {
  tickLabelFontSize,
  tickLabelFontFamily,
  tickLabelFormat,
}) {
  /* eslint-disable max-len */
  return reduce(map(ticks, tickLabelFormat), (widest, tick) =>
    Math.max(
      widest,
      getRenderedStringWidth(String(tick), `${tickLabelFontSize}px ${tickLabelFontFamily}`),
    )
  , 0);
}

/**
 * Calculate the number of ticks that fit into a specified width based on text styles.
 * @param {Number} widestTickLabelLength - Length of longest tick value.
 * @param {Number} availableWidth - Supplied width of axis container.
 * @returns {Number} Number of ticks that fit (without overlap) into available width.
 */
function calcNumTicksThatFit(widestTickLabelLength, availableWidth) {
  return Math.floor(availableWidth / widestTickLabelLength);
}

/**
 * Evenly filter tick values based on available width (for horizontal axis/axes).
 * @param {Array} ticks - Array of tick values.
 * @param {Object} axisProperties - Object consisting of axis container width and font styles.
 * @returns {Array} Tick values evenly filtered based on available width.
 */
export function filterTickValuesByWidth(ticks, axisProperties) {
  const widestTickLabelLength = calcLengthOfLongestTickLabel(ticks, axisProperties);
  const numTicksThatFit = calcNumTicksThatFit(widestTickLabelLength, get(axisProperties, 'width'));
  return takeSkipping(ticks, numTicksThatFit);
}

/**
 * Evenly filter tick values based on available height (for vertical axis/axes).
 * @param {Array} ticks - Array of tick values.
 * @param {Object} styles - Object consisting of axis container height and font styles.
 * @param {Number} styles.height - Supplied height of axis container.
 * @param {Number} styles.tickFontSize - Supplied tick font size.
 * @returns {Array} Tick values evenly filtered based on available height.
 */
export function filterTickValuesByHeight(ticks, { height, tickLabelFontSize }) {
  const numTicksThatFit = Math.floor(height / tickLabelFontSize);
  return takeSkipping(ticks, numTicksThatFit);
}

/**
 * Given an array of React children components, find the axis components that meet the specified conditions.
 * @param {Array} children - Opaque data structure as a flat array with keys assigned to each child.
 * @param {String} conditions - String that maps to valid condition for which to verify.
 * @returns {Object} React axis components keyed by their respective orientation (i.e., top, right, etc.)
 */
function findAxisComponentsByCondition(children, conditions) {
  return reduce(
    AXIS_ORIENTATION_OPTIONS,
    (result, axisOrientation) => {
      const validConditions = {
        orientationExists: (child) => child.props.orientation === axisOrientation,
        labelExists: (child) => !isEmpty(child.props.label)
      };
      return setValue(
        result,
        axisOrientation,
        find(children,
          (child) => every(
            conditions,
            (condition) => validConditions[condition](child),
          )
        )
      );
    }, {});
}

/**
 * Apply formatting, if any, to tick values for each axis.
 * @param {Object} axes - React axis components keyed by their respective orientation (i.e., top, right, etc.)
 * @param {Array} xDomain - domain of the x-axis data
 * @param {Array} yDomain - domain of the y-axis data
 * @returns {Object|null} Formatted tick values for each axis orientation, or null if axis doesn't exist.
 */
function getFormattedTickValues(axes, xDomain, yDomain) {
  return mapValues(
    axes,
    (axis) => {
      if (axis) {
        const orientation = get(axis, ['props', 'orientation']);
        const userSpecifiedTickValues = get(axis, ['props', 'tickValues']);
        const tickValues = userSpecifiedTickValues ||
          (orientation === 'top' || orientation === 'bottom'
            ? xDomain
            : yDomain);
        const tickFormat = get(axis, ['props', 'tickFormat']);
        const formattedTicks = (tickFormat && tickValues.map(tick => tickFormat(tick))) || tickValues;
        return formattedTicks;
      // eslint-disable-next-line no-else-return
      } else {
        return null;
      }
    }
  );
}

/**
 * Calculate the amount of total padding needed and if tick values require rotation to fit into the available width.
 * @param {Object} - Variables on which padding is dependent.
 * @returns {[Object, Boolean]} Resultant padding and boolean indicating whether tick rotation is necessary.
 */
function calcPaddingFromTicks({
  xDomain,
  xScaleType,
  yDomain,
  yScaleType,
  width,
  style,
  children
}) {
  // Find each oriented axis component, if any, from React children.
  const axes = findAxisComponentsByCondition(children, ['orientationExists']);
  const {
    top: topAxis,
    right: rightAxis,
    bottom: bottomAxis,
    left: leftAxis } = axes;

  // Get the formatted tick values for each axis (using the domain as the tick values).
  const {
    top: topAxisTickValues,
    right: rightAxisTickValues,
    bottom: bottomAxisTickValues,
    left: leftAxisTickValues } = getFormattedTickValues(axes, xDomain, yDomain);

  // Determine if auto-formatting is possible based on the d3 scale type.
  const canAutoFormatXAxis = canAutoFormatAxes(xScaleType);
  const canAutoFormatYAxis = canAutoFormatAxes(yScaleType);

  // Determine axis style properties.
  const tickLabelFontSize = get(style, 'fontSize', DEFAULT_AXIS_PROPERTIES.tickLabelFontSize);
  const tickFontFamily = get(style, 'fontFamily', DEFAULT_AXIS_PROPERTIES.tickLabelFontFamily);
  const axisProperties = { ...DEFAULT_AXIS_PROPERTIES, tickLabelFontSize, tickFontFamily };

  // Determine if x-axis tick labels require rotation (not needed for y-axis since overlap is not a concern)
  // If both top & bottom axes exist and one requires rotation, then rotate both.
  const widestTopAxisTickLabelLength = topAxis
    ? calcLengthOfLongestTickLabel(
      topAxisTickValues,
      DEFAULT_AXIS_PROPERTIES
    )
    : 0;
  const widestBottomAxisTickLabelLength = bottomAxis
    ? calcLengthOfLongestTickLabel(
      bottomAxisTickValues,
      DEFAULT_AXIS_PROPERTIES
    )
    : 0;
  const numTicksThatFitOnTopAxis = calcNumTicksThatFit(widestTopAxisTickLabelLength, width);
  const numTicksThatFitOnBottomAxis = calcNumTicksThatFit(widestBottomAxisTickLabelLength, width);

  // TOP/BOTTOM: If ticks overlap, calculate the rotated length (i.e., at -45 deg.), but also subtract tick height.
  // RIGHT/LEFT: Ticks cannot overlap. Simply calculate the length of the string (i.e., at 90 deg.)
  let autoRotate;
  let padding;
  if (numTicksThatFitOnTopAxis < topAxisTickValues.length || numTicksThatFitOnBottomAxis < bottomAxisTickValues.length) {
    autoRotate = true;
    padding = {
      top: (topAxis && canAutoFormatXAxis)
        ? sizeOfLongestRotatedString(topAxisTickValues, tickLabelFontSize, TICK_LABEL_ROTATION_ANGLE) - tickLabelFontSize
        : 0,
      right: (rightAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(rightAxisTickValues, axisProperties)
        : 0,
      bottom: (bottomAxis && canAutoFormatXAxis)
        ? sizeOfLongestRotatedString(bottomAxisTickValues, tickLabelFontSize, TICK_LABEL_ROTATION_ANGLE) - tickLabelFontSize
        : 0,
      left: (leftAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(leftAxisTickValues, axisProperties)
        : 0,
    };
  } else {
    autoRotate = false;
    padding = {
      top: (topAxis && canAutoFormatXAxis)
        ? tickLabelFontSize
        : 0,
      right: (rightAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(rightAxisTickValues, axisProperties)
        : 0,
      bottom: (bottomAxis && canAutoFormatXAxis)
        ? tickLabelFontSize
        : 0,
      left: (leftAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(leftAxisTickValues, axisProperties)
        : 0,
    };
  }
  return [padding, autoRotate];
}

/**
 * Given an axis component w/ label, determine the padding needed for the label (based on font height and additional buffer).
 * @param {Array} orientedAxisWithLabel - Opaque data structure as a flat array with keys assigned to each child.
 * @returns {{top: Number, right: Number, bottom: Number, left: Number}} Padding offset from label.
 */
function getLabelPadding(orientedAxisWithLabel) {
  return parseFloat((get(
    orientedAxisWithLabel,
    ['props', 'style', 'fontSize'],
    `${DEFAULT_AXIS_PROPERTIES.axisLabelFontSize}px`
  )))
  + parseFloat(ADDITIONAL_LABEL_PADDING);
}

/**
 * Given an array of React children components, find the axes components (if exist) and calculate the padding required for each label (if exists).
 * @param {Array} children - Opaque data structure as a flat array with keys assigned to each child.
 * @returns {{top: Number, right: Number, bottom: Number, left: Number}} Padding offsets for each label.
 */
function calcPaddingFromLabel(children) {
  const {
    top: topAxis,
    right: rightAxis,
    bottom: bottomAxis,
    left: leftAxis
  } = findAxisComponentsByCondition(children, ['orientationExists', 'labelExists']);

  return {
    top: (topAxis ? getLabelPadding(topAxis) : 0),
    right: (rightAxis ? getLabelPadding(rightAxis) : 0),
    bottom: (bottomAxis ? getLabelPadding(bottomAxis) : 0),
    left: (leftAxis ? getLabelPadding(leftAxis) : 0),
  };
}

/**
 * Combine two paddings according to a customizer.
 * @param {Array} paddings - Array of padding objects.
 * @param {Function} customizer - Function by which to customize assigned values.
 * @returns {{top: Number, right: Number, bottom: Number, left: Number}} Resultant padding.
 */
function mergePaddingsBy(paddings, customizer) {
  return reduce(
    paddings,
    (resultantPadding, currentPadding) => mergeWith(
      resultantPadding,
      currentPadding,
      customizer
    ), {});
}

/**
 * Calculate total padding and determine if rotation is needed.
 * @param {Object} - Variables on which padding is dependent.
 * @returns {[Object, Boolean]} Resultant padding and boolean indicating whether tick rotation is necessary.
 */
export function calcPadding({ children, xDomain, xScaleType, yDomain, yScaleType, width, style, initialPadding }) {
  const paddingFromLabel = calcPaddingFromLabel(children);
  const [paddingFromTicks, autoRotateTickLabels] = calcPaddingFromTicks({
    xDomain,
    xScaleType,
    yDomain,
    yScaleType,
    width,
    style,
    children,
  });
  // Add padding needed for labels to padding needed for ticks.
  const summedPaddings = mergePaddingsBy(
    [paddingFromLabel, paddingFromTicks],
    (originalProperty = 0, revisedProperty = 0) => originalProperty + revisedProperty
  );
  // Take largest padding property value from summed paddings (above) and user-specified padding
  // (i.e., still allow the ability to pad the chart more than what is auto-calculated).
  const mergedPaddings = mergePaddingsBy(
    [initialPadding, summedPaddings],
    (originalProperty = 0, revisedProperty = 0) => Math.max(originalProperty, revisedProperty)
  );

  return [mergedPaddings, autoRotateTickLabels];
}

/**
 * Calculate width and height of chart portion of SVG by subtracting out the padding from each side.
 * @param {Number} width - SVG Width.
 * @param {Number} height - SVG Height.
 * @param {{top: Number, right: Number, bottom: Number, left: Number}} padding - Chart padding accounting for axes labels and tick formatting.
 * @returns {{width: Number, height: Number}} Chart width and height offset by padding.
 */
export function calcChartDimensions(width, height, padding) {
  return {
    width: width - (padding.left + padding.right),
    height: height - (padding.top + padding.bottom),
  };
}
<commit_msg>Fix formatting. Rename a few things. Improve documentation.<commit_after>/* global window */

/**
 * Axis utils
 * @module
 */
import {
  every,
  find,
  get,
  isEmpty,
  map,
  mapValues,
  mergeWith,
  reduce,
  set as setValue
} from 'lodash';

import {
  takeSkipping
} from './array';

import {
  getRenderedStringWidth,
  sizeOfLongestRotatedString,
} from './strings';

export const DEFAULT_AXIS_PROPERTIES = {
  axisLabelFontSize: 11,
  height: 100,
  tickHeight: 10,
  tickLabelFontFamily: 'Helvetica',
  tickLabelFontSize: 10,
  tickLabelFormat: null,
  width: 230,
};

export const ALLOWED_SCALE_TYPES_FOR_AUTOFORMAT = ['point', 'ordinal', 'band'];

const AXIS_ORIENTATION_OPTIONS = ['top', 'right', 'bottom', 'left'];

const ADDITIONAL_LABEL_PADDING = '20px';

const TICK_LABEL_ROTATION_ANGLE = -45;

/**
 * Determine if auto-formatting can be applied based on d3 scale type(s).
 * Returns true if at least one scale passed in can be auto-formatted.
 * @param {...string} scaleTypes - d3 scale type(s)
 * @returns {Boolean}
 */
export function canAutoFormatAxes(...scaleTypes) {
  return scaleTypes.some(scaleType => ALLOWED_SCALE_TYPES_FOR_AUTOFORMAT.includes(scaleType));
}

/**
 * Calculate translation based on axis orientation.
 * @param {string} orientation - Orientation of the axis. One of ['top', 'bottom', 'left', 'right']
 * @param {number} width - Width of axis container.
 * @param {number} height - Height of axis container.
 * @returns {{x: number, y: number}}
 */
export function calcAxisTranslate(orientation, width = 0, height = 0) {
  if (orientation === 'bottom') {
    return {
      x: 0,
      y: height,
    };
  } else if (orientation === 'right') {
    return {
      x: width,
      y: 0,
    };
  }
  return {
    x: 0,
    y: 0,
  };
}

/**
 * Calculate label position and rotation based on orientation.
 * @param {string} orientation - Orientation of the axis. One of ['top', 'bottom', 'left', 'right']
 * @param {{x: number, y: number}} translate - Supplied translate for the axis.
 * @param {{top: number, bottom: number, left: number, right: number}} padding - Supplied padding for the axis.
 * @param {number} center - Calculated center of the axis.
 * @returns {{x: number, y: number, dX: number, dY: number, rotate: number }} Position and rotation of label. Shape: { x, y, dx, dy, rotate }
 */
export function calcLabelPosition(orientation, translate, padding, center) {
  switch (orientation) {
    case 'top':
      return {
        x: translate.x,
        y: translate.y - padding.top,
        dX: center,
        dY: '1em',
      };
    case 'bottom':
      return {
        x: translate.x,
        y: translate.y + padding.bottom,
        dX: center,
        dY: '-0.2em',
      };
    case 'left':
      return {
        x: translate.y,
        y: translate.x - padding.left,
        dX: -center,
        dY: '1em',
        rotate: 270,
      };
    case 'right':
      return {
        x: translate.y,
        y: -(translate.x + padding.right),
        dX: center,
        dY: '1em',
        rotate: 90,
      };
    default:
      return {
        x: translate.x,
        y: translate.y,
        dX: 0,
        dY: 0,
      };
  }
}

/**
 * Calculate the widest string length from supplied array of strings.
 * @param {Array} ticks - Array of tick values.
 * @param {Object} styles - Object consisting of axis container width and font styles.
 * @param {Number} styles.tickFontSize - Supplied tick font size.
 * @param {String} styles.tickFontFamily - Supplied tick font family.
 * @param {Function} styles.tickFormat - Supplied callback for tick string formatting.
 * @returns {Number} Number of ticks that fit (without overlap) into available width.
 */
function calcLengthOfLongestTickLabel(ticks, {
  tickLabelFontSize,
  tickLabelFontFamily,
  tickLabelFormat,
}) {
  /* eslint-disable max-len */
  return reduce(map(ticks, tickLabelFormat), (widest, tick) =>
    Math.max(
      widest,
      getRenderedStringWidth(String(tick), `${tickLabelFontSize}px ${tickLabelFontFamily}`),
    )
  , 0);
}

/**
 * Calculate the number of ticks that fit into a specified width based on text styles.
 * @param {Number} widestTickLabelLength - Length of longest tick value.
 * @param {Number} availableWidth - Supplied width of axis container.
 * @returns {Number} Number of ticks that fit (without overlap) into available width.
 */
function calcNumTicksThatFit(widestTickLabelLength, availableWidth) {
  return Math.floor(availableWidth / widestTickLabelLength);
}

/**
 * Evenly filter tick values based on available width (for horizontal axis/axes).
 * @param {Array} ticks - Array of tick values.
 * @param {Object} axisProperties - Object consisting of axis container width and font styles.
 * @returns {Array} Tick values evenly filtered based on available width.
 */
export function filterTickValuesByWidth(ticks, axisProperties) {
  const widestTickLabelLength = calcLengthOfLongestTickLabel(ticks, axisProperties);
  const numTicksThatFit = calcNumTicksThatFit(widestTickLabelLength, get(axisProperties, 'width'));
  return takeSkipping(ticks, numTicksThatFit);
}

/**
 * Evenly filter tick values based on available height (for vertical axis/axes).
 * @param {Array} ticks - Array of tick values.
 * @param {Object} styles - Object consisting of axis container height and font styles.
 * @param {Number} styles.height - Supplied height of axis container.
 * @param {Number} styles.tickFontSize - Supplied tick font size.
 * @returns {Array} Tick values evenly filtered based on available height.
 */
export function filterTickValuesByHeight(ticks, { height, tickLabelFontSize }) {
  const numTicksThatFit = Math.floor(height / tickLabelFontSize);
  return takeSkipping(ticks, numTicksThatFit);
}

/**
 * Given an array of React children components, find the axis components that meet the specified conditions.
 * @param {Array} children - Opaque data structure as a flat array with keys assigned to each child.
 * @param {String} conditions - String that maps to valid condition for which to verify.
 * @returns {Object} React axis components keyed by their respective orientation (i.e., top, right, etc.)
 */
function findAxisComponentsByCondition(children, conditions) {
  return reduce(
    AXIS_ORIENTATION_OPTIONS,
    (result, axisOrientation) => {
      const validConditions = {
        orientationExists: (child) => child.props.orientation === axisOrientation,
        labelExists: (child) => !isEmpty(child.props.label)
      };
      return setValue(
        result,
        axisOrientation,
        find(children,
          (child) => every(
            conditions,
            (condition) => validConditions[condition](child),
          )
        )
      );
    }, {});
}

/**
 * Apply formatting, if any, to tick values for each axis.
 * @param {Object} axes - React axis components keyed by their respective orientation (i.e., top, right, etc.)
 * @param {Array} xDomain - domain of the x-axis data
 * @param {Array} yDomain - domain of the y-axis data
 * @returns {Object} Formatted tick values for each axis orientation,
 * or null for respective orientation if axis does not exist.
 */
function getFormattedTickValues(axes, xDomain, yDomain) {
  return mapValues(
    axes,
    (axis) => {
      if (axis) {
        const orientation = get(axis, ['props', 'orientation']);
        const userSpecifiedTickValues = get(axis, ['props', 'tickValues']);
        const tickValues = userSpecifiedTickValues ||
          (orientation === 'top' || orientation === 'bottom'
            ? xDomain
            : yDomain);
        const tickFormat = get(axis, ['props', 'tickFormat']);
        const formattedTicks = (tickFormat && tickValues.map(tick => tickFormat(tick))) || tickValues;
        return formattedTicks;
      // eslint-disable-next-line no-else-return
      } else {
        return null;
      }
    }
  );
}

/**
 * Calculate the amount of total padding needed and if tick values require rotation to fit into the available width.
 * @param {Object} - Variables on which padding is dependent.
 * @returns {[Object, Boolean]} Resultant padding and boolean indicating whether tick rotation is necessary.
 */
function calcPaddingFromTicks({
  xDomain,
  xScaleType,
  yDomain,
  yScaleType,
  width,
  style,
  children
}) {
  // Find each oriented axis component, if any, from React children.
  const axes = findAxisComponentsByCondition(children, ['orientationExists']);
  const {
    top: topAxis,
    right: rightAxis,
    bottom: bottomAxis,
    left: leftAxis } = axes;

  // Get the formatted tick values for each axis (using the domain as the tick values).
  const {
    top: topAxisTickValues,
    right: rightAxisTickValues,
    bottom: bottomAxisTickValues,
    left: leftAxisTickValues } = getFormattedTickValues(axes, xDomain, yDomain);

  // Determine if auto-formatting is possible based on the d3 scale type.
  const canAutoFormatXAxis = canAutoFormatAxes(xScaleType);
  const canAutoFormatYAxis = canAutoFormatAxes(yScaleType);

  // Determine axis style properties.
  const tickLabelFontSize = get(style, 'fontSize', DEFAULT_AXIS_PROPERTIES.tickLabelFontSize);
  const tickFontFamily = get(style, 'fontFamily', DEFAULT_AXIS_PROPERTIES.tickLabelFontFamily);
  const axisProperties = { ...DEFAULT_AXIS_PROPERTIES, tickLabelFontSize, tickFontFamily };

  // Determine if x-axis tick labels require rotation (not needed for y-axis since overlap is not a concern)
  // If both top & bottom axes exist and one requires rotation, then rotate both.
  const widestTopAxisTickLabelLength = topAxis
    ? calcLengthOfLongestTickLabel(
      topAxisTickValues,
      DEFAULT_AXIS_PROPERTIES
    )
    : 0;
  const widestBottomAxisTickLabelLength = bottomAxis
    ? calcLengthOfLongestTickLabel(
      bottomAxisTickValues,
      DEFAULT_AXIS_PROPERTIES
    )
    : 0;
  const numTicksThatFitOnTopAxis = calcNumTicksThatFit(widestTopAxisTickLabelLength, width);
  const numTicksThatFitOnBottomAxis = calcNumTicksThatFit(widestBottomAxisTickLabelLength, width);

  // TOP/BOTTOM: If ticks overlap, calculate the rotated length (i.e., at -45 deg.), but also subtract tick height.
  // RIGHT/LEFT: Ticks cannot overlap. Simply calculate the length of the string (i.e., at 90 deg.)
  let autoRotate;
  let padding;
  if (numTicksThatFitOnTopAxis < topAxisTickValues.length
    || numTicksThatFitOnBottomAxis < bottomAxisTickValues.length
  ) {
    autoRotate = true;
    padding = {
      top: (topAxis && canAutoFormatXAxis)
        ? sizeOfLongestRotatedString(topAxisTickValues, tickLabelFontSize, TICK_LABEL_ROTATION_ANGLE) - tickLabelFontSize
        : 0,
      right: (rightAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(rightAxisTickValues, axisProperties)
        : 0,
      bottom: (bottomAxis && canAutoFormatXAxis)
        ? sizeOfLongestRotatedString(bottomAxisTickValues, tickLabelFontSize, TICK_LABEL_ROTATION_ANGLE) - tickLabelFontSize
        : 0,
      left: (leftAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(leftAxisTickValues, axisProperties)
        : 0,
    };
  } else {
    autoRotate = false;
    padding = {
      top: (topAxis && canAutoFormatXAxis)
        ? tickLabelFontSize
        : 0,
      right: (rightAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(rightAxisTickValues, axisProperties)
        : 0,
      bottom: (bottomAxis && canAutoFormatXAxis)
        ? tickLabelFontSize
        : 0,
      left: (leftAxis && canAutoFormatYAxis)
        ? calcLengthOfLongestTickLabel(leftAxisTickValues, axisProperties)
        : 0,
    };
  }
  return [padding, autoRotate];
}

/**
 * Given an axis component w/ label, determine the padding needed for the label (based on font height and additional buffer).
 * @param {Array} orientedAxisWithLabel - Opaque data structure as a flat array with keys assigned to each child.
 * @returns {{top: Number, right: Number, bottom: Number, left: Number}} Padding offset from label.
 */
function getLabelPadding(orientedAxisWithLabel) {
  return parseFloat((get(
    orientedAxisWithLabel,
    ['props', 'style', 'fontSize'],
    `${DEFAULT_AXIS_PROPERTIES.axisLabelFontSize}px`
  )))
  + parseFloat(ADDITIONAL_LABEL_PADDING);
}

/**
 * Given an array of React children components, find the axes components (if exist) and calculate the padding required for each label (if exists).
 * @param {Array} children - Opaque data structure as a flat array with keys assigned to each child.
 * @returns {{top: Number, right: Number, bottom: Number, left: Number}} Padding offsets for each label.
 */
function calcPaddingFromLabel(children) {
  const {
    top: topAxis,
    right: rightAxis,
    bottom: bottomAxis,
    left: leftAxis
  } = findAxisComponentsByCondition(children, ['orientationExists', 'labelExists']);

  return {
    top: (topAxis ? getLabelPadding(topAxis) : 0),
    right: (rightAxis ? getLabelPadding(rightAxis) : 0),
    bottom: (bottomAxis ? getLabelPadding(bottomAxis) : 0),
    left: (leftAxis ? getLabelPadding(leftAxis) : 0),
  };
}

/**
 * Combine two paddings according to a customizer.
 * @param {Array} paddings - Array of padding objects.
 * @param {Function} customizer - Function by which to customize assigned values.
 * @returns {{top: Number, right: Number, bottom: Number, left: Number}} Resultant padding.
 */
function mergePaddingsBy(paddings, customizer) {
  return reduce(
    paddings,
    (resultantPadding, currentPadding) => mergeWith(
      resultantPadding,
      currentPadding,
      customizer
    ), {});
}

/**
 * Calculate total padding and determine if rotation is needed.
 * @param {Object} - Variables on which padding is dependent.
 * @returns {[Object, Boolean]} Resultant padding and boolean indicating whether tick rotation is necessary.
 */
export function calcPadding({ children, xDomain, xScaleType, yDomain, yScaleType, width, style, initialPadding: initial }) {
  const labelPadding = calcPaddingFromLabel(children);
  const [tickPadding, autoRotateTickLabels] = calcPaddingFromTicks({
    xDomain,
    xScaleType,
    yDomain,
    yScaleType,
    width,
    style,
    children,
  });
  // Add padding needed for labels to padding needed for ticks.
  const summed = mergePaddingsBy(
    [labelPadding, tickPadding],
    (originalProperty = 0, revisedProperty = 0) => originalProperty + revisedProperty
  );
  // Take largest padding property value from summed paddings (above) and user-specified padding
  // (i.e., still allow the ability to pad the chart more than what is auto-calculated).
  const mergedPaddings = mergePaddingsBy(
    [initial, summed],
    (originalProperty = 0, revisedProperty = 0) => Math.max(originalProperty, revisedProperty)
  );

  return [mergedPaddings, autoRotateTickLabels];
}

/**
 * Calculate width and height of chart portion of SVG by subtracting out the padding from each side.
 * @param {Number} width - SVG Width.
 * @param {Number} height - SVG Height.
 * @param {{top: Number, right: Number, bottom: Number, left: Number}} padding - Chart padding accounting for axes labels and tick formatting.
 * @returns {{width: Number, height: Number}} Chart width and height offset by padding.
 */
export function calcChartDimensions(width, height, padding) {
  return {
    width: width - (padding.left + padding.right),
    height: height - (padding.top + padding.bottom),
  };
}
<|endoftext|>


<commit_before>/**
 * @file ListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import TouchRipple from '../TouchRipple';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

export default class ListItem extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: props.checked
        };

        this.checkboxChangeHandler = ::this.checkboxChangeHandler;
        this.radioChangeHandler = ::this.radioChangeHandler;
        this.touchTapHandler = ::this.touchTapHandler;

    }

    checkboxChangeHandler(checked) {
        this.setState({
            checked
        }, () => {

            const {onSelect, onDeselect} = this.props;

            if (checked) {
                onSelect && onSelect();
            } else {
                onDeselect && onDeselect();
            }

        });
    }

    radioChangeHandler() {

        const {checked} = this.state;

        if (!checked) {
            this.setState({
                checked: true
            }, () => {
                const {onSelect} = this.props;
                onSelect && onSelect();
            });
        }

    }

    touchTapHandler(e) {

        e.preventDefault();

        const {disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onTouchTap} = this.props;
        onTouchTap && onTouchTap(e);

        const {selectMode} = this.props;

        switch (selectMode) {
            case SelectMode.MULTI_SELECT:
                this.checkboxChangeHandler(!this.state.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.radioChangeHandler();
                return;
        }

    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: nextProps.checked
            });
        }
    }

    render() {

        const {

                index, className, style, theme, data, text, desc, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, disableTouchRipple, rippleDisplayCenter, renderer, itemRenderer, readOnly,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave

            } = this.props,
            {checked} = this.state,

            listItemClassName = (theme ? ` theme-${theme}` : '') + (checked ? ' activated' : '')
                + (className ? ' ' + className : ''),
            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT ?
                            <Radio className="list-item-checked"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="list-item-checkbox"
                                      theme={selectTheme}
                                      checked={checked}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        disableTouchRipple || readOnly ?
                            null
                            :
                            <TouchRipple ref="touchRipple"
                                         className={disabled || isLoading ? 'hidden' : ''}
                                         displayCenter={rippleDisplayCenter}/>
                    }

            </TipProvider>
        );

    }
};

ListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.any,
    desc: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    disableTouchRipple: PropTypes.bool,
    rippleDisplayCenter: PropTypes.bool,
    checked: PropTypes.bool,
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.string,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    onTouchTap: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

ListItem.defaultProps = {

    index: 0,

    className: null,
    style: null,
    theme: null,
    selectTheme: null,
    selectMode: SelectMode.NORMAL,

    data: null,
    value: null,
    text: null,
    desc: null,

    disabled: false,
    isLoading: false,
    disableTouchRipple: false,
    rippleDisplayCenter: false,
    checked: false,
    readOnly: false,

    iconCls: null,
    rightIconCls: null,

    tip: null,
    tipPosition: Position.BOTTOM,

    radioUncheckedIconCls: 'fa fa-check',
    radioCheckedIconCls: 'fa fa-check',
    checkboxUncheckedIconCls: 'fa fa-square-o',
    checkboxCheckedIconCls: 'fa fa-check-square',
    checkboxIndeterminateIconCls: 'fa fa-minus-square'

};<commit_msg>optimize ListItem<commit_after>/**
 * @file ListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import TouchRipple from '../TouchRipple';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

export default class ListItem extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: props.checked
        };

        this.checkboxChangeHandler = ::this.checkboxChangeHandler;
        this.radioChangeHandler = ::this.radioChangeHandler;
        this.touchTapHandler = ::this.touchTapHandler;

    }

    checkboxChangeHandler(checked) {
        this.setState({
            checked
        }, () => {

            const {onSelect, onDeselect} = this.props;

            if (checked) {
                onSelect && onSelect();
            } else {
                onDeselect && onDeselect();
            }

        });
    }

    radioChangeHandler() {

        const {checked} = this.state;

        if (!checked) {
            this.setState({
                checked: true
            }, () => {
                const {onSelect} = this.props;
                onSelect && onSelect();
            });
        }

    }

    touchTapHandler(e) {

        e.preventDefault();

        const {disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onTouchTap} = this.props;
        onTouchTap && onTouchTap(e);

        const {selectMode} = this.props;

        switch (selectMode) {
            case SelectMode.MULTI_SELECT:
                this.checkboxChangeHandler(!this.state.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.radioChangeHandler();
                return;
        }

    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: nextProps.checked
            });
        }
    }

    render() {

        const {

                index, className, style, theme, data, text, desc, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, disableTouchRipple, rippleDisplayCenter, renderer, itemRenderer, readOnly,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave

            } = this.props,
            {checked} = this.state,

            listItemClassName = (theme ? ` theme-${theme}` : '') + (checked ? ' activated' : '')
                + (className ? ' ' + className : ''),
            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT ?
                            <Radio className="list-item-checked"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="list-item-checkbox"
                                      theme={selectTheme}
                                      checked={checked}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        disableTouchRipple || readOnly ?
                            null
                            :
                            <TouchRipple ref="touchRipple"
                                         className={disabled || isLoading ? 'hidden' : ''}
                                         displayCenter={rippleDisplayCenter}/>
                    }

            </TipProvider>
        );

    }
};

ListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.any,
    desc: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    disableTouchRipple: PropTypes.bool,
    rippleDisplayCenter: PropTypes.bool,
    checked: PropTypes.bool,
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.string,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    onTouchTap: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

ListItem.defaultProps = {

    index: 0,

    className: null,
    style: null,
    theme: null,

    selectTheme: null,
    selectMode: SelectMode.NORMAL,

    data: null,
    value: null,
    text: null,
    desc: null,

    disabled: false,
    isLoading: false,
    disableTouchRipple: false,
    rippleDisplayCenter: false,
    checked: false,
    readOnly: false,

    iconCls: null,
    rightIconCls: null,

    tip: null,
    tipPosition: Position.BOTTOM,

    radioUncheckedIconCls: 'fa fa-check',
    radioCheckedIconCls: 'fa fa-check',
    checkboxUncheckedIconCls: 'fa fa-square-o',
    checkboxCheckedIconCls: 'fa fa-check-square',
    checkboxIndeterminateIconCls: 'fa fa-minus-square'

};<|endoftext|>


<commit_before>(function(buster, when, callbacks) {

var assert, fail, sentinel;

assert = buster.assert;
fail   = buster.fail;

sentinel = {};

function assertIsPromise(arg) {
	assert(when.isPromise(arg));
}

buster.testCase('when/callbacks', {
	'apply': {
		'should return a promise': function() {
			assertIsPromise(callbacks.apply(function() {}));
		},

		'should resolve with the callback arguments': function(done) {
			var promise = callbacks.apply(function(cb) {
				cb(sentinel);
			});

			promise.then(function(val) {
				assert.same(val, sentinel);
			}, fail).always(done);
		},

		'should reject with the errback arguments': function(done) {
			var promise = callbacks.apply(function(cb, eb){
				eb(sentinel);
			});

			promise.then(fail, function(reason) {
				assert.same(reason, sentinel);
			}).always(done);
		},

		'should turn exceptions into rejections': function(done) {
			var error = new Error();
			var promise = callbacks.apply(function(){
				throw error;
			});

			promise.then(fail, function(reason) {
				assert.equals(reason, error);
			}).always(done);
		},

		'should forward its second argument to the function': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.apply(async, [10, 15]);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		},

		'should turn multiple callback values into an array': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a * 10, b * 20);
			};

			var promise = callbacks.apply(async, [10, 20]);
			promise.then(function(results) {
				assert.equals(results, [100, 400]);
			}, fail).always(done);
		},

		'should accept promises on the extra arguments': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.apply(async, [when(10), 15]);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		}
	},

	'call': {
		'should return a promise': function() {
			assertIsPromise(callbacks.call(function() {}));
		},

		'should resolve with the callback arguments': function(done) {
			var promise = callbacks.apply(function(cb) {
				cb(sentinel);
			});

			promise.then(function(val) {
				assert.same(val, sentinel);
			}, fail).always(done);
		},

		'should reject with the errback arguments': function(done) {
			var promise = callbacks.apply(function(cb, eb){
				eb(sentinel);
			});

			promise.then(fail, function(reason) {
				assert.same(reason, sentinel);
			}).always(done);
		},

		'should turn exceptions into rejections': function(done) {
			var error = new Error();
			var promise = callbacks.call(function(){
				throw error;
			});

			promise.then(fail, function(reason) {
				assert.equals(reason, error);
			}).always(done);
		},

		'should forward its extra arguments to the function': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.call(async, 10, 15);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		},

		'should turn multiple callback values into an array': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a * 10, b * 20);
			};

			var promise = callbacks.call(async, 10, 20);
			promise.then(function(results) {
				assert.equals(results, [100, 400]);
			}, fail).always(done);
		},

		'should accept promises on the extra arguments': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.call(async, when(10), 15);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		}
	},

	'bind': {
		'should return a function': function() {
			assert.isFunction(callbacks.bind(function() {}));
		},

		'the returned function': {
			'should return a promise': function() {
				var result = callbacks.bind(function() {});
				assertIsPromise(result());
			},

			'should resolve the promise with the callback value': function(done) {
				var result = callbacks.bind(function(cb) {
					cb(10);
				});

				result().then(function(value) {
					assert.equals(value, 10);
				}, fail).always(done);
			},

			'should forward arguments to the original function': function(done) {
				var result = callbacks.bind(function(a, b, cb) {
					cb(a + b);
				});

				result(10, 15).then(function(value) {
					assert.equals(value, 25);
				}, fail).always(done);
			},

			'should reject the promise with the errback value': function(done) {
				var error = new Error();
				var result = callbacks.bind(function(cb, eb) {
					eb(error);
				});

				result().then(fail, function(reason) {
					assert.same(reason, error);
				}).always(done);
			},

			'should turn exceptions into rejections': function(done) {
				var error = new Error();
				var result = callbacks.bind(function(){
					throw error;
				});

				result().then(fail, function(reason) {
					assert.equals(reason, error);
				}).always(done);
			},

			'should turn multiple callback values into an array': function(done) {
				var result = callbacks.bind(function(a, b, cb/*, eb*/) {
					cb(a * 10, b * 20);
				});

				result(10, 20).then(function(results) {
					assert.equals(results, [100, 400]);
				}, fail).always(done);
			},

			'should accept promises as arguments': function(done) {
				var result = callbacks.bind(function(a, b, cb/*, eb*/) {
					cb(a + b);
				});

				result(when(10), 15).then(function(result) {
					assert.equals(result, 25);
				}, fail).always(done);
			}
		},

		'should accept leading arguments': function(done) {
			function fancySum(x, y, callback) {
				callback(x + y);
			}

			var partiallyApplied = callbacks.bind(fancySum, 5);

			partiallyApplied(10).then(function(value) {
				assert.equals(value, 15);
			}, fail).always(done);
		},

		'should accept promises as leading arguments': function(done) {
			function fancySum(x, y, callback) {
				callback(x + y);
			}

			var partiallyApplied = callbacks.bind(fancySum, when(5));

			partiallyApplied(10).then(function(value) {
				assert.equals(value, 15);
			}, fail).always(done);
		},
	},

	'promisify': {
		'should support callbacks in any position': function(done) {
			function weirdAsync(a, callback, b) {
				callback(a + b);
			}

			var promisified = callbacks.promisify(weirdAsync, {
				callback: 1
			});

			promisified(10, 5).then(function(result) {
				assert.equals(result, 15);
			}, fail).always(done);
		},

		'should support errbacks in any position': function(done) {
			function weirdAsync(errback, a, callback, b) {
				errback(a + b);
			}

			var promisified = callbacks.promisify(weirdAsync, {
				callback: 2,
				errback:  0
			});

			promisified(10, 5).then(fail, function(reason) {
				assert.equals(reason, 15);
			}).always(done);
		},

		'should turn multiple callback values into an array': function(done) {
			function invert(cb, eb, a, b) {
				cb(b, a);
			}

			var promisified = callbacks.promisify(invert, {
				callback: 0,
				errback:  1,
			});

			promisified(10, 20).then(function(results) {
				assert.equals(results, [20, 10]);
			}, fail).always(done);
		},

		'should turn exceptions into rejections': function(done) {
			var error = new Error();
			var result = callbacks.promisify(function(){
				throw error;
			}, {});

			result().then(fail, function(reason) {
				assert.equals(reason, error);
			}).always(done);
		},

		'should accept promises as arguments': function(done) {
			var result = callbacks.promisify(function(a, b, cb/*, eb*/) {
				cb(a + b);
			}, {
				callback: -2,
				errback:  -1,
			});

			result(when(10), 15).then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		},

		'should understand -1 as "the last argument"': function(done) {
			function asyncSum(/*n1, n2, n3...callback*/) {
				var args = [].slice.call(arguments, 0);

				var callback = args.pop();

				var result = args.reduce(function(prev, n) {
					return prev + n;
				});
				callback(result);
			}

			var promisified = callbacks.promisify(asyncSum, {
				callback: -1
			});

			promisified(5, 10, 15).then(function(result) {
				assert.equals(result, 30);
			}, fail).always(done);
		},

		'should understand -2 as "the penultimate argument"': function(done) {
			function asyncConcat(/*str1, str2, str3...errback, callback*/) {
				var args = [].slice.call(arguments, 0);

				/*var callback =*/ args.pop();
				var errback = args.pop();

				var result = args.reduce(function(prev, n) {
					return prev + ' ' + n;
				});

				errback(result);
			}

			var promisified = callbacks.promisify(asyncConcat, {
				errback: -2,
				callback: -1
			});

			var promise = promisified('That\'s', 'an', 'extreme', 'example');
			promise.then(fail, function(reason) {
				assert.equals(reason, 'That\'s an extreme example');
			}).always(done);
		}
	}
});

})(
	this.buster    || require('buster'),
	this.when      || require('..'),
	this.callbacks || require('../callbacks')
);
<commit_msg>Remove trailing commas<commit_after>(function(buster, when, callbacks) {

var assert, fail, sentinel;

assert = buster.assert;
fail   = buster.fail;

sentinel = {};

function assertIsPromise(arg) {
	assert(when.isPromise(arg));
}

buster.testCase('when/callbacks', {
	'apply': {
		'should return a promise': function() {
			assertIsPromise(callbacks.apply(function() {}));
		},

		'should resolve with the callback arguments': function(done) {
			var promise = callbacks.apply(function(cb) {
				cb(sentinel);
			});

			promise.then(function(val) {
				assert.same(val, sentinel);
			}, fail).always(done);
		},

		'should reject with the errback arguments': function(done) {
			var promise = callbacks.apply(function(cb, eb){
				eb(sentinel);
			});

			promise.then(fail, function(reason) {
				assert.same(reason, sentinel);
			}).always(done);
		},

		'should turn exceptions into rejections': function(done) {
			var error = new Error();
			var promise = callbacks.apply(function(){
				throw error;
			});

			promise.then(fail, function(reason) {
				assert.equals(reason, error);
			}).always(done);
		},

		'should forward its second argument to the function': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.apply(async, [10, 15]);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		},

		'should turn multiple callback values into an array': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a * 10, b * 20);
			};

			var promise = callbacks.apply(async, [10, 20]);
			promise.then(function(results) {
				assert.equals(results, [100, 400]);
			}, fail).always(done);
		},

		'should accept promises on the extra arguments': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.apply(async, [when(10), 15]);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		}
	},

	'call': {
		'should return a promise': function() {
			assertIsPromise(callbacks.call(function() {}));
		},

		'should resolve with the callback arguments': function(done) {
			var promise = callbacks.apply(function(cb) {
				cb(sentinel);
			});

			promise.then(function(val) {
				assert.same(val, sentinel);
			}, fail).always(done);
		},

		'should reject with the errback arguments': function(done) {
			var promise = callbacks.apply(function(cb, eb){
				eb(sentinel);
			});

			promise.then(fail, function(reason) {
				assert.same(reason, sentinel);
			}).always(done);
		},

		'should turn exceptions into rejections': function(done) {
			var error = new Error();
			var promise = callbacks.call(function(){
				throw error;
			});

			promise.then(fail, function(reason) {
				assert.equals(reason, error);
			}).always(done);
		},

		'should forward its extra arguments to the function': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.call(async, 10, 15);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		},

		'should turn multiple callback values into an array': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a * 10, b * 20);
			};

			var promise = callbacks.call(async, 10, 20);
			promise.then(function(results) {
				assert.equals(results, [100, 400]);
			}, fail).always(done);
		},

		'should accept promises on the extra arguments': function(done) {
			var async = function(a, b, cb/*, eb*/) {
				cb(a + b);
			};

			var promise = callbacks.call(async, when(10), 15);

			promise.then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		}
	},

	'bind': {
		'should return a function': function() {
			assert.isFunction(callbacks.bind(function() {}));
		},

		'the returned function': {
			'should return a promise': function() {
				var result = callbacks.bind(function() {});
				assertIsPromise(result());
			},

			'should resolve the promise with the callback value': function(done) {
				var result = callbacks.bind(function(cb) {
					cb(10);
				});

				result().then(function(value) {
					assert.equals(value, 10);
				}, fail).always(done);
			},

			'should forward arguments to the original function': function(done) {
				var result = callbacks.bind(function(a, b, cb) {
					cb(a + b);
				});

				result(10, 15).then(function(value) {
					assert.equals(value, 25);
				}, fail).always(done);
			},

			'should reject the promise with the errback value': function(done) {
				var error = new Error();
				var result = callbacks.bind(function(cb, eb) {
					eb(error);
				});

				result().then(fail, function(reason) {
					assert.same(reason, error);
				}).always(done);
			},

			'should turn exceptions into rejections': function(done) {
				var error = new Error();
				var result = callbacks.bind(function(){
					throw error;
				});

				result().then(fail, function(reason) {
					assert.equals(reason, error);
				}).always(done);
			},

			'should turn multiple callback values into an array': function(done) {
				var result = callbacks.bind(function(a, b, cb/*, eb*/) {
					cb(a * 10, b * 20);
				});

				result(10, 20).then(function(results) {
					assert.equals(results, [100, 400]);
				}, fail).always(done);
			},

			'should accept promises as arguments': function(done) {
				var result = callbacks.bind(function(a, b, cb/*, eb*/) {
					cb(a + b);
				});

				result(when(10), 15).then(function(result) {
					assert.equals(result, 25);
				}, fail).always(done);
			}
		},

		'should accept leading arguments': function(done) {
			function fancySum(x, y, callback) {
				callback(x + y);
			}

			var partiallyApplied = callbacks.bind(fancySum, 5);

			partiallyApplied(10).then(function(value) {
				assert.equals(value, 15);
			}, fail).always(done);
		},

		'should accept promises as leading arguments': function(done) {
			function fancySum(x, y, callback) {
				callback(x + y);
			}

			var partiallyApplied = callbacks.bind(fancySum, when(5));

			partiallyApplied(10).then(function(value) {
				assert.equals(value, 15);
			}, fail).always(done);
		}
	},

	'promisify': {
		'should support callbacks in any position': function(done) {
			function weirdAsync(a, callback, b) {
				callback(a + b);
			}

			var promisified = callbacks.promisify(weirdAsync, {
				callback: 1
			});

			promisified(10, 5).then(function(result) {
				assert.equals(result, 15);
			}, fail).always(done);
		},

		'should support errbacks in any position': function(done) {
			function weirdAsync(errback, a, callback, b) {
				errback(a + b);
			}

			var promisified = callbacks.promisify(weirdAsync, {
				callback: 2,
				errback:  0
			});

			promisified(10, 5).then(fail, function(reason) {
				assert.equals(reason, 15);
			}).always(done);
		},

		'should turn multiple callback values into an array': function(done) {
			function invert(cb, eb, a, b) {
				cb(b, a);
			}

			var promisified = callbacks.promisify(invert, {
				callback: 0,
				errback:  1
			});

			promisified(10, 20).then(function(results) {
				assert.equals(results, [20, 10]);
			}, fail).always(done);
		},

		'should turn exceptions into rejections': function(done) {
			var error = new Error();
			var result = callbacks.promisify(function(){
				throw error;
			}, {});

			result().then(fail, function(reason) {
				assert.equals(reason, error);
			}).always(done);
		},

		'should accept promises as arguments': function(done) {
			var result = callbacks.promisify(function(a, b, cb/*, eb*/) {
				cb(a + b);
			}, {
				callback: -2,
				errback:  -1
			});

			result(when(10), 15).then(function(result) {
				assert.equals(result, 25);
			}, fail).always(done);
		},

		'should understand -1 as "the last argument"': function(done) {
			function asyncSum(/*n1, n2, n3...callback*/) {
				var args = [].slice.call(arguments, 0);

				var callback = args.pop();

				var result = args.reduce(function(prev, n) {
					return prev + n;
				});
				callback(result);
			}

			var promisified = callbacks.promisify(asyncSum, {
				callback: -1
			});

			promisified(5, 10, 15).then(function(result) {
				assert.equals(result, 30);
			}, fail).always(done);
		},

		'should understand -2 as "the penultimate argument"': function(done) {
			function asyncConcat(/*str1, str2, str3...errback, callback*/) {
				var args = [].slice.call(arguments, 0);

				/*var callback =*/ args.pop();
				var errback = args.pop();

				var result = args.reduce(function(prev, n) {
					return prev + ' ' + n;
				});

				errback(result);
			}

			var promisified = callbacks.promisify(asyncConcat, {
				errback: -2,
				callback: -1
			});

			var promise = promisified('That\'s', 'an', 'extreme', 'example');
			promise.then(fail, function(reason) {
				assert.equals(reason, 'That\'s an extreme example');
			}).always(done);
		}
	}
});

})(
	this.buster    || require('buster'),
	this.when      || require('..'),
	this.callbacks || require('../callbacks')
);
<|endoftext|>


<commit_before>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  Platform,
} from 'react-native';
import Example from './components/example/Example'

export default class App extends Component<{}> {
  render() {
    return (
      <View style={styles.container}>
        <Text>This is the React Native App</Text>
        <Example />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
    alignItems: 'stretch',
    backgroundColor: '#F5FCFF',

  },
});
<commit_msg>Update App.native.js<commit_after>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  Platform,
} from 'react-native';
import Example from './components/example/Example'

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>This is the React Native App</Text>
        <Example />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
    alignItems: 'stretch',
    backgroundColor: '#F5FCFF',

  },
});
<|endoftext|>


<commit_before>const documents = require('./documents').Document;
const roles = require('./roles').Roles;
const users = require('./users').Users;

module.exports = {
  documents,
  roles,
  users,
};
<commit_msg>[feature #142608843] refcator code according to the renamed files<commit_after>const documents = require('./Document').Document;
const roles = require('./Roles').Roles;
const users = require('./Users').Users;

module.exports = {
  documents,
  roles,
  users,
};
<|endoftext|>


<commit_before>import React from 'react'
import R from 'ramda'

import { nilOrEmpty } from 'utils'
import Maybe from 'components/Maybe'
import Popover from 'components/Popover'

import {
  Wrapper,
  BoxWrapper,
  Label,
  Number,
  BuilderWrapper,
  Avatar,
  Linker,
  PopInfo,
  PopAvatar,
  PopNickname,
} from './styles/states_containers'

const BuilderList = ({ entries }) => (
  <BuilderWrapper>
    {entries.map(builder => (
      <Popover
        key={builder.htmlUrl}
        content={
          <PopInfo>
            <PopAvatar src={builder.avatar} />
            <PopNickname
              href={builder.htmlUrl}
              rel="noopener noreferrer"
              target="_blank"
            >
              @ {builder.nickname}
            </PopNickname>
          </PopInfo>
        }
        placement="bottom"
        trigger="hover"
      >
        <Linker
          href={builder.htmlUrl}
          rel="noopener noreferrer"
          target="_blank"
        >
          <Avatar src={builder.avatar} />
        </Linker>
      </Popover>
    ))}
  </BuilderWrapper>
)

const StatesContainers = ({ repo }) => (
  <Wrapper>
    <Linker
      href={`${repo.repoUrl}/issues`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        Issue
        <Number>{repo.issuesCount}</Number>
      </BoxWrapper>
    </Linker>
    <Linker
      href={`${repo.repoUrl}/pulls`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        PRs
        <Number>{repo.prsCount}</Number>
      </BoxWrapper>
    </Linker>
    <Linker
      href={`${repo.repoUrl}/releases`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        Release
        <Number
          small={
            nilOrEmpty(repo.releaseTag) ? true : repo.releaseTag.length > 10
          }
        >
          {repo.releaseTag || '--'}
        </Number>
      </BoxWrapper>
    </Linker>
    <Linker
      href={`${repo.repoUrl}/blob/master/LICENSE`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        License
        <Number small={repo.license.length > 5}>
          {R.toUpper(repo.license) || '--'}
        </Number>
      </BoxWrapper>
    </Linker>
    <BoxWrapper grow nomargin nohover>
      Contributers
      <Maybe
        test={!R.isEmpty(repo.contributors)}
        loading={
api rate limit
}
      >
        <BuilderList entries={repo.contributors} />
      </Maybe>
    </BoxWrapper>
  </Wrapper>
)

export default StatesContainers
<commit_msg>fix(preview repo): license null crash the page<commit_after>import React from 'react'
import R from 'ramda'

import { nilOrEmpty } from 'utils'
import Maybe from 'components/Maybe'
import Popover from 'components/Popover'

import {
  Wrapper,
  BoxWrapper,
  Label,
  Number,
  BuilderWrapper,
  Avatar,
  Linker,
  PopInfo,
  PopAvatar,
  PopNickname,
} from './styles/states_containers'

const BuilderList = ({ entries }) => (
  <BuilderWrapper>
    {entries.map(builder => (
      <Popover
        key={builder.htmlUrl}
        content={
          <PopInfo>
            <PopAvatar src={builder.avatar} />
            <PopNickname
              href={builder.htmlUrl}
              rel="noopener noreferrer"
              target="_blank"
            >
              @ {builder.nickname}
            </PopNickname>
          </PopInfo>
        }
        placement="bottom"
        trigger="hover"
      >
        <Linker
          href={builder.htmlUrl}
          rel="noopener noreferrer"
          target="_blank"
        >
          <Avatar src={builder.avatar} />
        </Linker>
      </Popover>
    ))}
  </BuilderWrapper>
)

const StatesContainers = ({ repo }) => (
  <Wrapper>
    <Linker
      href={`${repo.repoUrl}/issues`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        Issue
        <Number>{repo.issuesCount}</Number>
      </BoxWrapper>
    </Linker>
    <Linker
      href={`${repo.repoUrl}/pulls`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        PRs
        <Number>{repo.prsCount}</Number>
      </BoxWrapper>
    </Linker>
    <Linker
      href={`${repo.repoUrl}/releases`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        Release
        <Number
          small={
            nilOrEmpty(repo.releaseTag) ? true : repo.releaseTag.length > 10
          }
        >
          {repo.releaseTag || '--'}
        </Number>
      </BoxWrapper>
    </Linker>
    <Linker
      href={`${repo.repoUrl}/blob/master/LICENSE`}
      rel="noopener noreferrer"
      target="_blank"
    >
      <BoxWrapper>
        License
        {repo.license ? (
          <Number small={repo.license.length > 5}>
            {R.toUpper(repo.license) || '--'}
          </Number>
        ) : (
          <Number>--</Number>
        )}
      </BoxWrapper>
    </Linker>
    <BoxWrapper grow nomargin nohover>
      Contributers
      <Maybe
        test={!R.isEmpty(repo.contributors)}
        loading={
api rate limit
}
      >
        <BuilderList entries={repo.contributors} />
      </Maybe>
    </BoxWrapper>
  </Wrapper>
)

export default StatesContainers
<|endoftext|>