define([
    'jquery',
    '/api/config',
    '/common/common-util.js',
    '/common/common-hash.js',
    '/common/common-language.js',
    '/common/common-interface.js',
    '/common/common-constants.js',
    '/common/common-feedback.js',
    '/common/hyperscript.js',
    '/common/media-tag.js',
    '/common/clipboard.js',
    '/customize/messages.js',
    '/customize/application_config.js',
    '/customize/pages.js',
    '/bower_components/nthen/index.js',
    '/common/invitation.js',

    '/bower_components/scrypt-async/scrypt-async.js',
    'css!/customize/fonts/cptools/style.css',
    '/bower_components/croppie/croppie.min.js',
    'css!/bower_components/croppie/croppie.css',
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard,
             Messages, AppConfig, Pages, NThen, InviteInner) {
    var UIElements = {};

    // Configure MediaTags to use our local viewer
    if (MediaTag) {
        MediaTag.setDefaultConfig('pdf', {
            viewer: '/common/pdfjs/web/viewer.html'
        });
    }

    UIElements.updateTags = function (common, href) {
        var existing, tags;
        NThen(function(waitFor) {
            common.getSframeChannel().query("Q_GET_ALL_TAGS", null, waitFor(function(err, res) {
                if (err || res.error) { return void console.error(err || res.error); }
                existing = Object.keys(res.tags).sort();
            }));
        }).nThen(function (waitFor) {
            common.getPadAttribute('tags', waitFor(function (err, res) {
                if (err) {
                    if (err === 'NO_ENTRY') {
                        UI.alert(Messages.tags_noentry);
                    }
                    waitFor.abort();
                    return void console.error(err);
                }
                tags = res || [];
            }), href);
        }).nThen(function () {
            UI.dialog.tagPrompt(tags, existing, function (newTags) {
                if (!Array.isArray(newTags)) { return; }
                common.setPadAttribute('tags', newTags, null, href);
            });
        });
    };

    var importContent = function (type, f, cfg) {
        return function () {
            var $files = $('<input>', {type:"file"});
            if (cfg && cfg.accept) {
                $files.attr('accept', cfg.accept);
            }
            $files.click();
            $files.on('change', function (e) {
                var file = e.target.files[0];
                var reader = new FileReader();
                reader.onload = function (e) { f(e.target.result, file); };
                reader.readAsText(file, type);
            });
        };
    };

    var getPropertiesData = function (common, cb) {
        var data = {};
        NThen(function (waitFor) {
            var base = common.getMetadataMgr().getPrivateData().origin;
            common.getPadAttribute('', waitFor(function (err, val) {
                if (err || !val) {
                    waitFor.abort();
                    return void cb(err || 'EEMPTY');
                }
                if (!val.fileType) {
                    delete val.owners;
                    delete val.expire;
                }
                Util.extend(data, val);
                if (data.href) { data.href = base + data.href; }
                if (data.roHref) { data.roHref = base + data.roHref; }
            }));
            common.getPadMetadata(null, waitFor(function (obj) {
                if (obj && obj.error) { return; }
                data.owners = obj.owners;
                data.expire = obj.expire;
                data.pending_owners = obj.pending_owners;
            }));
        }).nThen(function () {
            cb(void 0, data);
        });
    };
    var createOwnerModal = function (common, data) {
        var friends = common.getFriends(true);
        var sframeChan = common.getSframeChannel();
        var priv = common.getMetadataMgr().getPrivateData();
        var user = common.getMetadataMgr().getUserData();
        var edPublic = priv.edPublic;
        var channel = data.channel;
        var owners = data.owners || [];
        var pending_owners = data.pending_owners || [];
        var teams = priv.teams;
        var teamOwner = data.teamId;

        var redrawAll = function () {};

        var div1 = h('div.cp-usergrid-user.cp-share-column.cp-ownership');
        var div2 = h('div.cp-usergrid-user.cp-share-column.cp-ownership');
        var $div1 = $(div1);
        var $div2 = $(div2);

        // Remove owner column
        var drawRemove = function (pending) {
            var _owners = {};
            var o = (pending ? pending_owners : owners) || [];
            o.forEach(function (ed) {
                var f;
                Object.keys(friends).some(function (c) {
                    if (friends[c].edPublic === ed) {
                        f = friends[c];
                        return true;
                    }
                });
                Object.keys(teams).some(function (id) {
                    if (teams[id].edPublic === ed) {
                        f = teams[id];
                        f.teamId = id;
                    }
                });
                if (ed === edPublic) {
                    f = f || user;
                    if (f.name) { f.edPublic = edPublic; }
                }
                _owners[ed] = f || {
                    displayName: Messages._getKey('owner_unknownUser', [ed]),
                    edPublic: ed,
                };
            });
            var msg = pending ? Messages.owner_removePendingText
                        : Messages.owner_removeText;
            var removeCol = UIElements.getUserGrid(msg, {
                common: common,
                large: true,
                data: _owners,
                noFilter: true
            }, function () {
            });
            var $div = $(removeCol.div);
            // When clicking on the remove button, we check the selected users.
            // If you try to remove yourself, we'll display an additional warning message
            var btnMsg = pending ? Messages.owner_removePendingButton : Messages.owner_removeButton;
            var removeButton = h('button.no-margin', btnMsg);
            $(removeButton).click(function () {
                // Check selection
                var $sel = $div.find('.cp-usergrid-user.cp-selected');
                var sel = $sel.toArray();
                if (!sel.length) { return; }
                var me = false;
                var toRemove = sel.map(function (el) {
                    var ed = $(el).attr('data-ed');
                    if (!ed) { return; }
                    if (teamOwner && teams[teamOwner] && teams[teamOwner].edPublic === ed) { me = true; }
                    if (ed === edPublic && !teamOwner) { me = true; }
                    return ed;
                }).filter(function (x) { return x; });
                NThen(function (waitFor) {
                    var msg = me ? Messages.owner_removeMeConfirm : Messages.owner_removeConfirm;
                    UI.confirm(msg, waitFor(function (yes) {
                        if (!yes) {
                            waitFor.abort();
                            return;
                        }
                    }));
                }).nThen(function (waitFor) {
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: channel,
                        command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
                        value: toRemove,
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            waitFor.abort();
                            redrawAll();
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            return void UI.warn(text);
                        }
                        UI.log(Messages.saved);
                    }));
                }).nThen(function (waitFor) {
                    sel.forEach(function (el) {
                        var curve = $(el).attr('data-curve');
                        var friend = curve === user.curvePublic ? user : friends[curve];
                        if (!friend) { return; }
                        common.mailbox.sendTo("RM_OWNER", {
                            channel: channel,
                            title: data.title,
                            pending: pending,
                            user: {
                                displayName: user.name,
                                avatar: user.avatar,
                                profile: user.profile,
                                notifications: user.notifications,
                                curvePublic: user.curvePublic,
                                edPublic: priv.edPublic
                            }
                        }, {
                            channel: friend.notifications,
                            curvePublic: friend.curvePublic
                        }, waitFor());
                    });
                }).nThen(function () {
                    redrawAll();
                });
            });
            $div.append(h('p', removeButton));
            return $div;
        };

        // Add owners column
        var drawAdd = function () {
            var $div = $(h('div.cp-share-column'));
            var _friends = JSON.parse(JSON.stringify(friends));
            Object.keys(_friends).forEach(function (curve) {
                if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
                    pending_owners.indexOf(_friends[curve].edPublic) !== -1 ||
                    !_friends[curve].notifications) {
                    delete _friends[curve];
                }
            });
            var addCol = UIElements.getUserGrid(Messages.owner_addText, {
                common: common,
                large: true,
                data: _friends
            }, function () {
                //console.log(arguments);
            });
            $div.append(addCol.div);

            var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
            Object.keys(teamsData).forEach(function (id) {
                var t = teamsData[id];
                t.teamId = id;
                if (owners.indexOf(t.edPublic) !== -1 || pending_owners.indexOf(t.edPublic) !== -1) {
                    delete teamsData[id];
                }
            });
            var teamsList = UIElements.getUserGrid(Messages.owner_addTeamText, {
                common: common,
                large: true,
                noFilter: true,
                data: teamsData
            }, function () {});
            $div.append(teamsList.div);

            // When clicking on the add button, we get the selected users.
            var addButton = h('button.no-margin', Messages.owner_addButton);
            $(addButton).click(function () {
                // Check selection
                var $sel = $div.find('.cp-usergrid-user.cp-selected');
                var sel = $sel.toArray();
                if (!sel.length) { return; }
                var toAdd = sel.map(function (el) {
                    var curve = $(el).attr('data-curve');
                    // If the pad is woned by a team, we can transfer ownership to ourselves
                    if (curve === user.curvePublic && teamOwner) { return priv.edPublic; }
                    var friend = friends[curve];
                    if (!friend) { return; }
                    return friend.edPublic;
                }).filter(function (x) { return x; });
                var toAddTeams = sel.map(function (el) {
                    var team = teamsData[$(el).attr('data-teamid')];
                    if (!team || !team.edPublic) { return; }
                    return {
                        edPublic: team.edPublic,
                        id: $(el).attr('data-teamid')
                    };
                }).filter(function (x) { return x; });

                NThen(function (waitFor) {
                    var msg = Messages.owner_addConfirm;
                    UI.confirm(msg, waitFor(function (yes) {
                        if (!yes) {
                            waitFor.abort();
                            return;
                        }
                    }));
                }).nThen(function (waitFor) {
                    // Add one of our teams as an owner
                    if (toAddTeams.length) {
                        // Send the command
                        sframeChan.query('Q_SET_PAD_METADATA', {
                            channel: channel,
                            command: 'ADD_OWNERS',
                            value: toAddTeams.map(function (obj) { return obj.edPublic; }),
                            teamId: teamOwner
                        }, waitFor(function (err, res) {
                            err = err || (res && res.error);
                            if (err) {
                                waitFor.abort();
                                redrawAll();
                                var text = err === "INSUFFICIENT_PERMISSIONS" ?
                                        Messages.fm_forbidden : Messages.error;
                                return void UI.warn(text);
                            }
                            var isTemplate = priv.isTemplate || data.isTemplate;
                            toAddTeams.forEach(function (obj) {
                                sframeChan.query('Q_STORE_IN_TEAM', {
                                    href: data.href || data.rohref,
                                    password: data.password,
                                    path: isTemplate ? ['template'] : undefined,
                                    title: data.title || '',
                                    teamId: obj.id
                                }, waitFor(function (err) {
                                    if (err) { return void console.error(err); }
                                }));
                            });
                        }));
                    }
                }).nThen(function (waitFor) {
                    // Offer ownership to a friend
                    if (toAdd.length) {
                        // Send the command
                        sframeChan.query('Q_SET_PAD_METADATA', {
                            channel: channel,
                            command: 'ADD_PENDING_OWNERS',
                            value: toAdd,
                            teamId: teamOwner
                        }, waitFor(function (err, res) {
                            err = err || (res && res.error);
                            if (err) {
                                waitFor.abort();
                                redrawAll();
                                var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                              : Messages.error;
                                return void UI.warn(text);
                            }
                        }));
                    }
                }).nThen(function (waitFor) {
                    sel.forEach(function (el) {
                        var curve = $(el).attr('data-curve');
                        var friend = curve === user.curvePublic ? user : friends[curve];
                        if (!friend) { return; }
                        common.mailbox.sendTo("ADD_OWNER", {
                            channel: channel,
                            href: data.href,
                            password: data.password,
                            title: data.title,
                            user: {
                                displayName: user.name,
                                avatar: user.avatar,
                                profile: user.profile,
                                notifications: user.notifications,
                                curvePublic: user.curvePublic,
                                edPublic: priv.edPublic
                            }
                        }, {
                            channel: friend.notifications,
                            curvePublic: friend.curvePublic
                        }, waitFor());
                    });
                }).nThen(function () {
                    redrawAll();
                    UI.log(Messages.saved);
                });
            });
            $div.append(h('p', addButton));
            return $div;
        };

        redrawAll = function (md) {
            var todo = function (obj) {
                if (obj && obj.error) { return; }
                owners = obj.owners || [];
                pending_owners = obj.pending_owners || [];
                $div1.empty();
                $div2.empty();
                $div1.append(drawRemove(false)).append(drawRemove(true));
                $div2.append(drawAdd());
            };

            if (md) { return void todo(md); }
            common.getPadMetadata({
                channel: data.channel
            }, todo);
        };

        $div1.append(drawRemove(false)).append(drawRemove(true));
        $div2.append(drawAdd());

        var handler = sframeChan.on('EV_RT_METADATA', function (md) {
            if (!$div1.length) {
                return void handler.stop();
            }
            owners = md.owners || [];
            pending_owners = md.pending_owners || [];
            redrawAll(md);
        });

        // Create modal
        var link = h('div.cp-share-columns', [
            div1,
            div2
            /*drawRemove()[0],
            drawAdd()[0]*/
        ]);
        var linkButtons = [{
            className: 'cancel',
            name: Messages.filePicker_close,
            onClick: function () {},
            keys: [27]
        }];
        return UI.dialog.customModal(link, {buttons: linkButtons});
    };
    var getRightsProperties = function (common, data, cb) {
        var $div = $('<div>');
        if (!data) { return void cb(void 0, $div); }

        var draw = function () {
            var $d = $('<div>');
            var priv = common.getMetadataMgr().getPrivateData();
            var user = common.getMetadataMgr().getUserData();
            var edPublic = priv.edPublic;
            var owned = false;
            var _owners = {};
            if (data.owners && data.owners.length) {
                if (data.owners.indexOf(edPublic) !== -1) {
                    owned = true;
                } else {
                    Object.keys(priv.teams || {}).some(function (id) {
                        var team = priv.teams[id] || {};
                        if (team.viewer) { return; }
                        if (data.owners.indexOf(team.edPublic) === -1) { return; }
                        owned = id;
                        return true;
                    });
                }
                var strangers = 0;
                data.owners.forEach(function (ed) {
                    // If a friend is an owner, add their name to the list
                    // otherwise, increment the list of strangers

                    // Our edPublic? print "Yourself"
                    if (ed === edPublic) {
                        _owners[ed] = {
                            selected: true,
                            name: user.name,
                            avatar: user.avatar
                        };
                        return;
                    }
                    // One of our teams? print the team name
                    if (Object.keys(priv.teams || {}).some(function (id) {
                        var team = priv.teams[id] || {};
                        if (team.edPublic !== ed) { return; }
                        _owners[ed] = {
                            name: team.name,
                            avatar: team.avatar
                        };
                        return true;
                    })) {
                        return;
                    }
                    // One of our friends? print the friend name
                    if (Object.keys(priv.friends || {}).some(function (c) {
                        var friend = priv.friends[c] || {};
                        if (friend.edPublic !== ed || c === 'me') { return; }
                        _owners[friend.edPublic] = {
                            name: friend.displayName,
                            avatar: friend.avatar
                        };
                        return true;
                    })) {
                        return;
                    }
                    // Otherwise it's a stranger
                    strangers++;
                });
                if (strangers) {
                    _owners['stangers'] = {
                        name: Messages._getKey('properties_unknownUser', [strangers]),
                    };
                }
            }
            var _ownersGrid = UIElements.getUserGrid(Messages.creation_owners, {
                common: common,
                noSelect: true,
                data: _owners,
                large: true
            }, function () {});
            if (_ownersGrid && Object.keys(_owners).length) {
                $d.append(_ownersGrid.div);
            } else {
                $d.append([
                    h('label', Messages.creation_owners),
                ]);
                $d.append(UI.dialog.selectable(Messages.creation_noOwner, {
                    id: 'cp-app-prop-owners',
                }));

            }

            var parsed;
            if (data.href || data.roHref) {
                parsed = Hash.parsePadUrl(data.href || data.roHref);
            }
            if (owned && parsed.hashData.type === 'pad') {
                var manageOwners = h('button.no-margin', Messages.owner_openModalButton);
                $(manageOwners).click(function () {
                    data.teamId = typeof(owned) !== "boolean" ? owned : undefined;
                    var modal = createOwnerModal(common, data);
                    UI.openCustomModal(modal, {
                        wide: true,
                    });
                });
                $d.append(h('p', manageOwners));
            }

            if (!data.noExpiration) {
                var expire = Messages.creation_expireFalse;
                if (data.expire && typeof (data.expire) === "number") {
                    expire = new Date(data.expire).toLocaleString();
                }
                $('<label>', {'for': 'cp-app-prop-expire'}).text(Messages.creation_expiration)
                    .appendTo($d);
                $d.append(UI.dialog.selectable(expire, {
                    id: 'cp-app-prop-expire',
                }));
            }

            if (!data.noPassword) {
                var hasPassword = data.password;
                if (hasPassword) {
                    $('<label>', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
                        .appendTo($d);
                    var password = UI.passwordInput({
                        id: 'cp-app-prop-password',
                        readonly: 'readonly'
                    });
                    var $pwInput = $(password).find('.cp-password-input');
                    $pwInput.val(data.password).click(function () {
                        $pwInput[0].select();
                    });
                    $d.append(password);
                }

                if (!data.noEditPassword && owned) { // FIXME SHEET fix password change for sheets
                    var sframeChan = common.getSframeChannel();

                    var isOO = parsed.type === 'sheet';
                    var isFile = parsed.hashData.type === 'file';
                    var isSharedFolder = parsed.type === 'drive';

                    var changePwTitle = Messages.properties_changePassword;
                    var changePwConfirm = isFile ? Messages.properties_confirmChangeFile : Messages.properties_confirmChange;
                    if (!hasPassword) {
                        changePwTitle = Messages.properties_addPassword;
                        changePwConfirm = isFile ? Messages.properties_confirmNewFile : Messages.properties_confirmNew;
                    }
                    $('<label>', {'for': 'cp-app-prop-change-password'})
                        .text(changePwTitle).appendTo($d);
                    var newPassword = UI.passwordInput({
                        id: 'cp-app-prop-change-password',
                        style: 'flex: 1;'
                    });
                    var passwordOk = h('button', Messages.properties_changePasswordButton);
                    var changePass = h('span.cp-password-change-container', [
                        newPassword,
                        passwordOk
                    ]);
                    var pLocked = false;
                    $(passwordOk).click(function () {
                        var newPass = $(newPassword).find('input').val();
                        if (data.password === newPass ||
                            (!data.password && !newPass)) {
                            return void UI.alert(Messages.properties_passwordSame);
                        }
                        if (pLocked) { return; }
                        pLocked = true;
                        UI.confirm(changePwConfirm, function (yes) {
                            if (!yes) { pLocked = false; return; }
                            $(passwordOk).html('').append(h('span.fa.fa-spinner.fa-spin', {style: 'margin-left: 0'}));
                            var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' :
                                        (isOO ? 'Q_OO_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE');

                            // If this is a file password change, register to the upload events:
                            // * if there is a pending upload, ask if we shoudl interrupt
                            // * display upload progress
                            var onPending;
                            var onProgress;
                            if (isFile) {
                                onPending = sframeChan.on('Q_BLOB_PASSWORD_CHANGE_PENDING', function (data, cb) {
                                    onPending.stop();
                                    UI.confirm(Messages.upload_uploadPending, function (yes) {
                                        cb({cancel: yes});
                                    });
                                });
                                onProgress = sframeChan.on('EV_BLOB_PASSWORD_CHANGE_PROGRESS', function (data) {
                                    if (typeof (data) !== "number") { return; }
                                    var p = Math.round(data);
                                    $(passwordOk).text(p + '%');
                                });
                            }

                            sframeChan.query(q, {
                                teamId: typeof(owned) !== "boolean" ? owned : undefined,
                                href: data.href || data.roHref,
                                password: newPass
                            }, function (err, data) {
                                $(passwordOk).text(Messages.properties_changePasswordButton);
                                pLocked = false;
                                if (err || data.error) {
                                    console.error(err || data.error);
                                    return void UI.alert(Messages.properties_passwordError);
                                }
                                UI.findOKButton().click();
                                if (isFile) {
                                    onProgress.stop();
                                    $(passwordOk).text(Messages.properties_changePasswordButton);
                                    var alertMsg = data.warning ? Messages.properties_passwordWarningFile
                                                                : Messages.properties_passwordSuccessFile;
                                    return void UI.alert(alertMsg, undefined, {force: true});
                                }
                                // If we didn't have a password, we have to add the /p/
                                // If we had a password and we changed it to a new one, we just have to reload
                                // If we had a password and we removed it, we have to remove the /p/
                                if (data.warning) {
                                    return void UI.alert(Messages.properties_passwordWarning, function () {
                                        common.gotoURL(hasPassword && newPass ? undefined : (data.href || data.roHref));
                                    }, {force: true});
                                }
                                return void UI.alert(Messages.properties_passwordSuccess, function () {
                                    if (!isSharedFolder) {
                                        common.gotoURL(hasPassword && newPass ? undefined : (data.href || data.roHref));
                                    }
                                }, {force: true});
                            });
                        });
                    });
                    $d.append(changePass);
                }
            }
            return $d;
        };

        var sframeChan = common.getSframeChannel();
        var handler = sframeChan.on('EV_RT_METADATA', function (md) {
            if (!$div.length) {
                handler.stop();
                return;
            }
            md = JSON.parse(JSON.stringify(md));
            data.owners = md.owners;
            data.expire = md.expire;
            data.pending_owners = md.pending_owners;
            $div.empty();
            $div.append(draw());
        });
        $div.append(draw());

        cb(void 0, $div);
    };
    var getPadProperties = function (common, data, cb) {
        var $d = $('<div>');
        if (!data) { return void cb(void 0, $d); }

        if (data.href) {
            $('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
            $d.append(UI.dialog.selectable(data.href, {
                id: 'cp-app-prop-link',
            }));
        }

        if (data.roHref) {
            $('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
            $d.append(UI.dialog.selectable(data.roHref, {
                id: 'cp-app-prop-rolink',
            }));
        }

        if (data.tags && Array.isArray(data.tags)) {
            $d.append(h('div.cp-app-prop', [Messages.fm_prop_tagsList, h('br'), h('span.cp-app-prop-content', data.tags.join(', '))]));
        }

        if (data.ctime) {
            $d.append(h('div.cp-app-prop', [Messages.fm_creation,  h('br'), h('span.cp-app-prop-content', new Date(data.ctime).toLocaleString())]));
        }

        if (data.atime) {
            $d.append(h('div.cp-app-prop', [Messages.fm_lastAccess,  h('br'), h('span.cp-app-prop-content', new Date(data.atime).toLocaleString())]));
        }

        if (common.isLoggedIn()) {
            // check the size of this file...
            var bytes = 0;
            NThen(function (waitFor) {
                var chan = [data.channel];
                if (data.rtChannel) { chan.push(data.rtChannel); }
                if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
                chan.forEach(function (c) {
                    common.getFileSize(c, waitFor(function (e, _bytes) {
                        if (e) {
                            // there was a problem with the RPC
                            console.error(e);
                        }
                        bytes += _bytes;
                    }));
                });
            }).nThen(function () {
                if (bytes === 0) { return void cb(void 0, $d); }
                var KB = Util.bytesToKilobytes(bytes);

                var formatted = Messages._getKey('formattedKB', [KB]);
                $d.append(h('div.cp-app-prop', [Messages.upload_size, h('br'), h('span.cp-app-prop-content', formatted)]));

        if (data.sharedFolder && false) {
            $('<label>', {'for': 'cp-app-prop-channel'}).text('Channel ID').appendTo($d);
            if (AppConfig.pinBugRecovery) { $d.append(h('p', AppConfig.pinBugRecovery)); }
            $d.append(UI.dialog.selectable(data.channel, {
                id: 'cp-app-prop-link',
            }));
        }

                cb(void 0, $d);
            });
        } else {
            cb(void 0, $d);
        }


    };
    UIElements.getProperties = function (common, data, cb) {
        var c1;
        var c2;
        var button = [{
            className: 'primary',
            name: Messages.okButton,
            onClick: function () {},
            keys: [13]
        }];
        NThen(function (waitFor) {
            getPadProperties(common, data, waitFor(function (e, c) {
                c1 = UI.dialog.customModal(c[0], {
                    buttons: button
                });
            }));
            getRightsProperties(common, data, waitFor(function (e, c) {
                c2 = UI.dialog.customModal(c[0], {
                    buttons: button
                });
            }));
        }).nThen(function () {
            var tabs = UI.dialog.tabs([{
                title: Messages.fc_prop,
                content: c1
            }, {
                title: Messages.creation_propertiesTitle,
                content: c2
            }]);
            cb (void 0, $(tabs));
        });
    };

    UIElements.getUserGrid = function (label, config, onSelect) {
        var common = config.common;
        var users = config.data;
        if (!users) { return; }

        var icons = Object.keys(users).map(function (key, i) {
            var data = users[key];
            var name = data.displayName || data.name || Messages.anonymous;
            var avatar = h('span.cp-usergrid-avatar.cp-avatar');
            UIElements.displayAvatar(common, $(avatar), data.avatar, name);
            return h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
                'data-ed': data.edPublic,
                'data-teamid': data.teamId,
                'data-curve': data.curvePublic || '',
                'data-name': name.toLowerCase(),
                'data-order': i,
                title: name,
                style: 'order:'+i+';'
            },[
                avatar,
                h('span.cp-usergrid-user-name', name)
            ]);
        }).filter(function (x) { return x; });

        var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : '';

        var inputFilter = h('input', {
            placeholder: Messages.share_filterFriend
        });

        var div = h('div.cp-usergrid-container' + noOthers + (config.large?'.large':''), [
            label ? h('label', label) : undefined,
            h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [
                inputFilter
            ]),
        ]);
        var $div = $(div);

        // Hide friends when they are filtered using the text input
        var redraw = function () {
            var name = $(inputFilter).val().trim().replace(/"/g, '').toLowerCase();
            $div.find('.cp-usergrid-user').show();
            if (name) {
                $div.find('.cp-usergrid-user:not(.cp-selected):not([data-name*="'+name+'"])').hide();
            }
        };
        $(inputFilter).on('keydown keyup change', redraw);

        $(div).append(h('div.cp-usergrid-grid', icons));
        if (!config.noSelect) {
            $div.on('click', '.cp-usergrid-user', function () {
                var sel = $(this).hasClass('cp-selected');
                if (!sel) {
                    $(this).addClass('cp-selected');
                } else {
                    var order = $(this).attr('data-order');
                    order = order ? 'order:'+order : '';
                    $(this).removeClass('cp-selected').attr('style', order);
                }
                onSelect();
            });
        }

        return {
            icons: icons,
            div: div
        };
    };


    var createShareWithFriends = function (config, onShare, linkGetter) {
        var common = config.common;
        var sframeChan = common.getSframeChannel();
        var title = config.title;
        var friends = config.friends;
        var myName = common.getMetadataMgr().getUserData().name;
        if (!friends) { return; }
        var order = [];

        var smallCurves = Object.keys(friends).map(function (c) {
            return friends[c].curvePublic.slice(0,8);
        });

        var div = h('div.contains-nav');
        var $div = $(div);
        // Replace "copy link" by "share with friends" if at least one friend is selected
        // Also create the "share with friends" button if it doesn't exist
        var refreshButtons = function () {
            var $nav = $div.closest('.alertify').find('nav');

            var friendMode = $div.find('.cp-usergrid-user.cp-selected').length;
            if (friendMode) {
                $nav.find('button.cp-share-with-friends').prop('disabled', '');
            } else {
                $nav.find('button.cp-share-with-friends').prop('disabled', 'disabled');
            }
        };

        config.noInclude = true;
        Object.keys(friends).forEach(function (curve) {
            var data = friends[curve];
            if (curve.length > 40 && data.notifications) { return; }
            delete friends[curve];
        });

        var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, {
            common: common,
            data: friends,
            noFilter: false,
            large: true
        }, refreshButtons);
        var friendDiv = friendsList.div;
        $div.append(friendDiv);
        var others = friendsList.icons;

        var privateData = common.getMetadataMgr().getPrivateData();
        var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {};
        var teams = {};
        Object.keys(teamsData).forEach(function (id) {
            // config.teamId only exists when we're trying to share a pad from a team drive
            // In this case, we don't want to share the pad with the current team
            if (config.teamId && config.teamId === id) { return; }
            if (!teamsData[id].hasSecondaryKey) { return; }
            var t = teamsData[id];
            teams[t.edPublic] = {
                notifications: true,
                displayName: t.name,
                edPublic: t.edPublic,
                avatar: t.avatar,
                id: id
            };
        });
        var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
            common: common,
            noFilter: true,
            large: true,
            data: teams
        }, refreshButtons);
        $div.append(teamsList.div);

        var shareButton = {
            className: 'primary cp-share-with-friends',
            name: Messages.share_withFriends,
            onClick: function () {
                var href = Hash.getRelativeHref(linkGetter());
                var $friends = $div.find('.cp-usergrid-user.cp-selected');
                $friends.each(function (i, el) {
                    var curve = $(el).attr('data-curve');
                    // Check if the selected element is a friend or a team
                    if (curve) { // Friend
                        if (!curve || !friends[curve]) { return; }
                        var friend = friends[curve];
                        if (!friend.notifications || !friend.curvePublic) { return; }
                        common.mailbox.sendTo("SHARE_PAD", {
                            href: href,
                            password: config.password,
                            isTemplate: config.isTemplate,
                            name: myName,
                            title: title
                        }, {
                            channel: friend.notifications,
                            curvePublic: friend.curvePublic
                        });
                        return;
                    }
                    // Team
                    var ed = $(el).attr('data-ed');
                    var team = teams[ed];
                    if (!team) { return; }
                    sframeChan.query('Q_STORE_IN_TEAM', {
                        href: href,
                        password: config.password,
                        path: config.isTemplate ? ['template'] : undefined,
                        title: title,
                        teamId: team.id
                    }, function (err) {
                        if (err) { return void console.error(err); }
                    });
                });

                UI.findCancelButton().click();

                // Update the "recently shared with" array:
                // Get the selected curves
                var curves = $friends.toArray().map(function (el) {
                    return ($(el).attr('data-curve') || '').slice(0,8);
                }).filter(function (x) { return x; });
                // Prepend them to the "order" array
                Array.prototype.unshift.apply(order, curves);
                order = Util.deduplicateString(order);
                // Make sure we don't have "old" friends and save
                order = order.filter(function (curve) {
                    return smallCurves.indexOf(curve) !== -1;
                });
                common.setAttribute(['general', 'share-friends'], order);
                if (onShare) {
                    onShare.fire();
                }
            },
            keys: [13]
        };

        common.getAttribute(['general', 'share-friends'], function (err, val) {
            order = val || [];
            // Sort friends by "recently shared with"
            others.sort(function (a, b) {
                var ca = ($(a).attr('data-curve') || '').slice(0,8);
                var cb = ($(b).attr('data-curve') || '').slice(0,8);
                if (!ca && !cb) { return 0; }
                if (!ca) { return 1; }
                if (!cb) { return -1; }
                var ia = order.indexOf(ca);
                var ib = order.indexOf(cb);
                if (ia === -1 && ib === -1) { return 0; }
                if (ia === -1) { return 1; }
                if (ib === -1) { return -1; }
                return ia - ib;
            });
            // Reorder the friend icons
            others.forEach(function (el, i) {
                $(el).attr('data-order', i).css('order', i);
            });
            // Display them
            $(friendDiv).find('.cp-usergrid-grid').detach();
            $(friendDiv).append(h('div.cp-usergrid-grid', others));
            refreshButtons();
        });
        return {
            content: div,
            buttons: [shareButton]
        };
    };

    var noContactsMessage = function(common){
        var metadataMgr = common.getMetadataMgr();
        var data = metadataMgr.getUserData();
        var origin = metadataMgr.getPrivateData().origin;
        if (common.isLoggedIn()) {
            return {
                content: h('p', Messages.share_noContactsLoggedIn),
                buttons: [{
                    className: 'secondary',
                    name: Messages.share_copyProfileLink,
                    onClick: function () {
                        var profile = data.profile ? (origin + '/profile/#' + data.profile) : '';
                        var success = Clipboard.copy(profile);
                        if (success) { UI.log(Messages.shareSuccess); }
                    },
                    keys: [13]
                  }]
            };
        } else {
            return {
                content: h('p', Messages.share_noContactsNotLoggedIn),
                buttons: [{
                    className: 'secondary',
                    name: Messages.login_register,
                    onClick: function () {
                        common.setLoginRedirect(function () {
                            common.gotoURL('/register/');
                        });
                    }
                  }, {
                    className: 'secondary',
                    name: Messages.login_login,
                    onClick: function () {
                        common.setLoginRedirect(function () {
                            common.gotoURL('/login/');
                        });
                    }
                  }
                  ]
            };
        }
    };

    UIElements.createShareModal = function (config) {
        var origin = config.origin;
        var pathname = config.pathname;
        var hashes = config.hashes;
        var common = config.common;

        if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }

        // check if the pad is password protected 
        var hash = hashes.editHash || hashes.viewHash;
        var href = origin + pathname + '#' + hash;
        var parsedHref = Hash.parsePadUrl(href);
        var hasPassword = parsedHref.hashData.password;

        var makeFaqLink = function () {
            var link = h('span', [
                h('i.fa.fa-question-circle'), 
                h('a', {href: '#'}, Messages.passwordFaqLink)
            ]);
            $(link).click(function () {
                common.openURL(config.origin + "/faq.html#security-pad_password");
            });
            return link;
        };
        

        var parsed = Hash.parsePadUrl(pathname);
        var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;

        var rights = h('div.msg.cp-inline-radio-group', [
            h('label', Messages.share_linkAccess),
            h('div.radio-group',[
            UI.createRadio('accessRights', 'cp-share-editable-false',
                           Messages.share_linkView, true, { mark: {tabindex:1} }),
            canPresent ? UI.createRadio('accessRights', 'cp-share-present',
                            Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
            UI.createRadio('accessRights', 'cp-share-editable-true',
                           Messages.share_linkEdit, false, { mark: {tabindex:1} })])
        ]);

        var $rights = $(rights);

        var saveValue = function () {
            var edit = Util.isChecked($rights.find('#cp-share-editable-true'));
            var present = Util.isChecked($rights.find('#cp-share-present'));
            common.setAttribute(['general', 'share'], {
                edit: edit,
                present: present
            });
        };

        var getLinkValue = function (initValue) {
            var val = initValue || {};
            var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true'));
            var embed = val.embed;
            var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present'));
            var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash;
            var href = origin + pathname + '#' + hash;
            var parsed = Hash.parsePadUrl(href);
            return origin + parsed.getUrl({embed: embed, present: present});
        };

        var makeCancelButton = function() {
            return {
                className: 'cancel',
                name: Messages.cancel,
                onClick: function () {},
                keys: [27]
            };
        };

        // Share link tab
        var linkContent = config.sharedFolder ? [
            h('label', Messages.sharedFolders_share),
            h('br'),
        ] : [
            UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
        ];
        linkContent.push(h('div.cp-spacer'));
        linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));

        // Show alert if the pad is password protected
        if (hasPassword) {
            linkContent.push(h('div.alert.alert-primary', [
                h('i.fa.fa-lock'), 
                Messages.share_linkPasswordAlert, h('br'),
                makeFaqLink()
            ]));
        }

        // warning about sharing links 
        var localStore = window.cryptpadStore;
        var dismissButton = h('span.fa.fa-times'); 
        var shareLinkWarning = h('div.alert.alert-warning.dismissable', 
            { style: 'display: none;' }, 
            [ 
                h('span.cp-inline-alert-text', Messages.share_linkWarning),
                dismissButton
            ]);
        linkContent.push(shareLinkWarning);

        localStore.get('hide-alert-shareLinkWarning', function (val) {
            if (val === '1') { return; }
            $(shareLinkWarning).show();

            $(dismissButton).on('click', function () {
                localStore.put('hide-alert-shareLinkWarning', '1');
                $(shareLinkWarning).remove();
            });

        });
          
        

        var link = h('div.cp-share-modal', linkContent);
        var $link = $(link);

        var linkButtons = [
            makeCancelButton(),
            !config.sharedFolder && {
              className: 'secondary',
              name: Messages.share_linkOpen,
              onClick: function () {
                  saveValue();
                  var v = getLinkValue({
                    embed: Util.isChecked($link.find('#cp-share-embed'))
                  });
                  window.open(v);
                  return true;
              },
              keys: [[13, 'ctrl']]
            },
            {
              className: 'primary',
              name: Messages.share_linkCopy,
              onClick: function () {
                  saveValue();
                  var v = getLinkValue({
                    embed: Util.isChecked($link.find('#cp-share-embed'))
                  });
                  var success = Clipboard.copy(v);
                  if (success) { UI.log(Messages.shareSuccess); }
              },
              keys: [13]
            }
          ];

        // update values for link preview when radio btns change
        $link.find('#cp-share-link-preview').val(getLinkValue());
        $rights.find('input[type="radio"]').on('change', function () {
            $link.find('#cp-share-link-preview').val(getLinkValue({
                embed: Util.isChecked($link.find('#cp-share-embed'))
            }));
        });
        $link.find('input[type="checkbox"]').on('change', function () {
            $link.find('#cp-share-link-preview').val(getLinkValue({
                embed: Util.isChecked($link.find('#cp-share-embed'))
            }));
        });

        var frameLink = UI.dialog.customModal(link, {
            buttons: linkButtons,
            onClose: config.onClose,
        });

        // Share with contacts tab

        var hasFriends = Object.keys(config.friends || {}).length !== 0;
        var onFriendShare = Util.mkEvent();


        var friendsObject = hasFriends ? createShareWithFriends(config, onFriendShare, getLinkValue) : noContactsMessage(common);
        var friendsList = friendsObject.content;

        onFriendShare.reg(saveValue);

        // XXX Don't display access rights if no contacts
        var contactsContent = h('div.cp-share-modal');
        var $contactsContent = $(contactsContent);
        
        $contactsContent.append(friendsList);
        
        // Show alert if the pad is password protected
        if (hasPassword) {
            $contactsContent.append(h('div.alert.alert-primary', [
                h('i.fa.fa-unlock'),
                Messages.share_contactPasswordAlert, h('br'),
                makeFaqLink()
            ]));
        }


        var contactButtons = friendsObject.buttons;
        contactButtons.unshift(makeCancelButton());
                             
        var frameContacts = UI.dialog.customModal(contactsContent, {
            buttons: contactButtons,
            onClose: config.onClose,
        });

        // Embed tab
        var getEmbedValue = function () {
            var url = getLinkValue({
                embed: true
            });
            return '<iframe src="' + url + '"></iframe>';
        };
        var embedContent = [
            h('p', Messages.viewEmbedTag),
            UI.dialog.selectableArea(getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1, rows: 3})
        ];

        // Show alert if the pad is password protected
        if (hasPassword) {
            embedContent.push(h('div.alert.alert-primary', [
                h('i.fa.fa-lock'), ' ', 
                Messages.share_embedPasswordAlert, h('br'),
                makeFaqLink()
            ]));
        }

        var embedButtons = [
            makeCancelButton(), {
            className: 'primary',
            name: Messages.share_linkCopy,
            onClick: function () {
                var v = getEmbedValue();
                var success = Clipboard.copy(v);
                if (success) { UI.log(Messages.shareSuccess); }
            },
            keys: [13]
        }];

        var embed = h('div.cp-share-modal', embedContent);
        var $embed = $(embed);

        // update values for link preview when radio btns change
        $embed.find('#cp-embed-link-preview').val(getEmbedValue());
        $rights.find('input[type="radio"]').on('change', function () {
            $embed.find('#cp-embed-link-preview').val(getEmbedValue());
        });

        var frameEmbed = UI.dialog.customModal(embed, {
            buttons: embedButtons,
            onClose: config.onClose,
        });

        // Create modal
        var tabs = [{
            title: Messages.share_contactCategory,
            icon: "fa fa-address-book",
            content: frameContacts,
            active: hasFriends
        }, {
            title: Messages.share_linkCategory,
            icon: "fa fa-link",
            content: frameLink,
            active: !hasFriends
        }, {
            title: Messages.share_embedCategory,
            icon: "fa fa-code",
            content: frameEmbed
        }];
        if (typeof(AppConfig.customizeShareOptions) === 'function') {
            AppConfig.customizeShareOptions(hashes, tabs, {
                type: 'DEFAULT',
                origin: origin,
                pathname: pathname
            });
        }

        var modal = UI.dialog.tabs(tabs);
        $(modal).find('.alertify-tabs-titles').after(rights);

        // disable edit share options if you don't have edit rights
        if (!hashes.editHash) {
            $rights.find('#cp-share-editable-false').attr('checked', true);
            $rights.find('#cp-share-editable-true').removeAttr('checked').attr('disabled', true);
        } else if (!hashes.viewHash) {
            $rights.find('#cp-share-editable-false').removeAttr('checked').attr('disabled', true);
            $rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true);
            $rights.find('#cp-share-editable-true').attr('checked', true);
        }

        common.getAttribute(['general', 'share'], function (err, val) {
            val = val || {};
            if (val.present && canPresent) {
                $rights.find('#cp-share-editable-false').prop('checked', false);
                $rights.find('#cp-share-editable-true').prop('checked', false);
                $rights.find('#cp-share-present').prop('checked', true);
            } else if ((val.edit === false && hashes.viewHash) || !hashes.editHash) {
                $rights.find('#cp-share-editable-false').prop('checked', true);
                $rights.find('#cp-share-editable-true').prop('checked', false);
                $rights.find('#cp-share-present').prop('checked', false);
            } else {
                $rights.find('#cp-share-editable-true').prop('checked', true);
                $rights.find('#cp-share-editable-false').prop('checked', false);
                $rights.find('#cp-share-present').prop('checked', false);
            }
            delete val.embed;
            if (!canPresent) {
                delete val.present;
            }
            $link.find('#cp-share-link-preview').val(getLinkValue(val));
        });
        common.getMetadataMgr().onChange(function () {
            // "hashes" is only available is the secure "share" app
            var _hashes = common.getMetadataMgr().getPrivateData().hashes;
            if (!_hashes) { return; }
            hashes = _hashes;
            $link.find('#cp-share-link-preview').val(getLinkValue());
        });
        return modal;
    };

    UIElements.createFileShareModal = function (config) {
        var origin = config.origin;
        var pathname = config.pathname;
        var hashes = config.hashes;
        var common = config.common;
        var fileData = config.fileData;

        if (!hashes.fileHash) { throw new Error("You must provide a file hash"); }
        var url = origin + pathname + '#' + hashes.fileHash;

        // check if the file is password protected 
        var parsedHref = Hash.parsePadUrl(url);
        var hasPassword = parsedHref.hashData.password;

        var makeFaqLink = function () {
            var link = h('span', [
                h('i.fa.fa-question-circle'), 
                h('a', {href: '#'}, Messages.passwordFaqLink)
            ]);
            $(link).click(function () {
                common.openURL(config.origin + "/faq.html#security-pad_password");
            });
            return link;
        };

        var getLinkValue = function () { return url; };

        var makeCancelButton = function() {
            return {className: 'cancel',
            name: Messages.cancel,
            onClick: function () {},
            keys: [27]};
        };

        // Share link tab
        var linkContent = [
            UI.dialog.selectableArea(getLinkValue(), { id: 'cp-share-link-preview', tabindex: 1, rows:2 })
        ];

        // Show alert if the pad is password protected
        if (hasPassword) {
            linkContent.push(h('div.alert.alert-primary', [
                h('i.fa.fa-lock'),
                Messages.share_linkPasswordAlert, h('br'),
                makeFaqLink()
            ]));
        }

        // warning about sharing links 
        var localStore = window.cryptpadStore;
        var dismissButton = h('span.fa.fa-times'); 
        var shareLinkWarning = h('div.alert.alert-warning.dismissable', 
            { style: 'display: none;' }, 
            [ 
                h('span.cp-inline-alert-text', Messages.share_linkWarning),
                dismissButton
            ]);
        linkContent.push(shareLinkWarning);

        localStore.get('hide-alert-shareLinkWarning', function (val) {
            if (val === '1') { return; }
            $(shareLinkWarning).show();

            $(dismissButton).on('click', function () {
                localStore.put('hide-alert-shareLinkWarning', '1');
                $(shareLinkWarning).remove();
            });

        });

        var link = h('div.cp-share-modal', linkContent);

        var linkButtons = [
            makeCancelButton(),
            {
                className: 'primary',
                name: Messages.share_linkCopy,
                onClick: function () {
                    var v = getLinkValue();
                    var success = Clipboard.copy(v);
                    if (success) { UI.log(Messages.shareSuccess);
                }
              },
              keys: [13]
            }
        ];

        var frameLink = UI.dialog.customModal(link, {
            buttons: linkButtons,
            onClose: config.onClose,
        });

        // share with contacts tab
        var hasFriends = Object.keys(config.friends || {}).length !== 0;

        var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : noContactsMessage(common);
        var friendsList = friendsObject.content;

        var contactsContent = h('div.cp-share-modal');
        var $contactsContent = $(contactsContent);
        $contactsContent.append(friendsList);

        // Show alert if the pad is password protected
        if (hasPassword) {
            $contactsContent.append(h('div.alert.alert-primary', [
                h('i.fa.fa-unlock'), 
                Messages.share_contactPasswordAlert, h('br'),
                makeFaqLink()
            ]));
        }

        var contactButtons = friendsObject.buttons;
        contactButtons.unshift(makeCancelButton());

        var frameContacts = UI.dialog.customModal(contactsContent, {
            buttons: contactButtons,
            onClose: config.onClose,
        });


        // Embed tab
        var embed = h('div.cp-share-modal', [
            h('p', Messages.fileEmbedScript),
            UI.dialog.selectable(common.getMediatagScript()),
            h('p', Messages.fileEmbedTag),
            UI.dialog.selectable(common.getMediatagFromHref(fileData)),
        ]);

        // Show alert if the pad is password protected
        if (hasPassword) {
            embed.append(h('div.alert.alert-primary', [
                h('i.fa.fa-lock'), ' ', 
                Messages.share_embedPasswordAlert, h('br'),
                makeFaqLink()
            ]));
        }

        var embedButtons = [{
            className: 'cancel',
            name: Messages.cancel,
            onClick: function () {},
            keys: [27]
        }, {
            className: 'primary',
            name: Messages.share_mediatagCopy,
            onClick: function () {
                var v = common.getMediatagFromHref(fileData);
                var success = Clipboard.copy(v);
                if (success) { UI.log(Messages.shareSuccess); }
            },
            keys: [13]
        }];
        var frameEmbed = UI.dialog.customModal(embed, {
            buttons: embedButtons,
            onClose: config.onClose,
        });

        // Create modal
        var tabs = [{
            title: Messages.share_contactCategory,
            icon: "fa fa-address-book",
            content: frameContacts,
            active: hasFriends,
        }, {
            title: Messages.share_linkCategory,
            icon: "fa fa-link",
            content: frameLink,
            active: !hasFriends
        }, {
            title: Messages.share_embedCategory,
            icon: "fa fa-code",
            content: frameEmbed
        }];
        if (typeof(AppConfig.customizeShareOptions) === 'function') {
            AppConfig.customizeShareOptions(hashes, tabs, {
                type: 'FILE',
                origin: origin,
                pathname: pathname
            });
        }
        var modal = UI.dialog.tabs(tabs);
        return modal;
    };

    UIElements.createInviteTeamModal = function (config) {
        var common = config.common;
        var hasFriends = Object.keys(config.friends || {}).length !== 0;

        if (!hasFriends) {
            return void UI.alert(Messages.team_noFriend);
        }
        var privateData = common.getMetadataMgr().getPrivateData();
        var team = privateData.teams[config.teamId];
        if (!team) { return void UI.warn(Messages.error); }

        var origin = privateData.origin;

        var module = config.module || common.makeUniversal('team');

        // Invite contacts
        var $div;
        var refreshButton = function () {
            if (!$div) { return; }
            var $modal = $div.closest('.alertify');
            var $nav = $modal.find('nav');
            var $btn = $nav.find('button.primary');
            var selected = $div.find('.cp-usergrid-user.cp-selected').length;
            if (selected) {
                $btn.prop('disabled', '');
            } else {
                $btn.prop('disabled', 'disabled');
            }
        };
        var getContacts = function () {
            var list = UIElements.getUserGrid(Messages.team_pickFriends, {
                common: common,
                data: config.friends,
                large: true
            }, refreshButton);
            var div = h('div.contains-nav');
            var $div = $(div);
            $div.append(list.div);
            var contactsButtons = [{
                className: 'primary',
                name: Messages.team_inviteModalButton,
                onClick: function () {
                    var $sel = $div.find('.cp-usergrid-user.cp-selected');
                    var sel = $sel.toArray();
                    if (!sel.length) { return; }

                    sel.forEach(function (el) {
                        var curve = $(el).attr('data-curve');
                        module.execCommand('INVITE_TO_TEAM', {
                            teamId: config.teamId,
                            user: config.friends[curve]
                        }, function (obj) {
                            if (obj && obj.error) {
                                console.error(obj.error);
                                return UI.warn(Messages.error);
                            }
                        });
                    });
                },
                keys: [13]
            }];

            return {
                content: div,
                buttons: contactsButtons
            };
        };
        var friendsObject = hasFriends ? getContacts() : noContactsMessage(common);
        var friendsList = friendsObject.content;
        var contactsButtons = friendsObject.buttons;
        contactsButtons.unshift({
            className: 'cancel',
            name: Messages.cancel,
            onClick: function () {},
            keys: [27]
        });

        var contactsContent = h('div.cp-share-modal', [
            friendsList
        ]);

        var frameContacts = UI.dialog.customModal(contactsContent, {
            buttons: contactsButtons,
        });

        var linkName, linkPassword, linkMessage, linkError, linkSpinText;
        var linkForm, linkSpin, linkResult;
        var linkWarning;
        // Invite from link
        var dismissButton = h('span.fa.fa-times');
        var linkContent = h('div.cp-share-modal', [
            h('p', Messages.team_inviteLinkTitle ), // XXX
            linkError = h('div.alert.alert-danger.cp-teams-invite-alert', {style : 'display: none;'}),
            linkForm = h('div.cp-teams-invite-form', [
                linkName = h('input', {
                    placeholder:  Messages.team_inviteLinkTempName // XXX
                }),
                h('br'),
                h('div.cp-teams-invite-block', [
                    h('span', Messages.team_inviteLinkSetPassword), // XXX
                    h('a.cp-teams-help.fa.fa-question-circle', {
                        href: origin + '/faq.html#security-pad_password',
                        target: "_blank",
                        'data-tippy-placement': "right"
                    })
                ]), // XXX
                linkPassword = UI.passwordInput({
                    id: 'cp-teams-invite-password',
                    placeholder: Messages.login_password // XXX
                }),
                h('div.cp-teams-invite-block',
                    h('span', Messages.team_inviteLinkNote) // XXX
                ),
                linkMessage = h('textarea.cp-teams-invite-message', { // XXX ansuz hitting enter submits...
                    placeholder: Messages.team_inviteLinkNoteMsg, // XXX
                    rows: 3
                })
            ]),
            linkSpin = h('div.cp-teams-invite-spinner', {
                style: 'display: none;'
            }, [
                h('i.fa.fa-spinner.fa-spin'),
                linkSpinText = h('span', Messages.team_inviteLinkLoading) // XXX
            ]),
            linkResult = h('div', {
                style: 'display: none;'
            }, h('textarea', {
                readonly: 'readonly'
            })),
            linkWarning = h('div.cp-teams-invite-alert.alert.alert-warning.dismissable', {
                style: "display: none;"
            }, [
                h('span.cp-inline-alert-text', Messages.team_inviteLinkWarning),
                dismissButton
            ]) // XXX
        ]);
        var localStore = window.cryptpadStore;
        localStore.get('hide-alert-teamInvite', function (val) {
            if (val === '1') { return; }
            $(linkWarning).show();

            $(dismissButton).on('click', function () {
                localStore.put('hide-alert-teamInvite', '1');
                $(linkWarning).remove();
            });
        });
        var $linkContent = $(linkContent);
        var href;
        var process = function () {
            var $nav = $linkContent.closest('.alertify').find('nav');
            $(linkError).text('').hide();
            var name = $(linkName).val();
            var pw = $(linkPassword).find('input').val();
            var msg = $(linkMessage).val();
            var hash = Hash.createRandomHash('invite', pw);
            var hashData = Hash.parseTypeHash('invite', hash);
            href = origin + '/teams/#' + hash;
            if (!name || !name.trim()) {
                $(linkError).text(Messages.team_inviteLinkErrorName).show(); // XXX
                return true;
            }

            var seeds = InviteInner.deriveSeeds(hashData.key);
            var salt = InviteInner.deriveSalt(pw, AppConfig.loginSalt);

            var bytes64;
            NThen(function (waitFor) {
                $(linkForm).hide();
                $(linkSpin).show();
                $nav.find('button.cp-teams-invite-create').hide();
                $nav.find('button.cp-teams-invite-copy').show();
                setTimeout(waitFor(), 150);
            }).nThen(function (waitFor) {
                InviteInner.deriveBytes(seeds.scrypt, salt, waitFor(function (_bytes) {
                    bytes64 = _bytes;
                }));
            }).nThen(function (waitFor) {
                $(linkSpinText).text(Messages.team_inviteLinkAdding); // XXX
                module.execCommand('CREATE_INVITE_LINK', {
                    name: name,
                    password: pw,
                    message: msg,
                    bytes64: bytes64,
                    hash: hash,
                    teamId: config.teamId,
                    seeds: seeds,
                }, waitFor(function (obj) {
                    if (obj && obj.error) {
                        waitFor.abort(); 
                        $(linkSpin).hide();
                        $(linkForm).show(); // XXX DB: check this is the right place to put things back 
                        $nav.find('button.cp-teams-invite-create').show();
                        $nav.find('button.cp-teams-invite-copy').hide();
                        return void $(linkError).text(Messages.team_inviteLinkError+obj.error).show(); // XXX
                    }
                    // Display result here
                    $(linkSpin).hide();
                    $(linkResult).show().find('textarea').text(href);
                    $nav.find('button.cp-teams-invite-copy').prop('disabled', '');
                }));
            });
            return true;
        };
        var linkButtons = [{
            className: 'cancel',
            name: Messages.cancel,
            onClick: function () {},
            keys: [27]
        }, {
            className: 'primary cp-teams-invite-create',
            name: Messages.team_inviteLinkCreate, // XXX
            onClick: function () {
                return process();
            },
            keys: [13]
        }, {
            className: 'primary cp-teams-invite-copy',
            name: Messages.team_inviteLinkCopy, // XXX
            onClick: function () {
                if (!href) { return; }
                var success = Clipboard.copy(href);
                if (success) { UI.log(Messages.shareSuccess); }
            },
            keys: []
        }];

        var frameLink = UI.dialog.customModal(linkContent, {
            buttons: linkButtons,
        });
        $(frameLink).find('.cp-teams-invite-copy').prop('disabled', 'disabled').hide();

        // Create modal
        var tabs = [{
            title: Messages.share_contactCategory,
            icon: "fa fa-address-book",
            content: frameContacts,
            active: hasFriends
        }, {
            title: Messages.share_linkCategory,
            icon: "fa fa-link",
            content: frameLink,
            active: !hasFriends
        }];

        var modal = UI.dialog.tabs(tabs);
        UI.openCustomModal(modal);
    };

    UIElements.createButton = function (common, type, rightside, data, callback) {
        var AppConfig = common.getAppConfig();
        var button;
        var sframeChan = common.getSframeChannel();
        var appType = (common.getMetadataMgr().getMetadata().type || 'pad').toUpperCase();
        switch (type) {
            case 'export':
                button = $('<button>', {
                    'class': 'fa fa-download cp-toolbar-icon-export',
                    title: Messages.exportButtonTitle,
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.exportButton));

                button.click(common.prepareFeedback(type));
                if (callback) {
                    button.click(callback);
                }
                break;
            case 'import':
                button = $('<button>', {
                    'class': 'fa fa-upload cp-toolbar-icon-import',
                    title: Messages.importButtonTitle,
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.importButton));
                /*if (data.types) {
                    // New import button in the toolbar
                    var importFunction = {
                        template: function () {
                            UIElements.openTemplatePicker(common, true);
                        },
                        file: function (cb) {
                            importContent('text/plain', function (content, file) {
                                cb(content, file);
                            }, {accept: data ? data.accept : undefined})
                        }
                    };
                    var toImport = [];
                    Object.keys(data.types).forEach(function (importType) {
                        if (!importFunction[importType] || !data.types[importType]) { return; }
                        var option = h('button', importType);
                        $(option).click(function () {
                            importFunction[importType](data.types[importType]);
                        });
                        toImport.push(options);
                    });

                    button.click(common.prepareFeedback(type));

                    if (toImport.length === 1) {
                        button.click(function () { $(toImport[0]).click(); });
                    } else {
                        Cryptpad.alert(h('p.cp-import-container', toImport));
                    }
                }
                else if (callback) {*/
                    // Old import button, used in settings
                    button
                    .click(common.prepareFeedback(type))
                    .click(importContent('text/plain', function (content, file) {
                        callback(content, file);
                    }, {accept: data ? data.accept : undefined}));
                //}
                break;
            case 'upload':
                button = $('<button>', {
                    'class': 'btn btn-primary new',
                    title: Messages.uploadButtonTitle,
                }).append($('<span>', {'class':'fa fa-upload'})).append(' '+Messages.uploadButton);
                if (!data.FM) { return; }
                var $input = $('<input>', {
                    'type': 'file',
                    'style': 'display: none;',
                    'multiple': 'multiple'
                }).on('change', function (e) {
                    var files = Util.slice(e.target.files);
                    files.forEach(function (file) {
                        var ev = {
                            target: data.target
                        };
                        if (data.filter && !data.filter(file)) {
                            return;
                        }
                        if (data.transformer) {
                            data.transformer(file, function (newFile) {
                                data.FM.handleFile(newFile, ev);
                            });
                            return;
                        }
                        data.FM.handleFile(file, ev);
                    });
                    if (callback) { callback(); }
                });
                if (data.accept) { $input.attr('accept', data.accept); }
                button.click(function () { $input.click(); });
                break;
            case 'importtemplate':
                if (!AppConfig.enableTemplates) { return; }
                if (!common.isLoggedIn()) { return; }
                button = $('<button>', {
                    'class': 'fa fa-upload cp-toolbar-icon-import',
                    title: Messages.template_import,
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.template_import));
                button
                .click(common.prepareFeedback(type))
                .click(function () {
                    UIElements.openTemplatePicker(common, true);
                });
                break;
            case 'template':
                if (!AppConfig.enableTemplates) { return; }
                if (!common.isLoggedIn()) { return; }
                button = $('<button>', {
                    title: Messages.saveTemplateButton,
                    class: 'fa fa-bookmark cp-toolbar-icon-template'
                });
                if (data.rt) {
                    button
                    .click(function () {
                        var title = data.getTitle() || document.title;
                        var todo = function (val) {
                            if (typeof(val) !== "string") { return; }
                            var toSave = data.rt.getUserDoc();
                            if (val.trim()) {
                                val = val.trim();
                                title = val;
                                try {
                                    var parsed = JSON.parse(toSave);
                                    var meta;
                                    if (Array.isArray(parsed) && typeof(parsed[3]) === "object") {
                                        meta = parsed[3].metadata; // pad
                                    } else if (parsed.info) {
                                        meta = parsed.info; // poll
                                    } else {
                                        meta = parsed.metadata;
                                    }
                                    if (typeof(meta) === "object") {
                                        meta.title = val;
                                        meta.defaultTitle = val;
                                        delete meta.users;
                                    }
                                    toSave = JSON.stringify(parsed);
                                } catch(e) {
                                    console.error("Parse error while setting the title", e);
                                }
                            }
                            sframeChan.query('Q_SAVE_AS_TEMPLATE', {
                                toSave: toSave,
                                title: title
                            }, function () {
                                UI.alert(Messages.templateSaved);
                                Feedback.send('TEMPLATE_CREATED');
                            });
                        };
                        UI.prompt(Messages.saveTemplatePrompt, title, todo);
                    });
                }
                break;
            case 'forget':
                button = $('<button>', {
                    title: Messages.forgetButtonTitle,
                    'class': "fa fa-trash cp-toolbar-icon-forget"
                });
                callback = typeof callback === "function" ? callback : function () {};
                button
                .click(common.prepareFeedback(type))
                .click(function() {
                    common.isPadStored(function (err, data) {
                        if (!data) {
                            return void UI.alert(Messages.autostore_notAvailable);
                        }
                        sframeChan.query('Q_IS_ONLY_IN_SHARED_FOLDER', null, function (err, res) {
                            if (err || res.error) { return void console.log(err || res.error); }
                            var msg = Messages.forgetPrompt;
                            if (res) {
                                UI.alert(Messages.sharedFolders_forget);
                                return;
                            } else if (!common.isLoggedIn()) {
                                msg = Messages.fm_removePermanentlyDialog;
                            }
                            UI.confirm(msg, function (yes) {
                                if (!yes) { return; }
                                sframeChan.query('Q_MOVE_TO_TRASH', null, function (err, obj) {
                                    err = err || (obj && obj.error);
                                    if (err) {
                                        callback(err);
                                        return void UI.warn(Messages.fm_forbidden);
                                    }
                                    var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
                                    var msg = common.fixLinks($('<div>').html(cMsg));
                                    UI.alert(msg);
                                    callback();
                                    return;
                                });
                            });

                        });
                    });
                });
                break;
            case 'present':
                button = $('<button>', {
                    title: Messages.presentButtonTitle,
                    'class': "fa fa-play-circle cp-toolbar-icon-present", // used in slide.js
                });
                break;
            case 'preview':
                button = $('<button>', {
                    title: Messages.previewButtonTitle,
                    'class': "fa fa-eye cp-toolbar-icon-preview",
                });
                break;
            case 'print':
                button = $('<button>', {
                    title: Messages.printButtonTitle2,
                    'class': "fa fa-print cp-toolbar-icon-print",
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.printText));
                break;
            case 'history':
                if (!AppConfig.enableHistory) {
                    button = $('<span>');
                    break;
                }
                var active = $(".cp-toolbar-history:visible").length !== 0;
                button = $('<button>', {
                    title: active ? Messages.history_closeTitle : Messages.historyButton,
                    'class': "fa fa-history cp-toolbar-icon-history",
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.historyText));
                button.toggleClass("active", active);
                if (data.histConfig) {
                    if (active) {
                        button.click(function () { $(".cp-toolbar-history-close").trigger("click"); });
                    }
                    else {
                        button
                            .click(common.prepareFeedback(type))
                            .on('click', function () {
                            common.getHistory(data.histConfig);
                        });
                    }
                }
                break;
            case 'more':
                button = $('<button>', {
                    title: Messages.moreActions,
                    'class': "cp-toolbar-drawer-button fa fa-ellipsis-h",
                });
                break;
            case 'mediatag':
                button = $('<button>', {
                    'class': 'fa fa-picture-o cp-toolbar-icon-mediatag',
                    title: Messages.filePickerButton,
                })
                .click(common.prepareFeedback(type));
                break;
            case 'savetodrive':
                button = $('<button>', {
                    'class': 'fa fa-cloud-upload cp-toolbar-icon-savetodrive',
                    title: Messages.canvas_saveToDrive,
                })
                .click(common.prepareFeedback(type));
                break;
            case 'hashtag':
                button = $('<button>', {
                    'class': 'fa fa-hashtag cp-toolbar-icon-hashtag',
                    title: Messages.tags_title,
                })
                .click(common.prepareFeedback(type))
                .click(function () {
                    common.isPadStored(function (err, data) {
                        if (!data) {
                            return void UI.alert(Messages.autostore_notAvailable);
                        }
                        UIElements.updateTags(common, null);
                    });
                });
                break;
            case 'toggle':
                button = $('<button>', {
                    'class': 'fa fa-caret-down cp-toolbar-icon-toggle',
                });
                window.setTimeout(function () {
                    button.attr('title', data.title);
                });
                var updateIcon = function (isVisible) {
                    button.removeClass('fa-caret-down').removeClass('fa-caret-up');
                    if (!isVisible) { button.addClass('fa-caret-down'); }
                    else { button.addClass('fa-caret-up'); }
                };
                button.click(function (e) {
                    data.element.toggle();
                    var isVisible = data.element.is(':visible');
                    if (callback) { callback(isVisible); }
                    if (isVisible) {
                        button.addClass('cp-toolbar-button-active');
                        if (e.originalEvent) { Feedback.send('TOGGLE_SHOW_' + appType); }
                    } else {
                        button.removeClass('cp-toolbar-button-active');
                        if (e.originalEvent) { Feedback.send('TOGGLE_HIDE_' + appType); }
                    }
                    updateIcon(isVisible);
                });
                updateIcon(data.element.is(':visible'));
                break;
            case 'properties':
                button = $('<button>', {
                    'class': 'fa fa-info-circle cp-toolbar-icon-properties',
                    title: Messages.propertiesButtonTitle,
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'})
                .text(Messages.propertiesButton))
                .click(common.prepareFeedback(type))
                .click(function () {
                    common.isPadStored(function (err, data) {
                        if (!data) {
                            return void UI.alert(Messages.autostore_notAvailable);
                        }
                        getPropertiesData(common, function (e, data) {
                            if (e) { return void console.error(e); }
                            UIElements.getProperties(common, data, function (e, $prop) {
                                if (e) { return void console.error(e); }
                                UI.openCustomModal($prop[0]);
                            });
                        });
                    });
                });
                break;
            case 'save': // OnlyOffice save
                button = $('<button>', {
                    'class': 'fa fa-save',
                    title: Messages.settings_save,
                }).append($('<span>', {'class': 'cp-toolbar-drawer-element'})
                .text(Messages.settings_save))
                .click(common.prepareFeedback(type));
                if (callback) { button.click(callback); }
                break;
            default:
                data = data || {};
                var icon = data.icon || "fa-question";
                button = $('<button>', {
                    'class': "fa " + icon,
                })
                .click(common.prepareFeedback(data.name || 'DEFAULT'));
                //.click(common.prepareFeedback(type));
                if (callback) {
                    button.click(callback);
                }
                if (data.title) { button.attr('title', data.title); }
                if (data.style) { button.attr('style', data.style); }
                if (data.id) { button.attr('id', data.id); }
                if (data.hiddenReadOnly) { button.addClass('cp-hidden-if-readonly'); }
                if (data.name) {
                    button.addClass('cp-toolbar-icon-'+data.name);
                    button.click(common.prepareFeedback(data.name));
                }
                if (data.text) {
                    $('<span>', {'class': 'cp-toolbar-drawer-element'}).text(data.text)
                        .appendTo(button);
                }
        }
        if (rightside) {
            button.addClass('cp-toolbar-rightside-button');
        }
        return button;
    };

    var createMdToolbar = function (common, editor) {
        var $toolbar = $('<div>', {
            'class': 'cp-markdown-toolbar'
        });
        var clean = function (str) {
            return str.replace(/^(\n)+/, '').replace(/(\n)+$/, '');
        };
        var actions = {
            'bold': {
                expr: '**{0}**',
                icon: 'fa-bold'
            },
            'italic': {
                expr: '_{0}_',
                icon: 'fa-italic'
            },
            'strikethrough': {
                expr: '~~{0}~~',
                icon: 'fa-strikethrough'
            },
            'heading': {
                apply: function (str) {
                    return '\n'+clean(str).split('\n').map(function (line) {
                        return '# '+line;
                    }).join('\n')+'\n';
                },
                icon: 'fa-header'
            },
            'link': {
                expr: '[{0}](http://)',
                icon: 'fa-link'
            },
            'quote': {
                apply: function (str) {
                    return '\n\n'+str.split('\n').map(function (line) {
                        return '> '+line;
                    }).join('\n')+'\n\n';
                },
                icon: 'fa-quote-right'
            },
            'nlist': {
                apply: function (str) {
                    return '\n'+clean(str).split('\n').map(function (line) {
                        return '1. '+line;
                    }).join('\n')+'\n';
                },
                icon: 'fa-list-ol'
            },
            'list': {
                apply: function (str) {
                    return '\n'+clean(str).split('\n').map(function (line) {
                        return '* '+line;
                    }).join('\n')+'\n';
                },
                icon: 'fa-list-ul'
            },
            'check': {
                apply: function (str) {
                    return '\n' + clean(str).split('\n').map(function (line) {
                        return '* [ ] ' + line;
                    }).join('\n') + '\n';
                },
                icon: 'fa-check-square-o'
            },
            'code': {
                apply: function (str) {
                    if (str.indexOf('\n') !== -1) {
                        return '\n```\n' + clean(str) + '\n```\n';
                    }
                    return '`' + str + '`';
                },
                icon: 'fa-code'
            },
            'toc': {
                expr: '[TOC]',
                icon: 'fa-newspaper-o'
            }
        };
        var onClick = function () {
            var type = $(this).attr('data-type');
            var texts = editor.getSelections();
            var newTexts = texts.map(function (str) {
                str = str || Messages.mdToolbar_defaultText;
                if (actions[type].apply) {
                    return actions[type].apply(str);
                }
                return actions[type].expr.replace('{0}', str);
            });
            editor.replaceSelections(newTexts, 'around');
            editor.focus();
        };
        for (var k in actions) {
            $('<button>', {
                'data-type': k,
                'class': 'fa ' + actions[k].icon,
                title: Messages['mdToolbar_' + k] || k
            }).click(onClick).appendTo($toolbar);
        }
        $('<button>', {
            'class': 'fa fa-question cp-markdown-help',
            title: Messages.mdToolbar_help
        }).click(function () {
            var href = Messages.mdToolbar_tutorial;
            common.openUnsafeURL(href);
        }).appendTo($toolbar);
        return $toolbar;
    };
    UIElements.createMarkdownToolbar = function (common, editor) {
        var readOnly = common.getMetadataMgr().getPrivateData().readOnly;
        if (readOnly) {
            return {
                toolbar: $(),
                button: $(),
                setState: function () {}
            };
        }

        var $toolbar = createMdToolbar(common, editor);
        var cfg = {
            title: Messages.mdToolbar_button,
            element: $toolbar
        };
        var onClick = function (visible) {
            common.setAttribute(['general', 'markdown-help'], visible, function (e) {
                if (e) { return void console.error(e); }
            });
        };

        var $toolbarButton = common.createButton('toggle', true, cfg, onClick);
        var tbState = true;
        common.getAttribute(['general', 'markdown-help'], function (e, data) {
            if (e) { return void console.error(e); }
            if ($(window).height() < 800 && $(window).width() < 800) { return; }
            if (data === true && $toolbarButton.length && tbState) {
                $toolbarButton.click();
            }
        });

        // setState provides the ability to disable the toolbar and the button in case we don't
        // have the markdown editor available (in code we can switch mode, in poll we can publish)
        var setState = function (state) {
            tbState = state;
            if (!state) {
                $toolbar.hide();
                $toolbarButton.hide();
                return;
            }
            common.getAttribute(['general', 'markdown-help'], function (e, data) {
                if (e) { return void console.error(e); }
                if ($(window).height() < 800 && $(window).width() < 800) { return; }
                if (data === true && $toolbarButton) {
                    // Show the toolbar using the button to make sure the icon in the button is
                    // correct (caret-down / caret-up)
                    $toolbar.hide();
                    $toolbarButton.click();
                    return;
                }
                $toolbar.show();
                $toolbarButton.click();
            });
            $toolbarButton.show();
        };

        return {
            toolbar: $toolbar,
            button: $toolbarButton,
            setState: setState
        };
    };

    var setHTML = UIElements.setHTML = function (e, html) {
        e.innerHTML = html;
        return e;
    };

    UIElements.createHelpMenu = function (common, categories) {
        var type = common.getMetadataMgr().getMetadata().type || 'pad';

        var elements = [];
        if (Messages.help && Messages.help.generic) {
            Object.keys(Messages.help.generic).forEach(function (el) {
                elements.push(setHTML(h('li'), Messages.help.generic[el]));
            });
        }
        if (categories) {
            categories.forEach(function (cat) {
                var msgs = Messages.help[cat];
                if (msgs) {
                    Object.keys(msgs).forEach(function (el) {
                        elements.push(setHTML(h('li'), msgs[el]));
                    });
                }
            });
        }

        var text = h('p.cp-help-text', [
            h('h1', Messages.help.title),
            h('ul', elements)
        ]);

        common.fixLinks(text);

        var closeButton = h('span.cp-help-close.fa.fa-window-close');
        var $toolbarButton = common.createButton('', true, {
            title: Messages.hide_help_button,
            text: Messages.help_button,
            name: 'help'
        }).addClass('cp-toolbar-button-active');
        var help = h('div.cp-help-container', [
            closeButton,
            text
        ]);

        var toggleHelp = function (forceClose) {
            if ($(help).hasClass('cp-help-hidden')) {
                if (forceClose) { return; }
                common.setAttribute(['hideHelp', type], false);
                $toolbarButton.addClass('cp-toolbar-button-active');
                $toolbarButton.attr('title', Messages.hide_help_button);
                return void $(help).removeClass('cp-help-hidden');
            }
            $toolbarButton.removeClass('cp-toolbar-button-active');
            $toolbarButton.attr('title', Messages.show_help_button);
            $(help).addClass('cp-help-hidden');
            common.setAttribute(['hideHelp', type], true);
        };

        var showMore = function () {
            $(text).addClass("cp-help-small");
            var $dot = $('<span>').text('...').appendTo($(text).find('h1'));
            $(text).click(function () {
                $(text).removeClass('cp-help-small');
                $(text).off('click');
                $dot.remove();
            });
        };

        $(closeButton).click(function (e) {
            e.stopPropagation();
            toggleHelp(true);
        });
        $toolbarButton.click(function () {
            toggleHelp();
        });

        common.getAttribute(['hideHelp', type], function (err, val) {
            //if ($(window).height() < 800 || $(window).width() < 800) { return void toggleHelp(true); }
            if (val === true) { return void toggleHelp(true); }
            // Note: Help is always hidden by default now, to avoid displaying to many things in the UI
            // This is why we have (true || ...)
            if (!val && (true || $(window).height() < 800 || $(window).width() < 800)) {
                return void showMore();
            }
        });

        return {
            menu: help,
            button: $toolbarButton,
            text: text
        };
    };

    // Avatars

    UIElements.displayMediatagImage = function (Common, $tag, cb) {
        if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.addedNodes.length) {
                    if (mutation.addedNodes.length > 1 ||
                        mutation.addedNodes[0].nodeName !== 'IMG') {
                        return void cb('NOT_IMAGE');
                    }
                    var $image = $tag.find('img');
                    var onLoad = function () {
                        var img = new Image();
                        img.onload = function () {
                            var _cb = cb;
                            cb = $.noop;
                            _cb(null, $image, img);
                        };
                        img.src = $image.attr('src');
                    };
                    if ($image[0].complete) { onLoad(); }
                    $image.on('load', onLoad);
                }
            });
        });
        observer.observe($tag[0], {
            attributes: false,
            childList: true,
            characterData: false
        });
        MediaTag($tag[0]).on('error', function (data) {
            console.error(data);
        });
    };

    var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
    var isEmoji = function (str) {
      return emoji_patt.test(str);
    };
    var emojiStringToArray = function (str) {
      var split = str.split(emoji_patt);
      var arr = [];
      for (var i=0; i<split.length; i++) {
        var char = split[i];
        if (char !== "") {
          arr.push(char);
        }
      }
      return arr;
    };
    var getFirstEmojiOrCharacter = function (str) {
      if (!str || !str.trim()) { return '?'; }
      var emojis = emojiStringToArray(str);
      return isEmoji(emojis[0])? emojis[0]: str[0];
    };
    var avatars = {};
    UIElements.setAvatar = function (hash, data) {
        avatars[hash] = data;
    };
    UIElements.getAvatar = function (hash) {
        return avatars[hash];
    };
    UIElements.displayAvatar = function (common, $container, href, name, cb) {
        var displayDefault = function () {
            var text = getFirstEmojiOrCharacter(name);
            var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
            $container.append($avatar);
            if (cb) { cb(); }
        };
        if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
        if (!href) { return void displayDefault(); }

        var centerImage = function ($img, $image, img) {
            var w = img.width;
            var h = img.height;
            if (w>h) {
                $image.css('max-height', '100%');
                $img.css('flex-direction', 'column');
                if (cb) { cb($img); }
                return;
            }
            $image.css('max-width', '100%');
            $img.css('flex-direction', 'row');
            if (cb) { cb($img); }
        };

        var parsed = Hash.parsePadUrl(href);
        if (parsed.type !== "file" || parsed.hashData.type !== "file") {
            var $img = $('<media-tag>').appendTo($container);
            var img = new Image();
            $(img).attr('src', href);
            img.onload = function () {
                centerImage($img, $(img), img);
                $(img).appendTo($img);
            };
            return;
        }
        // No password for avatars
        var privateData = common.getMetadataMgr().getPrivateData();
        var origin = privateData.fileHost || privateData.origin;
        var secret = Hash.getSecrets('file', parsed.hash);
        if (secret.keys && secret.channel) {
            var hexFileName = secret.channel;
            var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
            var src = origin + Hash.getBlobPathFromHex(hexFileName);
            common.getFileSize(hexFileName, function (e, data) {
                if (e || !data) { return void displayDefault(); }
                if (typeof data !== "number") { return void displayDefault(); }
                if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
                var $img = $('<media-tag>').appendTo($container);
                $img.attr('src', src);
                $img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
                UIElements.displayMediatagImage(common, $img, function (err, $image, img) {
                    if (err) { return void console.error(err); }
                    centerImage($img, $image,  img);
                });
            });
        }
    };
    var transformAvatar = function (file, cb) {
        if (file.type === 'image/gif') { return void cb(file); }
        var $croppie = $('<div>', {
            'class': 'cp-app-profile-resizer'
        });

        if (typeof ($croppie.croppie) !== "function") {
            console.warn('fuck');
            return void cb(file);
        }

        var todo = function () {
            UI.confirm($croppie[0], function (yes) {
                if (!yes) { return; }
                $croppie.croppie('result', {
                    type: 'blob',
                    size: {width: 300, height: 300}
                }).then(function(blob) {
                    blob.lastModifiedDate = new Date();
                    blob.name = 'avatar';
                    cb(blob);
                });
            });
        };

        var reader = new FileReader();
        reader.onload = function(e) {
            $croppie.croppie({
                url: e.target.result,
                viewport: { width: 100, height: 100 },
                boundary: { width: 400, height: 300 },
            });
            todo();
        };
        reader.readAsDataURL(file);
    };
    UIElements.addAvatar = function (common, cb) {
        var AVATAR_SIZE_LIMIT = 0.5;
        var allowedMediaTypes = [
            'image/png',
            'image/jpeg',
            'image/jpg',
            'image/gif',
        ];
        var fmConfig = {
            noHandlers: true,
            noStore: true,
            body: $('body'),
            onUploaded: cb
        };
        var FM = common.createFileManager(fmConfig);
        var accepted = ".gif,.jpg,.jpeg,.png";
        var data = {
            FM: FM,
            filter: function (file) {
                var sizeMB = Util.bytesToMegabytes(file.size);
                var type = file.type;
                // We can't resize .gif so we have to display an error if it is too big
                if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') {
                    UI.log(Messages._getKey('profile_uploadSizeError', [
                        Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT])
                    ]));
                    return false;
                }
                // Display an error if the image type is not allowed
                if (allowedMediaTypes.indexOf(type) === -1) {
                    UI.log(Messages._getKey('profile_uploadTypeError', [
                        accepted.split(',').join(', ')
                    ]));
                    return false;
                }
                return true;
            },
            transformer: transformAvatar,
            accept: accepted
        };
        return data;
    };

    /*  Create a usage bar which keeps track of how much storage space is used
        by your CryptDrive. The getPinnedUsage RPC is one of the heavier calls,
        so we throttle its usage. Clients will not update more than once per
        LIMIT_REFRESH_RATE. It will be update at least once every three such intervals
        If changes are made to your drive in the interim, they will trigger an
        update.
    */
    // NOTE: The callback must stay SYNCHRONOUS
    var LIMIT_REFRESH_RATE = 30000; // milliseconds
    UIElements.createUsageBar = function (common, teamId, cb) {
        if (AppConfig.hideUsageBar) { return cb('USAGE_BAR_HIDDEN'); }
        if (!common.isLoggedIn()) { return cb("NOT_LOGGED_IN"); }
        // getPinnedUsage updates common.account.usage, and other values
        // so we can just use those and only check for errors
        var $container = $('<span>', {'class':'cp-limit-container'});
        var todo = function (err, data) {
            if (err || !data) { return void console.error(err || 'No data'); }

            var usage = data.usage;
            var limit = data.limit;
            var plan = data.plan;
            $container.html('');
            var unit = Util.magnitudeOfBytes(limit);

            usage = unit === 'GB'? Util.bytesToGigabytes(usage):
                Util.bytesToMegabytes(usage);
            limit = unit === 'GB'? Util.bytesToGigabytes(limit):
                Util.bytesToMegabytes(limit);

            var $limit = $('<span>', {'class': 'cp-limit-bar'}).appendTo($container);
            var quota = usage/limit;
            var $usage = $('<span>', {'class': 'cp-limit-usage'}).css('width', quota*100+'%');
            var $buttons = $(h('span.cp-limit-buttons')).appendTo($container);

            var urls = common.getMetadataMgr().getPrivateData().accounts;
            var makeDonateButton = function () {
                var $a = $('<a>', {
                    'class': 'cp-limit-upgrade btn btn-primary',
                    href: urls.donateURL,
                    rel: "noreferrer noopener",
                    target: "_blank",
                }).text(Messages.crowdfunding_button2).appendTo($buttons);
                $a.click(function () {
                    Feedback.send('SUPPORT_CRYPTPAD');
                });
            };

            var makeUpgradeButton = function () {
                var $a = $('<a>', {
                    'class': 'cp-limit-upgrade btn btn-success',
                    href: urls.upgradeURL,
                    rel: "noreferrer noopener",
                    target: "_blank",
                }).text(Messages.upgradeAccount).appendTo($buttons);
                $a.click(function () {
                    Feedback.send('UPGRADE_ACCOUNT');
                });
            };

            if (!Config.removeDonateButton) {
                if (!common.isLoggedIn() || !Config.allowSubscriptions) {
                    // user is not logged in, or subscriptions are disallowed
                    makeDonateButton();
                } else if (!plan) {
                    // user is logged in and subscriptions are allowed
                    // and they don't have one. show upgrades
                    makeDonateButton();
                    makeUpgradeButton();
                } else {
                    // they have a plan. show nothing
                }
            }

            var prettyUsage;
            var prettyLimit;

            if (unit === 'GB') {
                prettyUsage = Messages._getKey('formattedGB', [usage]);
                prettyLimit = Messages._getKey('formattedGB', [limit]);
            } else {
                prettyUsage = Messages._getKey('formattedMB', [usage]);
                prettyLimit = Messages._getKey('formattedMB', [limit]);
            }

            if (quota < 0.8) { $usage.addClass('cp-limit-usage-normal'); }
            else if (quota < 1) { $usage.addClass('cp-limit-usage-warning'); }
            else { $usage.addClass('cp-limit-usage-above'); }
            var $text = $('<span>', {'class': 'cp-limit-usage-text'});
            $text.html(Messages._getKey('storageStatus', [prettyUsage, prettyLimit]));
            $container.prepend($text);
            $limit.append($usage);
        };

        var updateUsage = Util.notAgainForAnother(function () {
            common.getPinUsage(teamId, todo);
        }, LIMIT_REFRESH_RATE);

        var interval = setInterval(function () {
            updateUsage();
        }, LIMIT_REFRESH_RATE * 3);

        updateUsage();
        cb(null, $container);
        return {
            stop: function () {
                clearInterval(interval);
            }
        };
    };

    // Create a button with a dropdown menu
    // input is a config object with parameters:
    //  - container (optional): the dropdown container (span)
    //  - text (optional): the button text value
    //  - options: array of {tag: "", attributes: {}, content: "string"}
    //
    // allowed options tags: ['a', 'hr', 'p']
    UIElements.createDropdown = function (config) {
        if (typeof config !== "object" || !Array.isArray(config.options)) { return; }
        if (config.feedback && !config.common) { return void console.error("feedback in a dropdown requires sframe-common"); }

        var isElement = function (o) {
            return /HTML/.test(Object.prototype.toString.call(o)) &&
                typeof(o.tagName) === 'string';
        };
        var allowedTags = ['a', 'p', 'hr', 'div'];
        var isValidOption = function (o) {
            if (typeof o !== "object") { return false; }
            if (isElement(o)) { return true; }
            if (!o.tag || allowedTags.indexOf(o.tag) === -1) { return false; }
            return true;
        };

        // Container
        var $container = $(config.container);
        var containerConfig = {
            'class': 'cp-dropdown-container'
        };
        if (config.buttonTitle) {
            containerConfig.title = config.buttonTitle;
        }

        if (!config.container) {
            $container = $('<span>', containerConfig);
        }

        // Button
        var $button = $('<button>', {
            'class': ''
        }).append($('<span>', {'class': 'cp-dropdown-button-title'}).html(config.text || ""));
        /*$('<span>', {
            'class': 'fa fa-caret-down',
        }).appendTo($button);*/

        // Menu
        var $innerblock = $('<div>', {'class': 'cp-dropdown-content'});
        if (config.left) { $innerblock.addClass('cp-dropdown-left'); }

        config.options.forEach(function (o) {
            if (!isValidOption(o)) { return; }
            if (isElement(o)) { return $innerblock.append($(o)); }
            $('<' + o.tag + '>', o.attributes || {}).html(o.content || '').appendTo($innerblock);
        });

        $container.append($button).append($innerblock);

        var value = config.initialValue || '';

        var setActive = function ($el) {
            if ($el.length !== 1) { return; }
            $innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
            $el.addClass('cp-dropdown-element-active');
            var scroll = $el.position().top + $innerblock.scrollTop();
            if (scroll < $innerblock.scrollTop()) {
                $innerblock.scrollTop(scroll);
            } else if (scroll > ($innerblock.scrollTop() + 280)) {
                $innerblock.scrollTop(scroll-270);
            }
        };

        var hide = function () {
            window.setTimeout(function () { $innerblock.hide(); }, 0);
        };

        var show = function () {
            var wh = $(window).height();
            var topPos = $container[0].getBoundingClientRect().bottom;
            $innerblock.css('max-height', Math.floor(wh - topPos - 1)+'px');
            $innerblock.show();
            $innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
            if (config.isSelect && value) {
                var $val = $innerblock.find('[data-value="'+value+'"]');
                setActive($val);
                $innerblock.scrollTop($val.position().top + $innerblock.scrollTop());
            }
            if (config.feedback) { Feedback.send(config.feedback); }
        };

        $container.click(function (e) {
            e.stopPropagation();
            var state = $innerblock.is(':visible');
            $('.cp-dropdown-content').hide();
            try {
                $('iframe').each(function (idx, ifrw) {
                    $(ifrw).contents().find('.cp-dropdown-content').hide();
                });
            } catch (er) {
                // empty try catch in case this iframe is problematic (cross-origin)
            }
            if (state) {
                hide();
                return;
            }
            show();
        });

        if (config.isSelect) {
            var pressed = '';
            var to;
            $container.keydown(function (e) {
                var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible');
                if (e.which === 38) { // Up
                    if ($value.length) {
                        $value.mouseleave();
                        var $prev = $value.prev();
                        $prev.mouseenter();
                        setActive($prev);
                    }
                }
                if (e.which === 40) { // Down
                    if ($value.length) {
                        $value.mouseleave();
                        var $next = $value.next();
                        $next.mouseenter();
                        setActive($next);
                    }
                }
                if (e.which === 13) { //Enter
                    if ($value.length) {
                        $value.click();
                        hide();
                    }
                }
                if (e.which === 27) { // Esc
                    $value.mouseleave();
                    hide();
                }
            });
            $container.keypress(function (e) {
                window.clearTimeout(to);
                var c = String.fromCharCode(e.which);
                pressed += c;
                var $value = $innerblock.find('[data-value^="'+pressed+'"]:first');
                if ($value.length) {
                    setActive($value);
                    $innerblock.scrollTop($value.position().top + $innerblock.scrollTop());
                }
                to = window.setTimeout(function () {
                    pressed = '';
                }, 1000);
            });

            $container.setValue = function (val, name) {
                value = val;
                var $val = $innerblock.find('[data-value="'+val+'"]');
                var textValue = name || $val.html() || val;
                $button.find('.cp-dropdown-button-title').html(textValue);
            };
            $container.getValue = function () {
                return value || '';
            };
        }

        return $container;
    };

    UIElements.createUserAdminMenu = function (Common, config) {
        var metadataMgr = Common.getMetadataMgr();

        var displayNameCls = config.displayNameCls || 'cp-toolbar-user-name';
        var $displayedName = $('<span>', {'class': displayNameCls});

        var priv = metadataMgr.getPrivateData();
        var accountName = priv.accountName;
        var origin = priv.origin;
        var padType = metadataMgr.getMetadata().type;

        var $userName = $('<span>');
        var options = [];
        if (config.displayNameCls) {
            var $userAdminContent = $('<p>');
            if (accountName) {
                var $userAccount = $('<span>').append(Messages.user_accountName + ': ');
                $userAdminContent.append($userAccount).append(Util.fixHTML(accountName));
                $userAdminContent.append($('<br>'));
            }
            if (config.displayName && !AppConfig.disableProfile) {
                // Hide "Display name:" in read only mode
                $userName.append(Messages.user_displayName + ': ');
                $userName.append($displayedName);
            }
            $userAdminContent.append($userName);
            options.push({
                tag: 'p',
                attributes: {'class': 'cp-toolbar-account'},
                content: $userAdminContent.html()
            });
        }
        options.push({
            tag: 'a',
            attributes: {
                'target': '_blank',
                'href': origin+'/index.html',
                'class': 'fa fa-home'
            },
            content: h('span', Messages.homePage)
        });
        if (padType !== 'drive' || (!accountName && priv.newSharedFolder)) {
            options.push({
                tag: 'a',
                attributes: {
                    'target': '_blank',
                    'href': origin+'/drive/',
                    'class': 'fa fa-hdd-o'
                },
                content: h('span', Messages.type.drive)
            });
        }
        if (padType !== 'teams' && accountName) {
            options.push({
                tag: 'a',
                attributes: {
                    'target': '_blank',
                    'href': origin+'/teams/',
                    'class': 'fa fa-users'
                },
                content: h('span', Messages.type.teams)
            });
        }
        options.push({ tag: 'hr' });
        // Add the change display name button if not in read only mode
        if (config.changeNameButtonCls && config.displayChangeName && !AppConfig.disableProfile) {
            options.push({
                tag: 'a',
                attributes: {'class': config.changeNameButtonCls + ' fa fa-user'},
                content: h('span', Messages.user_rename)
            });
        }
        if (accountName && !AppConfig.disableProfile) {
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-profile fa fa-user-circle'},
                content: h('span', Messages.profileButton)
            });
        }
        if (padType !== 'settings') {
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-settings fa fa-cog'},
                content: h('span', Messages.settingsButton)
            });
        }
        options.push({ tag: 'hr' });
        // Add administration panel link if the user is an admin
        if (priv.edPublic && Array.isArray(Config.adminKeys) && Config.adminKeys.indexOf(priv.edPublic) !== -1) {
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-admin fa fa-cogs'},
                content: h('span', Messages.adminPage || 'Admin')
            });
        }
        if (padType !== 'support' && accountName && Config.supportMailbox) {
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-support fa fa-life-ring'},
                content: h('span', Messages.supportPage || 'Support')
            });
        }
        options.push({ tag: 'hr' });
        if (Config.allowSubscriptions) {
            options.push({
                tag: 'a',
                attributes: {
                    'target': '_blank',
                    'href': priv.plan ? priv.accounts.upgradeURL : origin+'/features.html',
                    'class': 'fa fa-star-o'
                },
                content: h('span', priv.plan ? Messages.settings_cat_subscription : Messages.pricing)
            });
        }
        if (!priv.plan && !Config.removeDonateButton) {
            options.push({
                tag: 'a',
                attributes: {
                    'target': '_blank',
                    'rel': 'noopener',
                    'href': priv.accounts.donateURL,
                    'class': 'fa fa-gift'
                },
                content: h('span', Messages.crowdfunding_button2)
            });
        }
        if (AppConfig.surveyURL) {
            options.push({
                tag: 'a',
                attributes: {
                    'target': '_blank',
                    'rel': 'noopener',
                    'href': AppConfig.surveyURL,
                    'class': 'cp-toolbar-survey fa fa-graduation-cap'
                },
                content: h('span', Messages.survey)
            });
        }
        options.push({ tag: 'hr' });
        // Add login or logout button depending on the current status
        if (accountName) {
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-logout fa fa-sign-out'},
                content: h('span', Messages.logoutButton)
            });
        } else {
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-login fa fa-sign-in'},
                content: h('span', Messages.login_login)
            });
            options.push({
                tag: 'a',
                attributes: {'class': 'cp-toolbar-menu-register fa fa-user-plus'},
                content: h('span', Messages.login_register)
            });
        }
        var $icon = $('<span>', {'class': 'fa fa-user-secret'});
        //var $userbig = $('<span>', {'class': 'big'}).append($displayedName.clone());
        var $userButton = $('<div>').append($icon);//.append($userbig);
        if (accountName) {
            $userButton = $('<div>').append(accountName);
        }
        /*if (account && config.displayNameCls) {
            $userbig.append($('<span>', {'class': 'account-name'}).text('(' + accountName + ')'));
        } else if (account) {
            // If no display name, do not display the parentheses
            $userbig.append($('<span>', {'class': 'account-name'}).text(accountName));
        }*/
        var dropdownConfigUser = {
            text: $userButton.html(), // Button initial text
            options: options, // Entries displayed in the menu
            left: true, // Open to the left of the button
            container: config.$initBlock, // optional
            feedback: "USER_ADMIN",
            common: Common
        };
        var $userAdmin = UIElements.createDropdown(dropdownConfigUser);

        /*
        // Uncomment these lines to have a language selector in the admin menu
        // FIXME clicking on the inner menu hides the outer one
        var $lang = UIElements.createLanguageSelector(Common);
        $userAdmin.find('.cp-dropdown-content').append($lang);
        */

        var $displayName = $userAdmin.find('.'+displayNameCls);

        var $avatar = $userAdmin.find('> button .cp-dropdown-button-title');
        var loadingAvatar;
        var to;
        var oldUrl = '';
        var updateButton = function () {
            var myData = metadataMgr.getUserData();
            if (!myData) { return; }
            if (loadingAvatar) {
                // Try again in 200ms
                window.clearTimeout(to);
                to = window.setTimeout(updateButton, 200);
                return;
            }
            loadingAvatar = true;
            var newName = myData.name;
            var url = myData.avatar;
            $displayName.text(newName || Messages.anonymous);
            if (accountName && oldUrl !== url) {
                $avatar.html('');
                UIElements.displayAvatar(Common, $avatar, url,
                        newName || Messages.anonymous, function ($img) {
                    oldUrl = url;
                    $userAdmin.find('> button').removeClass('cp-avatar');
                    if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); }
                    loadingAvatar = false;
                });
                return;
            }
            loadingAvatar = false;
        };
        metadataMgr.onChange(updateButton);
        updateButton();

        $userAdmin.find('a.cp-toolbar-menu-logout').click(function () {
            Common.logout(function () {
                window.parent.location = origin+'/';
            });
        });
        $userAdmin.find('a.cp-toolbar-menu-settings').click(function () {
            if (padType) {
                window.open(origin+'/settings/');
            } else {
                window.parent.location = origin+'/settings/';
            }
        });
        $userAdmin.find('a.cp-toolbar-menu-support').click(function () {
            if (padType) {
                window.open(origin+'/support/');
            } else {
                window.parent.location = origin+'/support/';
            }
        });
        $userAdmin.find('a.cp-toolbar-menu-admin').click(function () {
            if (padType) {
                window.open(origin+'/admin/');
            } else {
                window.parent.location = origin+'/admin/';
            }
        });
        $userAdmin.find('a.cp-toolbar-survey').click(function () {
            Feedback.send('SURVEY_CLICKED');
        });
        $userAdmin.find('a.cp-toolbar-menu-profile').click(function () {
            if (padType) {
                window.open(origin+'/profile/');
            } else {
                window.parent.location = origin+'/profile/';
            }
        });
        $userAdmin.find('a.cp-toolbar-menu-login').click(function () {
            Common.setLoginRedirect(function () {
                window.parent.location = origin+'/login/';
            });
        });
        $userAdmin.find('a.cp-toolbar-menu-register').click(function () {
            Common.setLoginRedirect(function () {
                window.parent.location = origin+'/register/';
            });
        });

        return $userAdmin;
    };

    // Provide $container if you want to put the generated block in another element
    // Provide $initBlock if you already have the menu block and you want the content inserted in it
    UIElements.createLanguageSelector = function (common, $container, $initBlock) {
        var options = [];
        var languages = Messages._languages;
        var keys = Object.keys(languages).sort();
        keys.forEach(function (l) {
            options.push({
                tag: 'a',
                attributes: {
                    'class': 'cp-language-value',
                    'data-value': l,
                    'href': '#',
                },
                content: languages[l] // Pretty name of the language value
            });
        });
        var dropdownConfig = {
            text: Messages.language, // Button initial text
            options: options, // Entries displayed in the menu
            //left: true, // Open to the left of the button
            container: $initBlock, // optional
            isSelect: true,
            common: common
        };
        var $block = UIElements.createDropdown(dropdownConfig);
        $block.attr('id', 'cp-language-selector');

        if ($container) {
            $block.appendTo($container);
        }

        Language.initSelector($block, common);

        return $block;
    };

    UIElements.createModal = function (cfg) {
        var $body = cfg.$body || $('body');
        var $blockContainer = $body.find('#'+cfg.id);
        if (!$blockContainer.length) {
            $blockContainer = $('<div>', {
                'class': 'cp-modal-container',
                tabindex: 1,
                'id': cfg.id
            });
        }
        var hide = function () {
            if (cfg.onClose) { return void cfg.onClose(); }
            $blockContainer.hide();
        };
        $blockContainer.html('').appendTo($body);
        var $block = $('<div>', {'class': 'cp-modal'}).appendTo($blockContainer);
        $('<span>', {
            'class': 'cp-modal-close fa fa-times',
            'title': Messages.filePicker_close
        }).click(hide).appendTo($block);
        $body.click(hide);
        $block.click(function (e) {
            e.stopPropagation();
        });
        $body.keydown(function (e) {
            if (e.which === 27) {
                hide();
            }
        });
        return $blockContainer;
    };

    UIElements.createNewPadModal = function (common) {
        // if in drive, show new pad modal instead
        if ($(".cp-app-drive-element-row.cp-app-drive-new-ghost").length !== 0) {
            return void $(".cp-app-drive-element-row.cp-app-drive-new-ghost").click();
        }

        var $modal = UIElements.createModal({
            id: 'cp-app-toolbar-creation-dialog',
            $body: $('body')
        });
        var $title = $('<h3>').text(Messages.fm_newFile);
        var $description = $('<p>').html(Messages.creation_newPadModalDescription);
        $modal.find('.cp-modal').append($title);
        $modal.find('.cp-modal').append($description);

        var $advanced;

        var $advancedContainer = $('<div>');
        var priv = common.getMetadataMgr().getPrivateData();
        var c = (priv.settings.general && priv.settings.general.creation) || {};
        if (AppConfig.displayCreationScreen && common.isLoggedIn() && c.skip) {
            var $cboxLabel = $(UI.createCheckbox('cp-app-toolbar-creation-advanced',
                                                 Messages.creation_newPadModalAdvanced, true))
                                 .appendTo($advancedContainer);
            $advanced = $cboxLabel.find('input');
            $description.append('<br>');
            $description.append(Messages.creation_newPadModalDescriptionAdvanced);
        }

        var $container = $('<div>');
        var i = 0;
        var types = AppConfig.availablePadTypes.filter(function (p) {
            if (p === 'drive') { return; }
            if (p === 'teams') { return; }
            if (p === 'contacts') { return; }
            if (p === 'todo') { return; }
            if (p === 'file') { return; }
            if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes &&
                AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; }
            return true;
        });
        types.forEach(function (p) {
            var $element = $('<li>', {
                'class': 'cp-icons-element',
                'id': 'cp-newpad-icons-'+ (i++)
            }).prepend(UI.getIcon(p)).appendTo($container);
            $element.append($('<span>', {'class': 'cp-icons-name'})
                .text(Messages.type[p]));
            $element.attr('data-type', p);
            $element.click(function () {
                $modal.hide();
                if ($advanced && Util.isChecked($advanced)) {
                    common.sessionStorage.put(Constants.displayPadCreationScreen, true, function (){
                        common.openURL('/' + p + '/');
                    });
                    return;
                }
                common.sessionStorage.put(Constants.displayPadCreationScreen, "", function () {
                    common.openURL('/' + p + '/');
                });
            });
        });

        var selected = -1;
        var next = function () {
            selected = ++selected % types.length;
            $container.find('.cp-icons-element-selected').removeClass('cp-icons-element-selected');
            $container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected');
        };

        $modal.off('keydown');
        $modal.keydown(function (e) {
            if (e.which === 9) {
                e.preventDefault();
                e.stopPropagation();
                next();
                return;
            }
            if (e.which === 13) {
                if ($container.find('.cp-icons-element-selected').length === 1) {
                    $container.find('.cp-icons-element-selected').click();
                }
                return;
            }
            if (e.which === 32 && $advanced) {
                $advanced.prop('checked', !$advanced.prop('checked'));
                $modal.focus();
                e.stopPropagation();
                e.preventDefault();
            }
        });


        $modal.find('.cp-modal').append($container).append($advancedContainer);
        window.setTimeout(function () {
            $modal.show();
            $modal.focus();
        });
    };

    UIElements.initFilePicker = function (common, cfg) {
        var onSelect = cfg.onSelect || $.noop;
        var sframeChan = common.getSframeChannel();
        sframeChan.on("EV_FILE_PICKED", function (data) {
            onSelect(data);
        });
    };
    UIElements.openFilePicker = function (common, types) {
        var sframeChan = common.getSframeChannel();
        sframeChan.event("EV_FILE_PICKER_OPEN", types);
    };

    UIElements.openTemplatePicker = function (common, force) {
        var metadataMgr = common.getMetadataMgr();
        var type = metadataMgr.getMetadataLazy().type;
        var sframeChan = common.getSframeChannel();
        var focus;

        var pickerCfgInit = {
            types: [type],
            where: ['template'],
            hidden: true
        };
        var pickerCfg = {
            types: [type],
            where: ['template'],
        };
        var onConfirm = function (yes) {
            if (!yes) {
                if (focus) { focus.focus(); }
                return;
            }
            delete pickerCfg.hidden;
            common.openFilePicker(pickerCfg);
            var first = true; // We can only pick a template once (for a new document)
            var fileDialogCfg = {
                onSelect: function (data) {
                    if (data.type === type && first) {
                        UI.addLoadingScreen({hideTips: true});
                        var chatChan = common.getPadChat();
                        var cursorChan = common.getCursorChannel();
                        sframeChan.query('Q_TEMPLATE_USE', {
                            href: data.href,
                            chat: chatChan,
                            cursor: cursorChan
                        }, function () {
                            first = false;
                            UI.removeLoadingScreen();
                            Feedback.send('TEMPLATE_USED');
                        });
                        if (focus) { focus.focus(); }
                        return;
                    }
                }
            };
            common.initFilePicker(fileDialogCfg);
        };

        sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {
            if (data) {
                common.openFilePicker(pickerCfgInit);
                focus = document.activeElement;
                if (force) { return void onConfirm(true); }
                UI.confirm(Messages.useTemplate, onConfirm, {
                    ok: Messages.useTemplateOK,
                    cancel: Messages.useTemplateCancel,
                });
            } else if (force) {
                UI.alert(Messages.template_empty);
            }
        });
    };

    UIElements.setExpirationValue = function (val, $expire) {
        if (val && typeof (val) === "number") {
            $expire.find('#cp-creation-expire').attr('checked', true).trigger('change');
            $expire.find('#cp-creation-expire-true').attr('checked', true);
            if (val % (3600 * 24 * 30) === 0) {
                $expire.find('#cp-creation-expire-unit').val("month");
                $expire.find('#cp-creation-expire-val').val(val / (3600 * 24 * 30));
                return;
            }
            if (val % (3600 * 24) === 0) {
                $expire.find('#cp-creation-expire-unit').val("day");
                $expire.find('#cp-creation-expire-val').val(val / (3600 * 24));
                return;
            }
            if (val % 3600 === 0) {
                $expire.find('#cp-creation-expire-unit').val("hour");
                $expire.find('#cp-creation-expire-val').val(val / 3600);
                return;
            }
            // if we're here, it means we don't have a valid value so we should check unlimited
            $expire.find('#cp-creation-expire-false').attr('checked', true);
        }
    };
    UIElements.getPadCreationScreen = function (common, cfg, appCfg, cb) {
        appCfg = appCfg || {};
        if (!common.isLoggedIn()) { return void cb(); }
        var sframeChan = common.getSframeChannel();
        var metadataMgr = common.getMetadataMgr();
        var privateData = metadataMgr.getPrivateData();
        var type = metadataMgr.getMetadataLazy().type;
        var fromFileData = privateData.fromFileData;

        var $body = $('body');
        var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
        var urlArgs = (Config.requireConf && Config.requireConf.urlArgs) || '';
        var l = h('div.cp-creation-logo', h('img', { src: '/customize/loading-logo.png?' + urlArgs }));
        $(l).appendTo($creationContainer);
        var $creation = $('<div>', { id: 'cp-creation', tabindex: 1 }).appendTo($creationContainer);

        // Title
        //var colorClass = 'cp-icon-color-'+type;
        //$creation.append(h('h2.cp-creation-title', Messages.newButtonTitle));
        var newPadH3Title = Messages['button_new' + type];
        $creation.append(h('h3.cp-creation-title', newPadH3Title));
        //$creation.append(h('h2.cp-creation-title.'+colorClass, Messages.newButtonTitle));

        // Deleted pad warning
        if (metadataMgr.getPrivateData().isDeleted) {
            $creation.append(h('div.cp-creation-deleted-container',
                h('div.cp-creation-deleted', Messages.creation_404)
            ));
        }

        var origin = privateData.origin;
        var createHelper = function (href, text) {
            var q = h('a.cp-creation-help.fa.fa-question-circle', {
                title: text,
                href: origin + href,
                target: "_blank",
                'data-tippy-placement': "right"
            });
            return q;
        };

        // Team pad
        var team;
        var teamExists = privateData.teams && Object.keys(privateData.teams).length;
        var teamValue;
        // storeInTeam can be
        // * a team ID ==> store in the team drive, and the team will be the owner
        // * -1 ==> store in the user drive, and the user will be the owner
        // * undefined ==> ask
        if (teamExists) {
            var teams = Object.keys(privateData.teams).map(function (id) {
                var data = privateData.teams[id];
                var avatar = h('span.cp-creation-team-avatar.cp-avatar');
                UIElements.displayAvatar(common, $(avatar), data.avatar, data.name);
                return h('div.cp-creation-team', {
                    'data-id': id,
                    title: data.name,
                },[
                    avatar,
                    h('span.cp-creation-team-name', data.name)
                ]);
            });
            teams.unshift(h('div.cp-creation-team', {
                'data-id': '-1',
                title: Messages.settings_cat_drive
            }, [
                h('span.cp-creation-team-avatar.fa.fa-hdd-o'),
                h('span.cp-creation-team-name', Messages.settings_cat_drive)
            ]));
            team = h('div.cp-creation-teams', [
                Messages.team_pcsSelectLabel,
                h('div.cp-creation-teams-grid', teams),
                createHelper('#', Messages.team_pcsSelectHelp)
            ]);
            var $team = $(team);
            $team.find('.cp-creation-team').click(function () {
                if ($(this).hasClass('cp-selected')) {
                    teamValue = undefined;
                    return void $(this).removeClass('cp-selected');
                }
                $team.find('.cp-creation-team').removeClass('cp-selected');
                $(this).addClass('cp-selected');
                teamValue = $(this).attr('data-id');
            });
            if (privateData.storeInTeam) {
                $team.find('[data-id="'+privateData.storeInTeam+'"]').addClass('cp-selected');
                teamValue = privateData.storeInTeam;
            }
        }


        // Owned pads
        // Default is Owned pad
        var owned = h('div.cp-creation-owned', [
            UI.createCheckbox('cp-creation-owned', Messages.creation_owned, true),
            createHelper('/faq.html#keywords-owned', Messages.creation_owned1)
        ]);

        // Life time
        var expire = h('div.cp-creation-expire', [
            UI.createCheckbox('cp-creation-expire', Messages.creation_expire, false),
            h('span.cp-creation-expire-picker.cp-creation-slider', [
                h('input#cp-creation-expire-val', {
                    type: "number",
                    min: 1,
                    max: 100,
                    value: 3
                }),
                h('select#cp-creation-expire-unit', [
                    h('option', { value: 'hour' }, Messages.creation_expireHours),
                    h('option', { value: 'day' }, Messages.creation_expireDays),
                    h('option', {
                        value: 'month',
                        selected: 'selected'
                    }, Messages.creation_expireMonths)
                ])
            ]),
            createHelper('/faq.html#keywords-expiring', Messages.creation_expire2),
        ]);

        // Password
        var password = h('div.cp-creation-password', [
            UI.createCheckbox('cp-creation-password', Messages.creation_password, false),
            h('span.cp-creation-password-picker.cp-creation-slider', [
                UI.passwordInput({id: 'cp-creation-password-val'})
                /*h('input#cp-creation-password-val', {
                    type: "text" // TODO type password with click to show
                }),*/
            ]),
            //createHelper('#', "TODO: password protection adds another layer of security ........") // TODO
        ]);

        var right = h('span.fa.fa-chevron-right.cp-creation-template-more');
        var left = h('span.fa.fa-chevron-left.cp-creation-template-more');
        var templates = h('div.cp-creation-template', [
            left,
            h('div.cp-creation-template-container', [
                h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')
            ]),
            right
        ]);

        var settings = h('div.cp-creation-remember', [
            UI.createCheckbox('cp-creation-remember', Messages.creation_saveSettings, false),
            createHelper('/settings/#creation', Messages.creation_settings),
            h('div.cp-creation-remember-help.cp-creation-slider', [
                h('span.fa.fa-exclamation-circle.cp-creation-warning'),
                Messages.creation_rememberHelp
            ])
        ]);

        var createDiv = h('div.cp-creation-create');
        var $create = $(createDiv);

        $(h('div#cp-creation-form', [
            team,
            owned,
            expire,
            password,
            settings,
            templates,
            createDiv
        ])).appendTo($creation);

        // Display templates

        var selected = 0; // Selected template in the list (highlighted)
        var TEMPLATES_DISPLAYED = 4; // Max templates displayed per page
        var next = function () {}; // Function called when pressing tab to highlight the next template
        var i = 0; // Index of the first template displayed in the current page
        sframeChan.query("Q_CREATE_TEMPLATES", type, function (err, res) {
            if (!res.data || !Array.isArray(res.data)) {
                return void console.error("Error: get the templates list");
            }
            var allData = res.data.slice().sort(function (a, b) {
                if (a.used === b.used) {
                    // Sort by name
                    if (a.name === b.name) { return 0; }
                    return a.name < b.name ? -1 : 1;
                }
                return b.used - a.used;
            });
            if (!appCfg.noTemplates) {
                allData.unshift({
                    name: Messages.creation_newTemplate,
                    id: -1,
                    //icon: h('span.fa.fa-bookmark')
                    icon: h('span.cptools.cptools-new-template')
                });
            }
            allData.unshift({
                name: Messages.creation_noTemplate,
                id: 0,
                //icon: h('span.fa.fa-file')
                icon: UI.getFileIcon({type: type})
            });
            var redraw = function (index) {
                if (index < 0) { i = 0; }
                else if (index > allData.length - 1) { return; }
                else { i = index; }
                var data = allData.slice(i, i + TEMPLATES_DISPLAYED);
                var $container = $(templates).find('.cp-creation-template-container').html('');
                data.forEach(function (obj, idx) {
                    var name = obj.name;
                    var $span = $('<span>', {
                        'class': 'cp-creation-template-element',
                        'title': name,
                    }).appendTo($container);
                    $span.data('id', obj.id);
                    if (idx === selected) { $span.addClass('cp-creation-template-selected'); }
                    if (!obj.thumbnail) {
                        $span.append(obj.icon || h('span.cptools.cptools-template'));
                    }
                    $('<span>', {'class': 'cp-creation-template-element-name'}).text(name)
                        .appendTo($span);
                    $span.click(function () {
                        $container.find('.cp-creation-template-selected')
                            .removeClass('cp-creation-template-selected');
                        $span.addClass('cp-creation-template-selected');
                        selected = idx;
                    });

                    // Add thumbnail if it exists
                    if (obj.thumbnail) {
                        common.addThumbnail(obj.thumbnail, $span, function () {});
                    }
                });
                $(right).off('click').removeClass('hidden').click(function () {
                    selected = 0;
                    redraw(i + TEMPLATES_DISPLAYED);
                });
                if (i >= allData.length - TEMPLATES_DISPLAYED ) { $(right).addClass('hidden'); }
                $(left).off('click').removeClass('hidden').click(function () {
                    selected = TEMPLATES_DISPLAYED - 1;
                    redraw(i - TEMPLATES_DISPLAYED);
                });
                if (i < TEMPLATES_DISPLAYED) { $(left).addClass('hidden'); }
            };
            if (fromFileData) {
                var todo = function (thumbnail) {
                    allData = [{
                        name: fromFileData.title,
                        id: 0,
                        thumbnail: thumbnail,
                        icon: h('span.cptools.cptools-file'),
                    }];
                    redraw(0);
                };
                todo();
                sframeChan.query("Q_GET_FILE_THUMBNAIL", null, function (err, res) {
                    if (err || (res && res.error)) { return; }
                    todo(res.data);
                });
            }
            else {
                redraw(0);
            }


            // Change template selection when Tab is pressed
            next = function (revert) {
                var max = $creation.find('.cp-creation-template-element').length;
                if (selected + 1 === max && !revert) {
                    selected = i + TEMPLATES_DISPLAYED < allData.length ? 0 : max;
                    return void redraw(i + TEMPLATES_DISPLAYED);
                }
                if (selected === 0 && revert) {
                    selected = i - TEMPLATES_DISPLAYED >= 0 ? TEMPLATES_DISPLAYED - 1 : 0;
                    return void redraw(i - TEMPLATES_DISPLAYED);
                }
                selected = revert ?
                            (--selected < 0 ? 0 : selected) :
                            ++selected >= max ? max-1 : selected;
                $creation.find('.cp-creation-template-element')
                    .removeClass('cp-creation-template-selected');
                $($creation.find('.cp-creation-template-element').get(selected))
                    .addClass('cp-creation-template-selected');
            };

        });

        // Display expiration form when checkbox checked
        $creation.find('#cp-creation-expire').on('change', function () {
            if ($(this).is(':checked')) {
                $creation.find('.cp-creation-expire-picker:not(.active)').addClass('active');
                $creation.find('.cp-creation-expire:not(.active)').addClass('active');
                $creation.find('#cp-creation-expire-val').focus();
                return;
            }
            $creation.find('.cp-creation-expire-picker').removeClass('active');
            $creation.find('.cp-creation-expire').removeClass('active');
            $creation.focus();
        });

        // Display password form when checkbox checked
        $creation.find('#cp-creation-password').on('change', function () {
            if ($(this).is(':checked')) {
                $creation.find('.cp-creation-password-picker:not(.active)').addClass('active');
                $creation.find('.cp-creation-password:not(.active)').addClass('active');
                $creation.find('#cp-creation-password-val').focus();
                return;
            }
            $creation.find('.cp-creation-password-picker').removeClass('active');
            $creation.find('.cp-creation-password').removeClass('active');
            $creation.focus();
        });

        // Display settings help when checkbox checked
        $creation.find('#cp-creation-remember').on('change', function () {
            if ($(this).is(':checked')) {
                $creation.find('.cp-creation-remember-help:not(.active)').addClass('active');
                return;
            }
            $creation.find('.cp-creation-remember-help').removeClass('active');
            $creation.focus();
        });

        // Keyboard shortcuts
        $creation.find('#cp-creation-expire-val').keydown(function (e) {
            if (e.which === 9) {
                e.stopPropagation();
            }
        });
        $creation.find('#cp-creation-expire-unit').keydown(function (e) {
            if (e.which === 9 && e.shiftKey) {
                e.stopPropagation();
            }
        });


        // Initial values
        if (!cfg.owned && typeof cfg.owned !== "undefined") {
            $creation.find('#cp-creation-owned').prop('checked', false);
        }
        if (cfg.skip) {
            $creation.find('#cp-creation-remember').prop('checked', true).trigger('change');
        }
        UIElements.setExpirationValue(cfg.expire, $creation);

        // Create the pad
        var getFormValues = function () {
            // Type of pad
            var ownedVal = $('#cp-creation-owned').is(':checked') ? 1 : 0;
            // Life time
            var expireVal = 0;
            if($('#cp-creation-expire').is(':checked')) {
                var unit = 0;
                switch ($('#cp-creation-expire-unit').val()) {
                    case "hour" : unit = 3600;           break;
                    case "day"  : unit = 3600 * 24;      break;
                    case "month": unit = 3600 * 24 * 30; break;
                    default: unit = 0;
                }
                expireVal = ($('#cp-creation-expire-val').val() || 0) * unit;
            }
            // Password
            var passwordVal = $('#cp-creation-password').is(':checked') ?
                                $('#cp-creation-password-val').val() : undefined;

            var $template = $creation.find('.cp-creation-template-selected');
            var templateId = $template.data('id') || undefined;
            // Team
            var team;
            if (teamValue) {
                team = privateData.teams[teamValue] || {};
                team.id = Number(teamValue);
            }

            return {
                owned: ownedVal,
                password: passwordVal,
                expire: expireVal,
                templateId: templateId,
                team: team
            };
        };
        var create = function () {
            var val = getFormValues();

            var skip = $('#cp-creation-remember').is(':checked');
            common.setAttribute(['general', 'creation', 'skip'], skip, function (e) {
                if (e) { return void console.error(e); }
            });
            common.setAttribute(['general', 'creation', 'noTemplate'], skip, function (e) {
                if (e) { return void console.error(e); }
            });

            common.setAttribute(['general', 'creation', 'owned'], val.owned, function (e) {
                if (e) { return void console.error(e); }
            });
            common.setAttribute(['general', 'creation', 'expire'], val.expire, function (e) {
                if (e) { return void console.error(e); }
            });

            if (val.expire) {
                Feedback.send('EXPIRING_PAD-'+val.expire);
            }

            $creationContainer.remove();
            common.createPad(val, function () {
                cb();
            });
        };


        var $button = $('<button>').text(Messages.creation_create).appendTo($create);
        $button.addClass('cp-creation-button-selected');
        $button.click(function () {
            create();
        });

        $creation.keydown(function (e) {
            if (e.which === 9) {
                e.preventDefault();
                e.stopPropagation();
                next(e.shiftKey);
                return;
            }
            if (e.which === 13) {
                $button.click();
                return;
            }
        });
        $creation.focus();
    };

    UIElements.onServerError = function (common, err, toolbar, cb) {
        if (["EDELETED", "EEXPIRED"].indexOf(err.type) === -1) { return; }
        var msg = err.type;
        if (err.type === 'EEXPIRED') {
            msg = Messages.expiredError;
            if (err.loaded) {
                msg += Messages.errorCopy;
            }
        } else if (err.type === 'EDELETED') {
            msg = Messages.deletedError;
            if (err.loaded) {
                msg += Messages.errorCopy;
            }
        }
        if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
        UI.errorLoadingScreen(msg, true, true);
        (cb || function () {})();
    };

    UIElements.displayPasswordPrompt = function (common, cfg, isError) {
        var error;
        if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); }
        var info = h('p.cp-password-info', Messages.password_info);
        var password = UI.passwordInput({placeholder: Messages.password_placeholder});
        var $password = $(password);
        var button = h('button', Messages.password_submit);
        cfg = cfg || {};

        if (cfg.value && !isError) {
            $password.find('.cp-password-input').val(cfg.value);
        }

        var submit = function () {
            var value = $password.find('.cp-password-input').val();
            UI.addLoadingScreen();
            common.getSframeChannel().query('Q_PAD_PASSWORD_VALUE', value, function (err, data) {
                if (!data) {
                    UIElements.displayPasswordPrompt(common, cfg, true);
                }
            });
        };
        $password.find('.cp-password-input').on('keydown', function (e) { if (e.which === 13) { submit(); } });
        $(button).on('click', function () { submit(); });


        var block = h('div#cp-loading-password-prompt', [
            error,
            info,
            h('p.cp-password-form', [
                password,
                button
            ])
        ]);
        UI.errorLoadingScreen(block);

        $password.find('.cp-password-input').focus();
    };

    var crowdfundingState = false;
    UIElements.displayCrowdfunding = function (common) {
        if (crowdfundingState) { return; }
        if (AppConfig.disableCrowdfundingMessages) { return; }
        var priv = common.getMetadataMgr().getPrivateData();
        if (priv.plan) { return; }

        crowdfundingState = true;
        setTimeout(function () {
            common.getAttribute(['general', 'crowdfunding'], function (err, val) {
                if (err || val === false) { return; }
                common.getSframeChannel().query('Q_GET_PINNED_USAGE', null, function (err, obj) {
                    var quotaMb = obj.quota / (1024 * 1024);
                    if (quotaMb < 10) { return; }
                    // Display the popup
                    var text = Messages.crowdfunding_popup_text;
                    var yes = h('button.cp-corner-primary', Messages.crowdfunding_popup_yes);
                    var no = h('button.cp-corner-primary', Messages.crowdfunding_popup_no);
                    var never = h('button.cp-corner-cancel', Messages.crowdfunding_popup_never);
                    var actions = h('div', [yes, no, never]);

                    var modal = UI.cornerPopup(text, actions, null, {big: true});

                    $(yes).click(function () {
                        modal.delete();
                        common.openURL(priv.accounts.donateURL);
                        Feedback.send('CROWDFUNDING_YES');
                    });
                    $(modal.popup).find('a').click(function (e) {
                        e.stopPropagation();
                        e.preventDefault();
                        modal.delete();
                        common.openURL(priv.accounts.donateURL);
                        Feedback.send('CROWDFUNDING_LINK');
                    });
                    $(no).click(function () {
                        modal.delete();
                        Feedback.send('CROWDFUNDING_NO');
                    });
                    $(never).click(function () {
                        modal.delete();
                        common.setAttribute(['general', 'crowdfunding'], false);
                        Feedback.send('CROWDFUNDING_NEVER');
                    });
                });
            });
        }, 5000);
    };

    var storePopupState = false;
    var autoStoreModal = {};
    UIElements.displayStorePadPopup = function (common, data) {
        if (storePopupState) { return; }
        storePopupState = true;
        if (data && data.stored) { return; } // We won't display the popup for dropped files
        var priv = common.getMetadataMgr().getPrivateData();

        var typeMsg = priv.pathname.indexOf('/file/') !== -1 ? Messages.autostore_file :
                        priv.pathname.indexOf('/drive/') !== -1 ? Messages.autostore_sf :
                          Messages.autostore_pad;
        var text = Messages._getKey('autostore_notstored', [typeMsg]);
        var footer = Messages.autostore_settings;

        var hide = h('button.cp-corner-cancel', Messages.autostore_hide);
        var store = h('button.cp-corner-primary', Messages.autostore_store);
        var actions = h('div', [store, hide]);

        var initialHide = data && data.autoStore && data.autoStore === -1;
        var modal = UI.cornerPopup(text, actions, footer, {hidden: initialHide});

        autoStoreModal[priv.channel] = modal;

        $(modal.popup).find('.cp-corner-footer a').click(function (e) {
            e.preventDefault();
            common.openURL('/settings/');
        });

        $(hide).click(function () {
            UIElements.displayCrowdfunding(common);
            delete autoStoreModal[priv.channel];
            modal.delete();
        });
        var waitingForStoringCb = false;
        $(store).click(function () {
            if (waitingForStoringCb) { return; }
            waitingForStoringCb = true;
            common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) {
                waitingForStoringCb = false;
                var error = err || (obj && obj.error);
                if (error) {
                    if (error === 'E_OVER_LIMIT') {
                        return void UI.warn(Messages.pinLimitReached);
                    }
                    return void UI.warn(Messages.autostore_error);
                }
                $(document).trigger('cpPadStored');
                delete autoStoreModal[priv.channel];
                modal.delete();
                UIElements.displayCrowdfunding(common);
                UI.log(Messages.autostore_saved);
            });
        });

    };

    var createContextMenu = function (menu) {
        var $menu = $(menu).appendTo($('body'));

        var display = function (e) {
            $menu.css({ display: "block" });
            var h = $menu.outerHeight();
            var w = $menu.outerWidth();
            var wH = window.innerHeight;
            var wW = window.innerWidth;
            if (h > wH) {
                $menu.css({
                    top: '0px',
                    bottom: ''
                });
            } else if (e.pageY + h <= wH) {
                $menu.css({
                    top: e.pageY+'px',
                    bottom: ''
                });
            } else {
                $menu.css({
                    bottom: '0px',
                    top: ''
                });
            }
            if(w > wW) {
                $menu.css({
                    left: '0px',
                    right: ''
                });
            } else if (e.pageX + w <= wW) {
                $menu.css({
                    left: e.pageX+'px',
                    right: ''
                });
            } else {
                $menu.css({
                    left: '',
                    right: '0px',
                });
            }
        };

        var hide = function () {
            $menu.hide();
        };
        var remove = function () {
            $menu.remove();
        };

        $('body').click(hide);

        return {
            menu: menu,
            show: display,
            hide: hide,
            remove: remove
        };
    };

    var mediatagContextMenu;
    UIElements.importMediaTagMenu = function (common) {
        if (mediatagContextMenu) { return mediatagContextMenu; }

        // Create context menu
        var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
            h('ul.dropdown-menu', {
                'role': 'menu',
                'aria-labelledBy': 'dropdownMenu',
                'style': 'display:block;position:static;margin-bottom:5px;'
            }, [
                h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
                    'tabindex': '-1',
                    'data-icon': "fa-cloud-upload",
                }, Messages.pad_mediatagImport)),
                h('li', h('a.cp-app-code-context-download.dropdown-item', {
                    'tabindex': '-1',
                    'data-icon': "fa-download",
                }, Messages.download_mt_button)),
            ])
        ]);
        // create the icon for each contextmenu option
        $(menu).find("li a.dropdown-item").each(function (i, el) {
            var $icon = $("<span>");
            if ($(el).attr('data-icon')) {
                var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa';
                $icon.addClass(font).addClass($(el).attr('data-icon'));
            } else {
                $icon.text($(el).text());
            }
            $(el).prepend($icon);
        });
        var m = createContextMenu(menu);

        mediatagContextMenu = m;

        var $menu = $(m.menu);
        $menu.on('click', 'a', function (e) {
            e.stopPropagation();
            m.hide();
            var $mt = $menu.data('mediatag');
            if ($(this).hasClass("cp-app-code-context-saveindrive")) {
                common.importMediaTag($mt);
            }
            else if ($(this).hasClass("cp-app-code-context-download")) {
                var media = $mt[0]._mediaObject;
                window.saveAs(media._blob.content, media.name);
            }
        });

        return m;
    };

    UIElements.displayFriendRequestModal = function (common, data) {
        var msg = data.content.msg;
        var text = Messages._getKey('contacts_request', [Util.fixHTML(msg.content.displayName)]);

        var todo = function (yes) {
            common.getSframeChannel().query("Q_ANSWER_FRIEND_REQUEST", {
                data: data,
                value: yes
            }, function (err, obj) {
                var error = err || (obj && obj.error);
                if (error) {
                    return void UI.warn(error);
                }
                if (yes) {
                    UI.log(Messages.contacts_added);
                } else {
                    UI.log(Messages.contacts_rejected);
                }
            });
        };

        var content = h('div.cp-share-modal', [
            setHTML(h('p'), text)
        ]);
        UI.proposal(content, todo);
    };

    UIElements.displayAddOwnerModal = function (common, data) {
        var priv = common.getMetadataMgr().getPrivateData();
        var user = common.getMetadataMgr().getUserData();
        var sframeChan = common.getSframeChannel();
        var msg = data.content.msg;

        var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
        var title = Util.fixHTML(msg.content.title);

        var text = Messages._getKey('owner_add', [name, title]);

        var link = h('a', {
            href: '#'
        }, Messages.requestEdit_viewPad);
        $(link).click(function (e) {
            e.preventDefault();
            e.stopPropagation();
            if (msg.content.password) {
                common.sessionStorage.put('newPadPassword', msg.content.password, function () {
                    common.openURL(msg.content.href);
                });
                return;
            }
            common.openURL(msg.content.href);
        });

        var div = h('div', [
            UI.setHTML(h('p'), text),
            link
        ]);

        var answer = function (yes) {
            common.mailbox.sendTo("ADD_OWNER_ANSWER", {
                channel: msg.content.channel,
                href: msg.content.href,
                password: msg.content.password,
                title: msg.content.title,
                answer: yes,
                user: {
                    displayName: user.name,
                    avatar: user.avatar,
                    profile: user.profile,
                    notifications: user.notifications,
                    curvePublic: user.curvePublic,
                    edPublic: priv.edPublic
                }
            }, {
                channel: msg.content.user.notifications,
                curvePublic: msg.content.user.curvePublic
            });
            common.mailbox.dismiss(data, function (err) {
                console.log(err);
            });
        };

        var todo = function (yes) {
            if (yes) {
                // ACCEPT
                sframeChan.query('Q_SET_PAD_METADATA', {
                    channel: msg.content.channel,
                    command: 'ADD_OWNERS',
                    value: [priv.edPublic]
                }, function (err, res) {
                    err = err || (res && res.error);
                    if (err) {
                        var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                      : Messages.error;
                        return void UI.warn(text);
                    }
                    UI.log(Messages.saved);

                    // Send notification to the sender
                    answer(true);

                    var data = JSON.parse(JSON.stringify(msg.content));
                    data.metadata = res;

                    // Add the pad to your drive
                    // This command will also add your mailbox to the metadata log
                    // The callback is called when the pad is stored, independantly of the metadata command
                    sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) {
                        if (err || (res && res.error)) {
                            return void console.error(err | res.error);
                        }
                        UI.log(Messages.saved);
                        if (autoStoreModal[data.channel]) {
                            autoStoreModal[data.channel].delete();
                            delete autoStoreModal[data.channel];
                        }
                    });

                    // Remove yourself from the pending owners
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: msg.content.channel,
                        command: 'RM_PENDING_OWNERS',
                        value: [priv.edPublic]
                    }, function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            console.error(err);
                        }
                    });
                });
                return;
            }

            // DECLINE
            // Remove yourself from the pending owners
            sframeChan.query('Q_SET_PAD_METADATA', {
                channel: msg.content.channel,
                command: 'RM_PENDING_OWNERS',
                value: [priv.edPublic]
            }, function (err, res) {
                err = err || (res && res.error);
                if (err) {
                    console.error(err);
                }
                // Send notification to the sender
                answer(false);
            });
        };

        UI.proposal(div, todo);
    };
    UIElements.displayAddTeamOwnerModal = function (common, data) {
        var priv = common.getMetadataMgr().getPrivateData();
        var user = common.getMetadataMgr().getUserData();
        var sframeChan = common.getSframeChannel();
        var msg = data.content.msg;

        var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
        var title = Util.fixHTML(msg.content.title);

        var text = Messages._getKey('owner_team_add', [name, title]);

        var div = h('div', [
            UI.setHTML(h('p'), text),
        ]);

        var answer = function (yes) {
            common.mailbox.sendTo("ADD_OWNER_ANSWER", {
                teamChannel: msg.content.teamChannel,
                title: msg.content.title,
                answer: yes,
                user: {
                    displayName: user.name,
                    avatar: user.avatar,
                    profile: user.profile,
                    notifications: user.notifications,
                    curvePublic: user.curvePublic,
                    edPublic: priv.edPublic
                }
            }, {
                channel: msg.content.user.notifications,
                curvePublic: msg.content.user.curvePublic
            });
            common.mailbox.dismiss(data, function (err) {
                if (err) { console.log(err); }
            });
        };
        var module = common.makeUniversal('team');

        var addOwner = function (chan, waitFor, cb) {
            // Remove yourself from the pending owners
            sframeChan.query('Q_SET_PAD_METADATA', {
                channel: chan,
                command: 'ADD_OWNERS',
                value: [priv.edPublic]
            }, function (err, res) {
                err = err || (res && res.error);
                if (!err) { return; }
                waitFor.abort();
                cb(err);
            });
        };
        var removePending = function (chan, waitFor, cb) {
            // Remove yourself from the pending owners
            sframeChan.query('Q_SET_PAD_METADATA', {
                channel: chan,
                command: 'RM_PENDING_OWNERS',
                value: [priv.edPublic]
            }, waitFor(function (err, res) {
                err = err || (res && res.error);
                if (!err) { return; }
                waitFor.abort();
                cb(err);
            }));
        };
        var changeAll = function (add, _cb) {
            var f = add ? addOwner : removePending;
            var cb = Util.once(_cb);
            NThen(function (waitFor) {
                f(msg.content.teamChannel, waitFor, cb);
                f(msg.content.chatChannel, waitFor, cb);
                f(msg.content.rosterChannel, waitFor, cb);
            }).nThen(function () { cb(); });
        };

        var todo = function (yes) {
            if (yes) {
                // ACCEPT
                changeAll(true, function (err) {
                    if (err) {
                        console.error(err);
                        var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                      : Messages.error;
                        return void UI.warn(text);
                    }
                    UI.log(Messages.saved);

                    // Send notification to the sender
                    answer(true);

                    // Mark ourselves as "owner" in our local team data
                    module.execCommand("ANSWER_OWNERSHIP", {
                        teamChannel: msg.content.teamChannel,
                        answer: true
                    }, function (obj) {
                        if (obj && obj.error) { console.error(obj.error); }
                    });

                    // Remove yourself from the pending owners
                    changeAll(false, function (err) {
                        if (err) { console.error(err); }
                    });
                });
                return;
            }

            // DECLINE
            // Remove yourself from the pending owners
            changeAll(false, function (err) {
                if (err) { console.error(err); }
                // Send notification to the sender
                answer(false);
                // Set our role back to ADMIN
                module.execCommand("ANSWER_OWNERSHIP", {
                    teamChannel: msg.content.teamChannel,
                    answer: false
                }, function (obj) {
                    if (obj && obj.error) { console.error(obj.error); }
                });
            });
        };

        UI.proposal(div, todo);
    };

    UIElements.getVerifiedFriend = function (common, curve, name) {
        var priv = common.getMetadataMgr().getPrivateData();
        var verified = h('p');
        var $verified = $(verified);

        if (priv.friends && priv.friends[curve]) {
            $verified.addClass('cp-notifications-requestedit-verified');
            var f = priv.friends[curve];
            $verified.append(h('span.fa.fa-certificate'));
            var $avatar = $(h('span.cp-avatar')).appendTo($verified);
            $verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName])));
            common.displayAvatar($avatar, f.avatar, f.displayName);
        } else {
            $verified.append(Messages._getKey('requestEdit_fromStranger', [name]));
        }
        return verified;
    };

    UIElements.displayInviteTeamModal = function (common, data) {
        var priv = common.getMetadataMgr().getPrivateData();
        var user = common.getMetadataMgr().getUserData();
        var msg = data.content.msg;

        var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
        var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '');

        var verified = UIElements.getVerifiedFriend(common, msg.author, name);

        var text = Messages._getKey('team_invitedToTeam', [name, teamName]);

        var div = h('div', [
            UI.setHTML(h('p'), text),
            verified
        ]);

        var module = common.makeUniversal('team');

        var answer = function (yes) {
            common.mailbox.sendTo("INVITE_TO_TEAM_ANSWER", {
                answer: yes,
                teamChannel: msg.content.team.channel,
                teamName: teamName,
                user: {
                    displayName: user.name,
                    avatar: user.avatar,
                    profile: user.profile,
                    notifications: user.notifications,
                    curvePublic: user.curvePublic,
                    edPublic: priv.edPublic
                }
            }, {
                channel: msg.content.user.notifications,
                curvePublic: msg.content.user.curvePublic
            });
            common.mailbox.dismiss(data, function (err) {
                console.log(err);
            });
        };

        var MAX_TEAMS_SLOTS = Constants.MAX_TEAMS_SLOTS;
        var todo = function (yes) {
            var priv = common.getMetadataMgr().getPrivateData();
            var numberOfTeams = Object.keys(priv.teams || {}).length;
            if (yes) {
                if (numberOfTeams >= MAX_TEAMS_SLOTS) {
                    return void UI.alert(Messages._getKey('team_maxTeams', [MAX_TEAMS_SLOTS]));
                }
                // ACCEPT
                module.execCommand('JOIN_TEAM', {
                    team: msg.content.team
                }, function (obj) {
                    if (obj && obj.error) {
                        if (obj.error === 'ENOENT') {
                            common.mailbox.dismiss(data, function () {});
                            return void UI.alert(Messages.deletedError);
                        }
                        return void UI.warn(Messages.error);
                    }
                    answer(true);
                    if (priv.app !== 'teams') { common.openURL('/teams/'); }
                });
                return;
            }

            // DECLINE
            answer(false);
        };

        UI.proposal(div, todo);
    };

    return UIElements;
});