Ver Fonte

Merge branch 'register'

JC Brand há 10 anos atrás
pai
commit
329746d839

+ 1 - 1
bower.json

@@ -29,7 +29,7 @@
     "bootstrapJS": "https://raw.githubusercontent.com/jcbrand/bootstrap/7d96a5f60d26c67b5348b270a775518b96a702c8/dist/js/bootstrap.js",
     "fontawesome": "~4.1.0",
     "typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js",
-    "strophejs-plugins": "https://github.com/strophe/strophejs-plugins.git#a56421ff4ecf0807113ab48c46728715597df599"
+    "strophejs-plugins": "https://github.com/strophe/strophejs-plugins.git#conversejs"
   },
   "exportsOverride": {}
 }

+ 552 - 219
converse.js

@@ -127,8 +127,8 @@
             } else {
                 audio = new Audio("/sounds/msg_received.mp3");
                 audio.play();
+                }
             }
-        }
     };
 
     var converse = {
@@ -162,6 +162,26 @@
     converse.initialize = function (settings, callback) {
         var converse = this;
 
+        // Logging
+        Strophe.log = function (level, msg) { console.log(level+' '+msg); };
+        Strophe.error = function (msg) {
+            console.log('ERROR: '+msg);
+        };
+
+        // Add Strophe Namespaces
+        Strophe.addNamespace('REGISTER', 'jabber:iq:register');
+        Strophe.addNamespace('XFORM', 'jabber:x:data');
+
+        // Add Strophe Statuses
+        var i = 0;
+        Object.keys(Strophe.Status).forEach(function (key) {
+            i = Math.max(i, Strophe.Status[key]);
+        });
+        Strophe.Status.REGIFAIL        = i + 1;
+        Strophe.Status.REGISTERED      = i + 2;
+        Strophe.Status.CONFLICT        = i + 3;
+        Strophe.Status.NOTACCEPTABLE   = i + 5;
+
         // Constants
         // ---------
         var UNENCRYPTED = 0;
@@ -179,13 +199,11 @@
             'dnd':          2,
             'online':       1
         };
-
         var INACTIVE = 'inactive';
         var ACTIVE = 'active';
         var COMPOSING = 'composing';
         var PAUSED = 'paused';
         var GONE = 'gone';
-
         var HAS_CSPRNG = ((typeof crypto !== 'undefined') &&
             ((typeof crypto.randomBytes === 'function') ||
                 (typeof crypto.getRandomValues === 'function')
@@ -195,95 +213,58 @@
             (typeof OTR !== "undefined") &&
             (typeof DSA !== "undefined")
         );
-
         var OPENED = 'opened';
         var CLOSED = 'closed';
 
         // Default configuration values
         // ----------------------------
-        this.allow_contact_requests = true;
-        this.allow_dragresize = true;
-        this.allow_logout = true;
-        this.allow_muc = true;
-        this.allow_otr = true;
-        this.animate = true;
-        this.auto_list_rooms = false;
-        this.auto_reconnect = false;
-        this.auto_subscribe = false;
-        this.bosh_service_url = undefined; // The BOSH connection manager URL.
-        this.cache_otr_key = false;
-        this.debug = false;
-        this.default_box_height = 324; // The default height, in pixels, for the control box, chat boxes and chatrooms.
-        this.expose_rid_and_sid = false;
-        this.forward_messages = false;
-        this.hide_muc_server = false;
-        this.hide_offline_users = false;
-        this.i18n = locales.en;
-        this.keepalive = false;
-        this.message_carbons = false;
-        this.no_trimming = false; // Set to true for phantomjs tests (where browser apparently has no width)
-        this.play_sounds = false;
-        this.prebind = false;
-        this.roster_groups = false;
-        this.show_controlbox_by_default = false;
-        this.show_only_online_users = false;
-        this.show_toolbar = true;
-        this.storage = 'session';
-        this.use_otr_by_default = false;
-        this.use_vcards = true;
-        this.visible_toolbar_buttons = {
-            'emoticons': true,
-            'call': false,
-            'clear': true,
-            'toggle_participants': true
+        var default_settings = {
+            allow_contact_requests: true,
+            allow_dragresize: true,
+            allow_logout: true,
+            allow_muc: true,
+            allow_otr: true,
+            allow_registration: true,
+            animate: true,
+            auto_list_rooms: false,
+            auto_reconnect: false,
+            auto_subscribe: false,
+            bosh_service_url: undefined, // The BOSH connection manager URL.
+            cache_otr_key: false,
+            debug: false,
+            default_box_height: 400, // The default height, in pixels, for the control box, chat boxes and chatrooms.
+            expose_rid_and_sid: false,
+            forward_messages: false,
+            hide_muc_server: false,
+            hide_offline_users: false,
+            i18n: locales.en,
+            keepalive: false,
+            message_carbons: false,
+            no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
+            play_sounds: false,
+            prebind: false,
+            roster_groups: false,
+            show_controlbox_by_default: false,
+            show_only_online_users: false,
+            show_toolbar: true,
+            storage: 'session',
+            use_otr_by_default: false,
+            use_vcards: true,
+            visible_toolbar_buttons: {
+                'emoticons': true,
+                'call': false,
+                'clear': true,
+                'toggle_participants': true
+            },
+            xhr_custom_status: false,
+            xhr_custom_status_url: '',
+            xhr_user_search: false,
+            xhr_user_search_url: ''
         };
-        this.xhr_custom_status = false;
-        this.xhr_custom_status_url = '';
-        this.xhr_user_search = false;
-        this.xhr_user_search_url = '';
-
+        _.extend(this, default_settings);
         // Allow only whitelisted configuration attributes to be overwritten
-        _.extend(this, _.pick(settings, [
-            'allow_contact_requests',
-            'allow_dragresize',
-            'allow_logout',
-            'allow_muc',
-            'allow_otr',
-            'animate',
-            'auto_list_rooms',
-            'auto_reconnect',
-            'auto_subscribe',
-            'bosh_service_url',
-            'cache_otr_key',
-            'connection',
-            'debug',
-            'default_box_height',
-            'expose_rid_and_sid',
-            'forward_messages',
-            'fullname',
-            'hide_muc_server',
-            'hide_offline_users',
-            'i18n',
-            'jid',
-            'keepalive',
-            'message_carbons',
-            'no_trimming',
-            'play_sounds',
-            'prebind',
-            'rid',
-            'roster_groups',
-            'show_controlbox_by_default',
-            'show_only_online_users',
-            'show_toolbar',
-            'sid',
-            'storage',
-            'use_otr_by_default',
-            'use_vcards',
-            'xhr_custom_status',
-            'xhr_custom_status_url',
-            'xhr_user_search',
-            'xhr_user_search_url'
-        ]));
+        _.extend(this, _.pick(settings, Object.keys(default_settings)));
+
         if (settings.visible_toolbar_buttons) {
             _.extend(
                 this.visible_toolbar_buttons,
@@ -351,10 +332,15 @@
         // Module-level functions
         // ----------------------
         this.giveFeedback = function (message, klass) {
-            $('.conn-feedback').attr('class', 'conn-feedback').text(message);
-            if (klass) {
-                $('.conn-feedback').addClass(klass);
-            }
+            $('.conn-feedback').each(function (idx, el) {
+                var $el = $(el);
+                $el.addClass('conn-feedback').text(message);
+                if (klass) {
+                    $el.addClass(klass);
+                } else {
+                    $el.removeClass('error');
+                }
+            });
         };
 
         this.log = function (txt, level) {
@@ -440,7 +426,6 @@
         };
 
         this.onConnect = function (status, condition, reconnect) {
-            var $button, $form;
             if ((status === Strophe.Status.CONNECTED) ||
                 (status === Strophe.Status.ATTACHED)) {
                 if ((typeof reconnect !== 'undefined') && (reconnect)) {
@@ -451,30 +436,26 @@
                     converse.onConnected();
                 }
             } else if (status === Strophe.Status.DISCONNECTED) {
-                converse.giveFeedback(__('Disconnected'), 'error');
                 if (converse.auto_reconnect) {
                     converse.reconnect();
                 } else {
                     converse.renderLoginPanel();
                 }
             } else if (status === Strophe.Status.Error) {
-                converse.renderLoginPanel();
                 converse.giveFeedback(__('Error'), 'error');
             } else if (status === Strophe.Status.CONNECTING) {
                 converse.giveFeedback(__('Connecting'));
-            } else if (status === Strophe.Status.CONNFAIL) {
-                converse.renderLoginPanel();
-                converse.giveFeedback(__('Connection Failed'), 'error');
             } else if (status === Strophe.Status.AUTHENTICATING) {
                 converse.giveFeedback(__('Authenticating'));
             } else if (status === Strophe.Status.AUTHFAIL) {
-                converse.renderLoginPanel();
                 converse.giveFeedback(__('Authentication Failed'), 'error');
+                converse.connection.disconnect(__('Authentication Failed'));
             } else if (status === Strophe.Status.DISCONNECTING) {
                 if (!converse.connection.connected) {
                     converse.renderLoginPanel();
-                } else {
-                    converse.giveFeedback(__('Disconnecting'), 'error');
+                }
+                if (condition) {
+                    converse.giveFeedback(condition, 'error');
                 }
             }
         };
@@ -534,7 +515,7 @@
             this.session.browserStorage = new Backbone.BrowserStorage[converse.storage](id);
             this.session.fetch();
             $(window).on('beforeunload', $.proxy(function () {
-                if (converse.connection.connected) {
+                if (converse.connection.authenticated) {
                     this.setSession();
                 } else {
                     this.clearSession();
@@ -563,7 +544,6 @@
             converse.chatboxviews.closeAllChatBoxes(false);
             converse.clearSession();
             converse.connection.disconnect();
-            converse.connection.reset();
         };
 
         this.registerGlobalEventHandlers = function () {
@@ -642,16 +622,6 @@
         };
 
         this.onConnected = function () {
-            if (this.debug) {
-                this.connection.xmlInput = function (body) { console.log(body); };
-                this.connection.xmlOutput = function (body) { console.log(body); };
-                Strophe.log = function (level, msg) {
-                    console.log(level+' '+msg);
-                };
-                Strophe.error = function (msg) {
-                    console.log('ERROR: '+msg);
-                };
-            }
             // When reconnecting, there might be some open chat boxes. We don't
             // know whether these boxes are of the same account or not, so we
             // close them now.
@@ -1703,6 +1673,7 @@
 
         this.RoomsPanel = Backbone.View.extend({
             tagName: 'div',
+            className: 'controlbox-pane',
             id: 'chatrooms',
             events: {
                 'submit form.add-chatroom': 'createChatRoom',
@@ -1937,6 +1908,14 @@
                 }
             },
 
+            giveFeedback: function (message, klass) {
+                var $el = this.$('.conn-feedback');
+                $el.addClass('conn-feedback').text(message);
+                if (klass) {
+                    $el.addClass(klass);
+                }
+            },
+
             onConnected: function () {
                 if (this.model.get('connected')) {
                     this.render().initRoster();
@@ -1963,15 +1942,6 @@
                     b64_sha1('converse.roster.groups'+converse.bare_jid));
                 converse.rosterview = new converse.RosterView({model: rostergroups});
                 this.contactspanel.$el.append(converse.rosterview.$el);
-                // TODO:
-                // See if we shouldn't also fetch the roster here... otherwise
-                // the roster is always populated by the rosterHandler method,
-                // which appears to be a less economic way.
-                // i.e. from what it seems, only groups are fetched from
-                // browserStorage, and no contacts.
-                // XXX: Make sure that if fetch is called, we don't sort on
-                // each item add...
-                // converse.roster.fetch()
                 converse.rosterview.render().fetch().update();
                 return this;
             },
@@ -1987,15 +1957,29 @@
             },
 
             renderLoginPanel: function () {
+                var $feedback = this.$('.conn-feedback'); // we want to still show any existing feedback.
                 this.$el.html(converse.templates.controlbox(this.model.toJSON()));
                 var cfg = {'$parent': this.$el.find('.controlbox-panes'), 'model': this};
                 if (!this.loginpanel) {
                     this.loginpanel = new converse.LoginPanel(cfg);
+                    if (converse.allow_registration) {
+                        this.registerpanel = new converse.RegisterPanel(cfg);
+                    }
                 } else {
                     this.loginpanel.delegateEvents().initialize(cfg);
+                    if (converse.allow_registration) {
+                        this.registerpanel.delegateEvents().initialize(cfg);
+                    }
                 }
                 this.loginpanel.render();
+                if (converse.allow_registration) {
+                    this.registerpanel.render().$el.hide();
+                }
                 this.initDragResize();
+                if ($feedback.length) {
+                    this.$('.conn-feedback').replaceWith($feedback);
+                }
+                return this;
             },
 
             renderContactsPanel: function () {
@@ -2078,7 +2062,8 @@
             },
 
             switchTab: function (ev) {
-                ev.preventDefault();
+                // TODO: automatically focus the relevant input
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
                 var $tab = $(ev.target),
                     $sibling = $tab.parent().siblings('li').children('a'),
                     $tab_panel = $($tab.attr('href'));
@@ -2086,6 +2071,7 @@
                 $sibling.removeClass('current');
                 $tab.addClass('current');
                 $tab_panel.show();
+                return this;
             },
 
             showHelpMessages: function (msgs) {
@@ -2419,73 +2405,15 @@
                 var $form= this.$el.find('form.chatroom-form'),
                     $stanza = $(stanza),
                     $fields = $stanza.find('field'),
-                    title = $stanza.find('title').text(),
-                    instructions = $stanza.find('instructions').text(),
-                    i, j, options=[], $field, $options,
-					values=[], $values, value;
-                var input_types = {
-                    'text-private': 'password',
-                    'text-single': 'textline',
-                    'fixed': 'label',
-                    'boolean': 'checkbox',
-                    'hidden': 'hidden',
-                    'jid-multi': 'textarea',
-                    'list-single': 'dropdown',
-                    'list-multi': 'dropdown'
-                };
+                    title = $stanza.find('title').text();
                 $form.find('span.spinner').remove();
                 $form.append($('<legend>').text(title));
                 if (instructions != title) {
-                    $form.append($('<p>').text(instructions));
-                }
-                for (i=0; i<$fields.length; i++) {
-                    $field = $($fields[i]);
-                    if ($field.attr('type') == 'list-single' || $field.attr('type') == 'list-multi') {
-						values = [];
-                        $values = $field.children('value');
-                        for (j=0; j<$values.length; j++) {
-							values.push($($values[j]).text());
-						}
-                        options = [];
-                        $options = $field.children('option');
-                        for (j=0; j<$options.length; j++) {
-                            value = $($options[j]).find('value').text();
-                            options.push(converse.templates.select_option({
-                                value: value,
-                                label: $($options[j]).attr('label'),
-								selected: (values.indexOf(value) >= 0)
-                            }));
-                        }
-                        $form.append(converse.templates.form_select({
-                            name: $field.attr('var'),
-                            label: $field.attr('label'),
-                            options: options.join(''),
-                            multiple: ($field.attr('type') == 'list-multi')
-                        }));
-                    } else if ($field.attr('type') == 'fixed') {
-                        $form.append($('<p>').text($field.find('value').text()));
-                    } else if ($field.attr('type') == 'jid-multi') {
-                        $form.append(converse.templates.form_textarea({
-                            name: $field.attr('var'),
-                            label: $field.attr('label') || '',
-                            value: $field.find('value').text()
-                        }));
-                    } else if ($field.attr('type') == 'boolean') {
-                        $form.append(converse.templates.form_checkbox({
-                            name: $field.attr('var'),
-                            type: input_types[$field.attr('type')],
-                            label: $field.attr('label') || '',
-                            checked: $field.find('value').text() === "1" && 'checked="1"' || ''
-                        }));
-                    } else {
-                        $form.append(converse.templates.form_input({
-                            name: $field.attr('var'),
-                            type: input_types[$field.attr('type')],
-                            label: $field.attr('label') || '',
-                            value: $field.find('value').text()
-                        }));
-                    }
+                    $form.append($('<p class="instructions">').text(this.instructions));
                 }
+                _.each($fields, function (field) {
+                    $form.append(utils.xForm2webForm(field));
+                });
                 $form.append('<input type="submit" value="'+__('Save')+'"/>');
                 $form.append('<input type="button" value="'+__('Cancel')+'"/>');
                 $form.on('submit', $.proxy(this.saveConfiguration, this));
@@ -2499,26 +2427,7 @@
                     count = $inputs.length,
                     configArray = [];
                 $inputs.each(function () {
-                    var $input = $(this), value;
-                    if ($input.is('[type=checkbox]')) {
-                        value = $input.is(':checked') && 1 || 0;
-                    } else if ($input.is('textarea')) {
-                        value = [];
-                        var lines = $input.val().split('\n');
-                        for( var vk=0; vk<lines.length; vk++) {
-                            var val = $.trim(lines[vk]);
-                            if (val === '')
-                                continue;
-                            value.push(val);
-                        }
-                    } else {
-                        value = $input.val();
-                    }
-                    var cnode = $(converse.templates.field({
-                        name: $input.attr('name'),
-                        value: value
-                    }))[0];
-                    configArray.push(cnode);
+                    configArray.push(utils.webForm2xForm(this));
                     if (!--count) {
                         converse.connection.muc.saveConfiguration(
                             that.model.get('jid'),
@@ -2537,7 +2446,7 @@
             },
 
             onConfigSaved: function (stanza) {
-                // XXX
+                // TODO: provide feedback
             },
 
             onErrorConfigSaved: function (stanza) {
@@ -3087,7 +2996,9 @@
                 this.model.each($.proxy(function (model) {
                     var id = model.get('id');
                     if (include_controlbox || id !== 'controlbox') {
-                        this.get(id).close();
+                        if (this.get(id)) { // Should always resolve, but shit happens
+                            this.get(id).close();
+                        }
                     }
                 }, this));
                 return this;
@@ -4550,28 +4461,422 @@
             }
         });
 
-        this.LoginPanel = Backbone.View.extend({
+        this.RegisterPanel = Backbone.View.extend({
             tagName: 'div',
-            id: "login-dialog",
+            id: "register",
+            className: 'controlbox-pane',
             events: {
-                'submit form#converse-login': 'authenticate'
+                'submit form#converse-register': 'onProviderChosen'
             },
 
-            connect: function ($form, jid, password) {
-                if ($form) {
-                    $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
+            initialize: function (cfg) {
+                this.reset();
+                this.$parent = cfg.$parent;
+                this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
+                this.registerHooks();
+            },
+
+            render: function () {
+                this.$parent.append(this.$el.html(
+                    converse.templates.register_panel({
+                        'label_domain': __("Your XMPP provider's domain:"),
+                        'label_register': __('Fetch registration form')
+                    })
+                ));
+                this.$tabs.append(converse.templates.register_tab({label_register: __('Register')}));
+                return this;
+            },
+
+            registerHooks: function () {
+                /* Hook into Strophe's _connect_cb, so that we can send an IQ
+                 * requesting the registration fields.
+                 */
+                var conn = converse.connection;
+                var connect_cb = conn._connect_cb.bind(conn);
+                conn._connect_cb = $.proxy(function (req, callback, raw) {
+                    if (!this._registering) {
+                        connect_cb(req, callback, raw);
+                    } else {
+                        if (this.getRegistrationFields(req, callback, raw)) {
+                            delete this._registering;
+                        }
+                    }
+                }, this);
+            },
+
+            getRegistrationFields: function (req, _callback, raw) {
+                /*  Send an IQ stanza to the XMPP server asking for the
+                 *  registration fields.
+                 *
+                 *  Parameters:
+                 *    (Strophe.Request) req - The current request
+                 *    (Function) callback
+                 */
+                converse.log("sendQueryStanza was called");
+                var conn = converse.connection;
+                conn.connected = true;
+
+                var body = conn._proto._reqToData(req);
+                if (!body) { return; }
+                if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) {
+                    return false;
                 }
-                var resource = Strophe.getResourceFromJid(jid);
-                if (!resource) {
-                    jid += '/converse.js-' + Math.floor(Math.random()*139749825).toString();
+                var register = body.getElementsByTagName("register");
+                var mechanisms = body.getElementsByTagName("mechanism");
+                if (register.length === 0 && mechanisms.length === 0) {
+                    conn._proto._no_auth_received(_callback);
+                    return false;
+                }
+                if (register.length === 0) {
+                    conn._changeConnectStatus(
+                            Strophe.Status.REGIFAIL,
+                            'Sorry, the given provider does not support in band account registration. Please try with a different provider.');
+                    return true;
+                }
+                // Send an IQ stanza to get all required data fields
+                conn._addSysHandler(this.onRegistrationFields.bind(this), null, "iq", null, null);
+                conn.send($iq({type: "get"}).c("query", {xmlns: Strophe.NS.REGISTER}).tree());
+                return true;
+            },
+
+            onRegistrationFields: function (stanza) {
+                /*  Handler for Registration Fields Request.
+                 *
+                 *  Parameters:
+                 *    (XMLElement) elem - The query stanza.
+                 */
+                if (stanza.getElementsByTagName("query").length !== 1) {
+                    converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
+                    return false;
+                }
+                this.setFields(stanza);
+                this.renderRegistrationForm(stanza);
+                return false;
+            },
+
+            reset: function (settings) {
+                var defaults = {
+                    fields: {},
+                    urls: [],
+                    title: "",
+                    instructions: "",
+                    registered: false,
+                    _registering: false,
+                    domain: null,
+                    form_type: null
+                };
+                _.extend(this, defaults);
+                if (settings) {
+                    _.extend(this, _.pick(settings, Object.keys(defaults)));
+                }
+            },
+
+            onProviderChosen: function (ev) {
+                /* Callback method that gets called when the user has chosen an
+                 * XMPP provider.
+                 *
+                 * Parameters:
+                 *      (Submit Event) ev - Form submission event.
+                 */
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                var $form = $(ev.target),
+                    $domain_input = $form.find('input[name=domain]'),
+                    domain = $domain_input.val(),
+                    errors = false;
+                if (!domain) {
+                    $domain_input.addClass('error');
+                    return;
+                }
+                $form.find('input[type=submit]').hide()
+                    .after('<button class="cancel hor_centered">Cancel</button>')
+                    .after('<span class="spinner login-submit"/>')
+                    .after('<p class="info">Requesting a registration form from the XMPP server</p>');
+                $form.find('button.cancel').on('click', $.proxy(this.cancelRegistration, this));
+                this.reset({
+                    domain: Strophe.getDomainFromJid(domain),
+                    _registering: true
+                });
+                converse.connection.connect(this.domain, "", $.proxy(this.onRegistering, this));
+                return false;
+            },
+
+            giveFeedback: function (message, klass) {
+                this.$('.reg-feedback').attr('class', 'reg-feedback').text(message);
+                if (klass) {
+                    $('.reg-feedback').addClass(klass);
+                }
+            },
+
+            onRegistering: function (status, error) {
+                var that;
+                console.log('onRegistering');
+                if (_.contains([
+                            Strophe.Status.DISCONNECTED,
+                            Strophe.Status.CONNFAIL,
+                            Strophe.Status.REGIFAIL
+                        ], status)) {
+
+                    converse.log('Problem during registration: Strophe.Status is: '+status);
+                    this.cancelRegistration();
+                    if (error) {
+                        this.giveFeedback(error, 'error');
+                    } else {
+                        this.giveFeedback(__(
+                                'Something went wrong establishing a connection with "%1$s". Are you sure it exists?',
+                                this.domain
+                            ), 'error');
+                    }
+                } else if (status == Strophe.Status.CONFLICT) {
+                    // TODO
+                    converse.log('CONFLICT');
+                } else if (status == Strophe.Status.NOTACCEPTABLE) {
+                    // TODO
+                    converse.log('NOTACCEPTABLE');
+                } else if (status == Strophe.Status.REGISTERED) {
+                    converse.log("Registered successfully.");
+                    converse.connection.reset();
+                    that = this;
+                    this.$('form').hide(function () {
+                        $(this).replaceWith('<span class="spinner centered"/>');
+                        if (that.fields.password && that.fields.username) {
+                            // automatically log the user in
+                            converse.connection.connect(
+                                that.fields.username+'@'+that.domain,
+                                that.fields.password,
+                                converse.onConnect
+                            );
+                            converse.chatboxviews.get('controlbox')
+                                .switchTab({target: that.$tabs.find('.current')})
+                                .giveFeedback(__('Now logging you in'));
+                        } else {
+                            converse.chatboxviews.get('controlbox')
+                                .renderLoginPanel()
+                                .giveFeedback(__('Registered successfully'));
+                        }
+                        that.reset();
+                    });
+                }
+            },
+
+            renderRegistrationForm: function (stanza) {
+                /* Renders the registration form based on the XForm fields
+                 * received from the XMPP server.
+                 *
+                 * Parameters:
+                 *      (XMLElement) stanza - The IQ stanza received from the XMPP server.
+                 */
+                var $form= this.$('form'),
+                    $stanza = $(stanza),
+                    $fields;
+                $form.empty().append(converse.templates.registration_form({
+                    'domain': this.domain,
+                    'title': this.title,
+                    'instructions': this.instructions
+                }));
+                if (this.form_type == 'xform') {
+                    $fields = $stanza.find('field');
+                    _.each($fields, function (field) {
+                        $form.append(utils.xForm2webForm($(field), $stanza));
+                    });
+                } else {
+                    // Show fields
+                    _.each(Object.keys(this.fields), $.proxy(function (key) {
+                        $form.append('<label>'+key+'</label>');
+                        var $input = $('<input placeholder="'+key+'" name="'+key+'"></input>');
+                        if (key === 'password' || key === 'email') {
+                            $input.attr('type', key);
+                        }
+                        $form.append($input);
+                    }, this));
+                    // Show urls
+                    _.each(this.urls, $.proxy(function (url) {
+                        $form.append($('<a target="blank"></a>').attr('href', url).text(url));
+                    }, this));
+                }
+                if (this.fields) {
+                    $form.append('<input type="submit" class="submit" value="'+__('Register')+'"/>');
+                    $form.on('submit', $.proxy(this.submitRegistrationForm, this));
+                    $form.append('<input type="button" class="submit" value="'+__('Cancel')+'"/>');
+                    $form.find('input[type=button]').on('click', $.proxy(this.cancelRegistration, this));
+                } else {
+                    $form.append('<input type="button" class="submit" value="'+__('Return')+'"/>');
+                    $form.find('input[type=button]').on('click', $.proxy(this.cancelRegistration, this));
+                }
+            },
+
+            reportErrors: function (stanza) {
+                /* Report back to the user any error messages received from the
+                 * XMPP server after attempted registration.
+                 *
+                 * Parameters:
+                 *      (XMLElement) stanza - The IQ stanza received from the
+                 *      XMPP server.
+                 */
+                var $form= this.$('form'), flash;
+                var $errmsgs = $(stanza).find('error text');
+                var $flash = $form.find('.form-errors');
+                if (!$flash.length) {
+                   flash = '<legend class="form-errors"></legend>';
+                    if ($form.find('p.instructions').length) {
+                        $form.find('p.instructions').append(flash);
+                    } else {
+                        $form.prepend(flash);
+                    }
+                    $flash = $form.find('.form-errors');
+                } else {
+                    $flash.empty();
+                }
+                $errmsgs.each(function (idx, txt) {
+                    $flash.append($('<p>').text($(txt).text()));
+                });
+                if (!$errmsgs.length) {
+                    $flash.append($('<p>').text(
+                        __('The provider rejected your registration attempt. '+
+                           'Please check the values you entered for correctness.')));
+                }
+                $flash.show();
+            },
+
+            cancelRegistration: function (ev) {
+                /* Handler, when the user cancels the registration form.
+                 */
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                converse.connection.reset();
+                this.render();
+            },
+
+            submitRegistrationForm : function (ev) {
+                /* Handler, when the user submits the registration form.
+                 * Provides form error feedback or starts the registration
+                 * process.
+                 *
+                 * Parameters:
+                 *      (Event) ev - the submit event.
+                 */
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                var $empty_inputs = this.$('input.required:emptyVal');
+                if ($empty_inputs.length) {
+                    $empty_inputs.addClass('error');
+                    return;
+                }
+                var $inputs = $(ev.target).find(':input:not([type=button]):not([type=submit])'),
+                    iq = $iq({type: "set"})
+                        .c("query", {xmlns:Strophe.NS.REGISTER})
+                        .c("x", {xmlns: Strophe.NS.XFORM, type: 'submit'});
+
+                $inputs.each(function () {
+                    iq.cnode(utils.webForm2xForm(this)).up();
+                });
+                converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, "iq", null, null);
+                converse.connection.send(iq);
+                this.setFields(iq.tree());
+            },
+
+            setFields: function (stanza) {
+                /* Stores the values that will be sent to the XMPP server
+                 * during attempted registration.
+                 *
+                 * Parameters:
+                 *      (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server.
+                 */
+                var $query = $(stanza).find('query'), $xform;
+                if ($query.length > 0) {
+                    $xform = $query.find('x[xmlns="'+Strophe.NS.XFORM+'"]');
+                    if ($xform.length > 0) {
+                        this._setFieldsFromXForm($xform);
+                    } else {
+                        this._setFieldsFromLegacy($query);
+                    }
                 }
-                converse.connection.connect(jid, password, converse.onConnect);
+            },
+
+            _setFieldsFromLegacy: function ($query) {
+                $query.children().each($.proxy(function (idx, field) {
+                    var $field = $(field);
+                    if (field.tagName.toLowerCase() === 'instructions') {
+                        this.instructions = Strophe.getText(field);
+                        return;
+                    } else if (field.tagName.toLowerCase() === 'x') {
+                        if ($field.attr('xmlns') === 'jabber:x:oob') {
+                            $field.find('url').each($.proxy(function (idx, url) {
+                                this.urls.push($(url).text());
+                            }, this));
+                        }
+                        return;
+                    }
+                    this.fields[field.tagName.toLowerCase()] = Strophe.getText(field);
+                }, this));
+                this.form_type = 'legacy';
+            },
+
+            _setFieldsFromXForm: function ($xform) {
+                this.title = $xform.find('title').text();
+                this.instructions = $xform.find('instructions').text();
+                $xform.find('field').each($.proxy(function (idx, field) {
+                    var _var = field.getAttribute('var');
+                    if (_var) {
+                        this.fields[_var.toLowerCase()] = $(field).children('value').text();
+                    } else {
+                        // TODO: other option seems to be type="fixed"
+                        console.log("WARNING: Found field we couldn't parse");
+                    }
+                }, this));
+                this.form_type = 'xform';
+            },
+
+            _onRegisterIQ: function (stanza) {
+                /* Callback method that gets called when a return IQ stanza
+                 * is received from the XMPP server, after attempting to
+                 * register a new user.
+                 *
+                 * Parameters:
+                 *      (XMLElement) stanza - The IQ stanza.
+                 */
+                var i, field, error = null, that,
+                    query = stanza.getElementsByTagName("query");
+                if (query.length > 0) {
+                    query = query[0];
+                }
+                if (stanza.getAttribute("type") === "error") {
+                    converse.log("Registration failed.");
+                    error = stanza.getElementsByTagName("error");
+                    if (error.length !== 1) {
+                        converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
+                        return false;
+                    }
+                    error = error[0].firstChild.tagName.toLowerCase();
+                    if (error === 'conflict') {
+                        converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error);
+                    } else if (error === 'not-acceptable') {
+                        converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error);
+                    } else {
+                        converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error);
+                    }
+                    this.reportErrors(stanza);
+                } else {
+                    converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null);
+                }
+                return false;
+            },
+
+            remove: function () {
+                this.$tabs.empty();
+                this.$el.parent().empty();
+            }
+        });
+
+        this.LoginPanel = Backbone.View.extend({
+            tagName: 'div',
+            id: "login-dialog",
+            className: 'controlbox-pane',
+            events: {
+                'submit form#converse-login': 'authenticate'
             },
 
             initialize: function (cfg) {
                 cfg.$parent.html(this.$el.html(
                     converse.templates.login_panel({
-                        'label_username': __('XMPP/Jabber Username:'),
+                        'label_username': __('XMPP Username:'),
                         'label_password': __('Password:'),
                         'label_login': __('Log In')
                     })
@@ -4616,6 +4921,18 @@
                 return false;
             },
 
+            connect: function ($form, jid, password) {
+                if ($form) {
+                    $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
+                }
+                var resource = Strophe.getResourceFromJid(jid);
+                if (!resource) {
+                    jid += '/converse.js-' + Math.floor(Math.random()*139749825).toString();
+                }
+                converse.connection.connect(jid, password, converse.onConnect);
+            },
+
+
             remove: function () {
                 this.$tabs.empty();
                 this.$el.parent().empty();
@@ -4695,9 +5012,17 @@
             });
         };
 
+        this.setUpXMLLogging = function () {
+            if (this.debug) {
+                this.connection.xmlInput = function (body) { console.log(body); };
+                this.connection.xmlOutput = function (body) { console.log(body); };
+            }
+        };
+
         this.initConnection = function () {
             var rid, sid, jid;
             if (this.connection && this.connection.connected) {
+                this.setUpXMLLogging();
                 this.onConnected();
             } else {
                 // XXX: it's not yet clear what the order of preference should
@@ -4712,6 +5037,7 @@
                     throw("Error: you must supply a value for the bosh_service_url");
                 }
                 this.connection = new Strophe.Connection(this.bosh_service_url);
+                this.setUpXMLLogging();
 
                 if (this.prebind) {
                     if (this.jid && this.sid && this.rid) {
@@ -4742,10 +5068,14 @@
              * connection.
              */
             this.initial_presence_sent = false;
-            this.roster.off().reset(); // Removes roster contacts
+            if (this.roster) {
+                this.roster.off().reset(); // Removes roster contacts
+            }
             this.connection.roster._callbacks = []; // Remove all Roster handlers (e.g. rosterHandler)
-            this.rosterview.model.off().reset(); // Removes roster groups
-            this.rosterview.undelegateEvents().remove();
+            if (this.rosterview) {
+                this.rosterview.model.off().reset(); // Removes roster groups
+                this.rosterview.undelegateEvents().remove();
+            }
             this.chatboxes.remove(); // Don't call off(), events won't get re-registered upon reconnect.
             if (this.features) {
                 this.features.reset();
@@ -4781,6 +5111,9 @@
         // Initialization
         // --------------
         // This is the end of the initialize method.
+        if (settings.connection) {
+            this.connection = settings.connection;
+        }
         this._initializePlugins();
         this._initialize();
         this.registerGlobalEventHandlers();

+ 85 - 35
css/converse.css

@@ -481,26 +481,29 @@
     transform: rotate(359deg);
   }
 }
-span.spinner:before {
+#conversejs .spinner:before {
   font-size: 24px;
   font-family: 'Converse-js' !important;
   content: "\231b";
 }
-span.spinner {
+#conversejs .spinner {
   -webkit-animation: spin 2s infinite linear;
   -moz-animation: spin 2s infinite linear;
   -o-animation: spin 2s infinite linear;
   animation: spin 2s infinite linear;
-  width: 100%;
   display: block;
   text-align: center;
+  margin: 5px;
 }
-span.spinner.centered {
+#conversejs .centered {
   text-align: center;
-  padding-top: 5em;
+  display: block;
+  margin: 5em auto;
 }
-span.spinner.hor_centered {
+#conversejs .hor_centered {
   text-align: center;
+  display: block;
+  margin: 0 auto;
 }
 #conversejs #minimized-chats .box-flyout {
   position: absolute;
@@ -737,12 +740,27 @@ span.spinner.hor_centered {
 #conversejs div.delayed .chat-message-me {
   color: #7EABBB;
 }
-input.error {
+#conversejs input.error {
   border: 1px solid red;
 }
-#conversejs .conn-feedback.error {
+#conversejs .error {
   color: red;
 }
+#converse-register .provider-title {
+  font-size: 115%;
+}
+#converse-register .provider-score {
+  width: 178px;
+  margin-bottom: 8px;
+}
+#converse-register .form-help .url {
+  font-weight: bold;
+  color: #2D617A;
+}
+#conversejs .reg-feedback {
+  font-size: 85%;
+}
+#conversejs .reg-feedback,
 #converse-login .conn-feedback {
   width: 100%;
   text-align: center;
@@ -807,6 +825,7 @@ dl.add-converse-contact {
 #conversejs .fancy-dropdown {
   border: 1px solid #ddd;
   height: 22px;
+  text-align: left;
 }
 #conversejs .fancy-dropdown a.choose-xmpp-status {
   width: 155px;
@@ -926,9 +945,6 @@ dl.add-converse-contact {
   display: block;
   clear: both;
 }
-#conversejs .chatroom-form label.label-ta {
-    height: auto;
-}
 #conversejs .chatroom-form label input,
 #conversejs .chatroom-form label select {
   float: right;
@@ -965,6 +981,7 @@ dl.add-converse-contact {
   padding: 0 5px 0 0;
 }
 #converse-roster {
+  text-align: left;
   width: 100%;
   position: relative;
   margin: 0.5em 0 0 0;
@@ -1013,6 +1030,9 @@ dl.add-converse-contact {
 #conversejs dd.available-chatroom a.open-room {
   width: 148px;
 }
+#available-chatrooms {
+  text-align: left;
+}
 #available-chatrooms dt,
 #converse-roster dt {
   font-weight: normal;
@@ -1110,30 +1130,72 @@ dl.add-converse-contact {
   width: 300px;
 }
 #conversejs .controlbox-pane {
-  padding: 0;
-  border-bottom-right-radius: 4px;
+  text-align: center;
+  background-color: white;
   border-bottom-left-radius: 4px;
+  border-bottom-right-radius: 4px;
+  border: 0;
+  font-size: 14px;
+  height: 289px;
+  height: calc(100% - 35px);
+  overflow-y: auto;
+  padding: 0;
+  position: absolute;
+  width: 100%;
 }
 #conversejs .controlbox-pane dd {
   margin-left: 0;
   margin-bottom: 0;
-  padding: 1em;
 }
 #conversejs .controlbox-pane dd.odd {
   background-color: #DCEAC5;
 }
+#conversejs form#converse-register .title {
+  font-weight: bold;
+}
+#conversejs form#converse-register .info {
+  font-style: italic;
+  color: green;
+  font-size: 85%;
+  margin: 5px 0;
+}
+#conversejs .form-help,
+#conversejs form#converse-register .instructions {
+  color: gray;
+  font-size: 85%;
+}
+#conversejs .form-help:hover,
+#conversejs form#converse-register .instructions:hover {
+  color: #4f4f4f;
+}
+#conversejs .form-help {
+  padding-top: 5px;
+}
+#conversejs form#converse-register .form-errors {
+  color: red;
+  display: none;
+}
+#conversejs form#converse-register,
 #conversejs form#converse-login {
   background: white;
-  padding: 2em 0.5em;
+  padding: 1em 0.5em;
 }
+#conversejs form#converse-register input,
 #conversejs form#converse-login input {
-  width: 100%;
+  width: 178px;
+  height: 30px;
 }
+#conversejs form#converse-register label,
 #conversejs form#converse-login label {
   margin-top: 0.5em;
+  font-weight: bold;
 }
-#conversejs form#converse-login .login-submit {
+#conversejs form#converse-register .login-submit,
+#conversejs form#converse-login .login-submit,
+#conversejs form#converse-register .submit,
+#conversejs form#converse-login .submit {
   margin-top: 1em;
+  height: 30px;
 }
 #conversejs form.set-xmpp-status {
   background: none;
@@ -1182,11 +1244,11 @@ select#select-xmpp-status {
   text-decoration: none;
   border-top-right-radius: 4px;
   border-top-left-radius: 4px;
-  color: #666;
+  color: #888;
   text-shadow: 0 1px 0 #fafafa;
 }
 #conversejs .chat-head #controlbox-tabs li a:hover {
-  color: black;
+  color: #4f4f4f;
 }
 #conversejs .chat-head #controlbox-tabs li a {
   background-color: white;
@@ -1201,21 +1263,6 @@ select#select-xmpp-status {
   cursor: default;
   color: #666666;
 }
-#conversejs div#users,
-#conversejs div#chatrooms,
-#conversejs div#login-dialog,
-#conversejs div#settings {
-  border: 0;
-  font-size: 14px;
-  background-color: white;
-  border-bottom-right-radius: 4px;
-  border-bottom-left-radius: 4px;
-  width: 100%;
-  height: 289px;
-  height: calc(100% - 35px);
-  overflow-y: hidden;
-  position: absolute;
-}
 #conversejs div#chatrooms {
   overflow-y: auto;
 }
@@ -1413,6 +1460,9 @@ input.custom-xmpp-status {
 #conversejs .chatbox .dropdown dd.search-xmpp ul li:hover {
   background-color: #bed6e5;
 }
+#conversejs .xmpp-status-menu {
+  text-align: left;
+}
 #conversejs .xmpp-status-menu li a {
   width: 100%;
 }
@@ -1433,7 +1483,7 @@ input.custom-xmpp-status {
   bottom: 6px;
   box-shadow: 1px 3px 5px 3px rgba(0, 0, 0, 0.4);
   display: block;
-  height: 324px;
+  height: 400px;
   position: absolute;
 }
 #conversejs .minimized-chats-flyout {

+ 1 - 0
css/theme.css

@@ -16,6 +16,7 @@ h4,
 h5,
 h6 {
   margin: 0 0 35px;
+  text-transform: uppercase;
   font-family: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-weight: 700;
   letter-spacing: 1px;

+ 1 - 0
index.html

@@ -260,6 +260,7 @@
             play_sounds: true,
             roster_groups: true,
             show_controlbox_by_default: true,
+            debug: false,
             xhr_user_search: false
         });
     });

+ 97 - 33
less/converse.less

@@ -509,29 +509,32 @@
   }
 }
 
-span.spinner:before {
+#conversejs .spinner:before {
   font-size: 24px;
   font-family: 'Converse-js' !important;
   content: "\231b";
 }
 
-span.spinner {
+#conversejs .spinner {
     -webkit-animation: spin 2s infinite linear;
     -moz-animation: spin 2s infinite linear;
     -o-animation: spin 2s infinite linear;
     animation: spin 2s infinite linear;
-    width: 100%;
     display: block;
     text-align: center;
+    margin: 5px;
 }
 
-span.spinner.centered {
+#conversejs .centered {
     text-align: center;
-    padding-top: 5em;
+    display: block;
+    margin: 5em auto;
 }
 
-span.spinner.hor_centered {
+#conversejs .hor_centered {
     text-align: center;
+    display: block;
+    margin: 0 auto;
 }
 
 #conversejs #minimized-chats .box-flyout {
@@ -811,14 +814,33 @@ span.spinner.hor_centered {
     color: #7EABBB;
 }
 
-input.error {
+#conversejs input.error {
     border: 1px solid red;
 }
 
-#conversejs .conn-feedback.error {
+#conversejs .error {
     color: red;
 }
 
+#converse-register .provider-title {
+    font-size: 115%;
+}
+
+#converse-register .provider-score {
+    width: 178px;
+    margin-bottom: 8px;
+}
+
+#converse-register .form-help .url {
+    font-weight: bold;
+    color: #2D617A;
+}
+
+#conversejs .reg-feedback {
+    font-size: 85%;
+}
+
+#conversejs .reg-feedback,
 #converse-login .conn-feedback {
     width: 100%;
     text-align: center;
@@ -893,6 +915,7 @@ dl.add-converse-contact {
 #conversejs .fancy-dropdown {
     border:1px solid #ddd;
     height: 22px;
+    text-align: left;
 }
 
 #conversejs .fancy-dropdown a.choose-xmpp-status {
@@ -1074,6 +1097,7 @@ dl.add-converse-contact {
 }
 
 #converse-roster {
+    text-align: left;
     width: 100%;
     position: relative;
     margin: 0.5em 0 0 0;
@@ -1131,6 +1155,10 @@ dl.add-converse-contact {
     width: 148px;
 }
 
+#available-chatrooms {
+    text-align: left;
+}
+
 #available-chatrooms dt,
 #converse-roster dt {
     font-weight: normal;
@@ -1247,36 +1275,84 @@ dl.add-converse-contact {
 }
 
 #conversejs .controlbox-pane {
-    padding: 0;
-    border-bottom-right-radius: 4px;
+    text-align: center;
+    background-color: white;
     border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    border: 0;
+    font-size: 14px;
+    height: 289px;
+    height: ~"calc(100% - 35px)";
+    overflow-y: auto;
+    padding: 0;
+    position: absolute;
+    width: 100%;
 }
 
 #conversejs .controlbox-pane dd {
     margin-left: 0;
     margin-bottom: 0;
-    padding: 1em;
 }
 
 #conversejs .controlbox-pane dd.odd {
     background-color: #DCEAC5;
 }
 
+#conversejs form#converse-register .title {
+    font-weight: bold;
+}
+
+#conversejs form#converse-register .info {
+    font-style: italic;
+    color: green;
+    font-size: 85%;
+    margin: 5px 0;
+}
+
+#conversejs .form-help,
+#conversejs form#converse-register .instructions {
+    color: gray;
+    font-size: 85%;
+}
+
+#conversejs .form-help:hover,
+#conversejs form#converse-register .instructions:hover {
+    color: #4f4f4f;
+}
+
+#conversejs .form-help {
+    padding-top: 5px;
+}
+
+#conversejs form#converse-register .form-errors {
+    color: red;
+    display: none;
+}
+
+#conversejs form#converse-register,
 #conversejs form#converse-login {
     background: white;
-    padding: 2em 0.5em;
+    padding: 1em 0.5em;
 }
 
+#conversejs form#converse-register input,
 #conversejs form#converse-login input {
-    width: 100%;
+    width: 178px;
+    height: 30px;
 }
 
+#conversejs form#converse-register label,
 #conversejs form#converse-login label {
     margin-top: 0.5em;
+    font-weight: bold;
 }
 
-#conversejs form#converse-login .login-submit {
+#conversejs form#converse-register .login-submit,
+#conversejs form#converse-login .login-submit,
+#conversejs form#converse-register .submit,
+#conversejs form#converse-login .submit {
     margin-top: 1em;
+    height: 30px;
 }
 
 #conversejs form.set-xmpp-status {
@@ -1334,12 +1410,12 @@ select#select-xmpp-status {
     text-decoration: none;
     border-top-right-radius: 4px;
     border-top-left-radius: 4px;
-    color: #666;
+    color: #888;
     text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
 }
 
 #conversejs .chat-head #controlbox-tabs li a:hover {
-    color: black;
+    color: #4f4f4f
 }
 
 #conversejs .chat-head #controlbox-tabs li a {
@@ -1357,22 +1433,6 @@ select#select-xmpp-status {
     color: rgb(102,102,102);
 }
 
-#conversejs div#users,
-#conversejs div#chatrooms,
-#conversejs div#login-dialog,
-#conversejs div#settings {
-    border: 0;
-    font-size: 14px;
-    background-color: white;
-    border-bottom-right-radius: 4px;
-    border-bottom-left-radius: 4px;
-    width: 100%;
-    height: 289px;
-    height: ~"calc(100% - 35px)";
-    overflow-y: hidden;
-    position: absolute;
-}
-
 #conversejs div#chatrooms {
     overflow-y: auto;
 }
@@ -1607,6 +1667,10 @@ input.custom-xmpp-status {
     background-color: #bed6e5;
 }
 
+#conversejs .xmpp-status-menu {
+    text-align: left;
+}
+
 #conversejs .xmpp-status-menu li a {
     width: 100%;
 }
@@ -1631,7 +1695,7 @@ input.custom-xmpp-status {
     bottom: 6px;
     box-shadow: 1px 3px 5px 3px rgba(0,0,0,0.4);
     display: block;
-    height: 324px;
+    height: 400px;
     position: absolute;
 }
 

+ 27 - 5
main.js

@@ -1,3 +1,16 @@
+var config;
+if (typeof(require) === 'undefined') {
+    /* XXX: Hack to work around r.js's stupid parsing.
+    * We want to save the configuration in a variable so that we can reuse it in
+    * tests/main.js.
+    */
+    require = {
+        config: function (c) {
+            config = c;
+        }
+    };
+}
+
 require.config({
     baseUrl: '.',
     paths: {
@@ -79,9 +92,11 @@ require.config({
         "controlbox":               "src/templates/controlbox",
         "controlbox_toggle":        "src/templates/controlbox_toggle",
         "field":                    "src/templates/field",
+        "form_captcha":             "src/templates/form_captcha",
         "form_checkbox":            "src/templates/form_checkbox",
         "form_input":               "src/templates/form_input",
         "form_select":              "src/templates/form_select",
+        "form_textarea":            "src/templates/form_textarea",
         "group_header":             "src/templates/group_header",
         "info":                     "src/templates/info",
         "login_panel":              "src/templates/login_panel",
@@ -91,6 +106,9 @@ require.config({
         "occupant":                 "src/templates/occupant",
         "pending_contact":          "src/templates/pending_contact",
         "pending_contacts":         "src/templates/pending_contacts",
+        "register_panel":           "src/templates/register_panel",
+        "register_tab":             "src/templates/register_tab",
+        "registration_form":        "src/templates/registration_form",
         "requesting_contact":       "src/templates/requesting_contact",
         "requesting_contacts":      "src/templates/requesting_contacts",
         "room_description":         "src/templates/room_description",
@@ -103,8 +121,7 @@ require.config({
         "status_option":            "src/templates/status_option",
         "toggle_chats":             "src/templates/toggle_chats",
         "toolbar":                  "src/templates/toolbar",
-        "trimmed_chat":             "src/templates/trimmed_chat",
-        "form_textarea":            "src/templates/form_textarea"
+        "trimmed_chat":             "src/templates/trimmed_chat"
     },
 
     map: {
@@ -143,10 +160,15 @@ require.config({
         'strophe':              { exports: 'Strophe' },
         'strophe.disco':        { deps: ['strophe'] },
         'strophe.muc':          { deps: ['strophe'] },
+        'strophe.register':     { deps: ['strophe'] },
         'strophe.roster':       { deps: ['strophe'] },
         'strophe.vcard':        { deps: ['strophe'] }
     }
 });
-require(["converse"], function(converse) {
-    window.converse = converse;
-});
+
+if (typeof(require) === 'function') {
+    require(config);
+    require(["converse"], function(converse) {
+        window.converse = converse;
+    });
+}

+ 55 - 36
mockup/controlbox.html

@@ -31,16 +31,26 @@
             <div class="chat-head controlbox-head">
                 <ul id="controlbox-tabs">
                     <li><a class="current" href="#login">Sign in</a></li>
+                    <li><a href="#register">Register</a></li>
                 </ul>
                 <a class="close-chatbox-button icon-close"></a>
             </div>
-            <div id="login-dialog">
+            <div id="login-dialog" class="controlbox-pane">
                 <form id="converse-login">
-                    <label>XMPP/Jabber Username:</label><input type="text" id="jid">
+                    <label>XMPP Username:</label><input type="text" id="jid" placeholder="name@server">
                     <label>Password:</label><input type="password" id="password">
-                    <input class="login-submit" type="submit" value="Log In">
+                    <input class="submit" type="submit" value="Log In">
+                    <span class="conn-feedback"></span>
+                </form>
+            </div>
+            <div id="register" class="controlbox-pane" style="display: none">
+                <form id="converse-register">
+                    <label>XMPP Username:</label><input type="text" id="jid" placeholder="name@server">
+                    <label>Password:</label><input type="password" name="password">
+                    <label>Confirm Password:</label><input type="password" name="password_confirm">
+                    <input class="submit" type="submit" value="Register">
+                    <span class="conn-feedback"></span>
                 </form>
-                <span class="conn-feedback"></span>
             </div>
         </div>
     </div>
@@ -55,6 +65,7 @@
                 </ul>
                 <a class="close-chatbox-button icon-close"></a>
             </div>
+
             <div id="users" class="controlbox-pane" style="display: block;">
                 <form class="set-xmpp-status" action="" method="post">
                     <span id="xmpp-status-holder">
@@ -227,39 +238,41 @@
                         </dd>
                     </dl>
                 </div>
-                <div id="chatrooms" style="display: none;">
-                    <form class="add-chatroom" action="" method="post">
-                        <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
-                        <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
-                        <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
-                        <input type="submit" name="join" value="Join">
-                        <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
-                    </form>
-                    <dl id="available-chatrooms">
-                        <dt>Rooms on conference.opkode.im</dt>
-                        <dd class="available-chatroom">
-                            <a class="open-room" 
-                                data-room-jid="converse.js@conference.opkode.im"
-                                title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
-                            <a class="room-info icon-room-info" 
-                                data-room-jid="converse.js@conference.opkode.im" 
-                                title="Show more information on this room" href="#">&nbsp;</a>
-                            <div class="room-info">
-                                <p class="room-info"><strong>Description:</strong></p>
-                                <p class="room-info"><strong>Occupants:</strong> 2</p>
-                                <p class="room-info"><strong>Features:</strong> </p>
-                                <ul>
-                                    <li class="room-info">Moderated</li><li class="room-info">Open room</li>
-                                    <li class="room-info">Permanent room</li><li class="room-info">Public</li>
-                                    <li class="room-info">Semi-anonymous</li>
-                                    <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
-                                    <p></p>
-                                </ul>
-                            </div>
-                        </dd>
-                    </dl>
-                </div>
             </div>
+
+            <div id="chatrooms" class="controlbox-pane" style="display: none;">
+                <form class="add-chatroom" action="" method="post">
+                    <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
+                    <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
+                    <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
+                    <input type="submit" name="join" value="Join">
+                    <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
+                </form>
+                <dl id="available-chatrooms">
+                    <dt>Rooms on conference.opkode.im</dt>
+                    <dd class="available-chatroom">
+                        <a class="open-room" 
+                            data-room-jid="converse.js@conference.opkode.im"
+                            title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
+                        <a class="room-info icon-room-info" 
+                            data-room-jid="converse.js@conference.opkode.im" 
+                            title="Show more information on this room" href="#">&nbsp;</a>
+                        <div class="room-info">
+                            <p class="room-info"><strong>Description:</strong></p>
+                            <p class="room-info"><strong>Occupants:</strong> 2</p>
+                            <p class="room-info"><strong>Features:</strong> </p>
+                            <ul>
+                                <li class="room-info">Moderated</li><li class="room-info">Open room</li>
+                                <li class="room-info">Permanent room</li><li class="room-info">Public</li>
+                                <li class="room-info">Semi-anonymous</li>
+                                <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
+                                <p></p>
+                            </ul>
+                        </div>
+                    </dd>
+                </dl>
+            </div>
+
         </div>
     </div>
 </div>
@@ -272,6 +285,12 @@ $(document).ready(function () {
     $('a[href=#users]').click(function (ev) {
         switchTab(ev); 
     });
+    $('a[href=#register]').click(function (ev) { 
+        switchTab(ev);
+    });
+    $('a[href=#login]').click(function (ev) { 
+        switchTab(ev);
+    });
 
     $("a.choose-xmpp-status").click(function (ev) {
         ev.preventDefault();

+ 33 - 25
src/templates.js

@@ -18,6 +18,7 @@ define("converse-templates", [
     "tpl!controlbox_toggle",
     "tpl!field",
     "tpl!form_checkbox",
+    "tpl!form_captcha",
     "tpl!form_input",
     "tpl!form_select",
     "tpl!group_header",
@@ -29,6 +30,9 @@ define("converse-templates", [
     "tpl!occupant",
     "tpl!pending_contact",
     "tpl!pending_contacts",
+    "tpl!register_panel",
+    "tpl!register_tab",
+    "tpl!registration_form",
     "tpl!requesting_contact",
     "tpl!requesting_contacts",
     "tpl!room_description",
@@ -64,30 +68,34 @@ define("converse-templates", [
         controlbox_toggle:      arguments[16],
         field:                  arguments[17],
         form_checkbox:          arguments[18],
-        form_input:             arguments[19],
-        form_select:            arguments[20],
-        group_header:           arguments[21],
-        info:                   arguments[22],
-        login_panel:            arguments[23],
-        login_tab:              arguments[24],
-        message:                arguments[25],
-        new_day:                arguments[26],
-        occupant:               arguments[27],
-        pending_contact:        arguments[28],
-        pending_contacts:       arguments[29],
-        requesting_contact:     arguments[30],
-        requesting_contacts:    arguments[31],
-        room_description:       arguments[32],
-        room_item:              arguments[33],
-        room_panel:             arguments[34],
-        roster:                 arguments[35],
-        roster_item:            arguments[36],
-        select_option:          arguments[37],
-        search_contact:         arguments[38],
-        status_option:          arguments[39],
-        toggle_chats:           arguments[40],
-        toolbar:                arguments[41],
-        trimmed_chat:           arguments[42],
-        form_textarea:          arguments[43]
+        form_captcha:           arguments[19],
+        form_input:             arguments[20],
+        form_select:            arguments[21],
+        group_header:           arguments[22],
+        info:                   arguments[23],
+        login_panel:            arguments[24],
+        login_tab:              arguments[25],
+        message:                arguments[26],
+        new_day:                arguments[27],
+        occupant:               arguments[28],
+        pending_contact:        arguments[29],
+        pending_contacts:       arguments[30],
+        register_panel:         arguments[31],
+        register_tab:           arguments[32],
+        registration_form:      arguments[33],
+        requesting_contact:     arguments[34],
+        requesting_contacts:    arguments[35],
+        room_description:       arguments[36],
+        room_item:              arguments[37],
+        room_panel:             arguments[38],
+        roster:                 arguments[39],
+        roster_item:            arguments[40],
+        select_option:          arguments[41],
+        search_contact:         arguments[42],
+        status_option:          arguments[43],
+        toggle_chats:           arguments[44],
+        toolbar:                arguments[45],
+        trimmed_chat:           arguments[46],
+        form_textarea:          arguments[47]
     };
 });

+ 9 - 0
src/templates/form_captcha.html

@@ -0,0 +1,9 @@
+{[ if (label) { ]}
+<label>
+    {{label}}
+</label>
+{[ } ]}
+<img src="data:{{type}};base64,{{data}}">
+<input name="{{name}}" type="text" {[ if (required) { ]} class="required" {[ } ]} >
+
+

+ 2 - 1
src/templates/form_checkbox.html

@@ -1 +1,2 @@
-<label>{{label}}<input name="{{name}}" type="{{type}}" {{checked}}></label>
+<label>{{label}}</label>
+<input name="{{name}}" type="{{type}}" {{checked}}>

+ 8 - 1
src/templates/form_input.html

@@ -1 +1,8 @@
-<label>{{label}}<input name="{{name}}" type="{{type}}" value="{{value}}"></label>
+{[ if (label) { ]}
+<label>
+    {{label}}
+</label>
+{[ } ]}
+<input name="{{name}}" type="{{type}}" 
+    {[ if (value) { ]} value="{{value}}" {[ } ]}
+    {[ if (required) { ]} class="required" {[ } ]} >

+ 2 - 1
src/templates/form_select.html

@@ -1 +1,2 @@
-<label>{{label}}<select name="{{name}}"  {[ if (multiple) { ]} multiple="multiple" {[ } ]}>{{options}}</select></label>
+<label>{{label}}</label>
+<select name="{{name}}"  {[ if (multiple) { ]} multiple="multiple" {[ } ]}>{{options}}</select>

+ 2 - 1
src/templates/form_textarea.html

@@ -1 +1,2 @@
-<label class="label-ta">{{label}}<textarea name="{{name}}">{{value}}</textarea></label>
+<label class="label-ta">{{label}}</label>
+<textarea name="{{name}}">{{value}}</textarea>

+ 3 - 3
src/templates/login_panel.html

@@ -1,8 +1,8 @@
 <form id="converse-login" method="post">
     <label>{{label_username}}</label>
-    <input type="username" name="jid" placeholder="Username">
+    <input type="username" name="jid" placeholder="user@server">
     <label>{{label_password}}</label>
-    <input type="password" name="password" placeholder="Password">
-    <input class="login-submit" type="submit" value="{{label_login}}">
+    <input type="password" name="password" placeholder="password">
+    <input class="submit" type="submit" value="{{label_login}}">
     <span class="conn-feedback"></span>
 </form>

+ 7 - 0
src/templates/register_panel.html

@@ -0,0 +1,7 @@
+<form id="converse-register">
+    <span class="reg-feedback"></span>
+    <label>{{label_domain}}</label>
+    <input type="text" name="domain" placeholder=" e.g. conversejs.org">
+    <p class="form-help">Tip: A list of public XMPP providers is available <a href="https://xmpp.net/directory.php" class="url" target="_blank">here</a>.</p>
+    <input class="submit" type="submit" value="{{label_register}}">
+</form>

+ 1 - 0
src/templates/register_tab.html

@@ -0,0 +1 @@
+<li><a class="s" href="#register">{{label_register}}</a></li>

+ 6 - 0
src/templates/registration_form.html

@@ -0,0 +1,6 @@
+<p class="provider-title">{{domain}}</p>
+<a href='https://xmpp.net/result.php?domain={{domain}}&amp;type=client'>
+    <img class="provider-score" src='https://xmpp.net/badge.php?domain={{domain}}' alt='xmpp.net score' />
+</a>
+<p class="title">{{title}}</p>
+<p class="instructions">{{instructions}}</p>

+ 123 - 1
src/utils.js

@@ -1,4 +1,21 @@
-define(["jquery"], function ($) {
+define(["jquery", "converse-templates"], function ($, templates) {
+    "use strict";
+
+    var XFORM_TYPE_MAP = {
+        'text-private': 'password',
+        'text-single': 'textline',
+        'fixed': 'label',
+        'boolean': 'checkbox',
+        'hidden': 'hidden',
+        'jid-multi': 'textarea',
+        'list-single': 'dropdown',
+        'list-multi': 'dropdown'
+    };
+
+    $.expr[':'].emptyVal = function(obj){
+        return obj.value === '';
+    };
+
     $.fn.hasScrollBar = function() {
         if (!$.contains(document, this.get(0))) {
             return false;
@@ -52,6 +69,111 @@ define(["jquery"], function ($) {
                 * See actionInfoMessages
                 */
             return str;
+        },
+
+        webForm2xForm: function (field) {
+            /* Takes an HTML DOM and turns it into an XForm field.
+             *
+             * Parameters:
+             *      (DOMElement) field - the field to convert
+             */
+            var $input = $(field), value;
+            if ($input.is('[type=checkbox]')) {
+                value = $input.is(':checked') && 1 || 0;
+            } else if ($input.is('textarea')) {
+                value = [];
+                var lines = $input.val().split('\n');
+                for( var vk=0; vk<lines.length; vk++) {
+                    var val = $.trim(lines[vk]);
+                    if (val === '')
+                        continue;
+                    value.push(val);
+                }
+            } else {
+                value = $input.val();
+            }
+            return $(templates.field({
+                name: $input.attr('name'),
+                value: value
+            }))[0];
+        },
+
+        xForm2webForm: function ($field, $stanza) {
+            /* Takes a field in XMPP XForm (XEP-004: Data Forms) format
+             * and turns it into a HTML DOM field.
+             *
+             *  Parameters:
+             *      (XMLElement) field - the field to convert
+             */
+
+            // FIXME: take <required> into consideration
+            var options = [], j, $options, $values, value, values;
+
+            if ($field.attr('type') == 'list-single' || $field.attr('type') == 'list-multi') {
+                values = [];
+                $values = $field.children('value');
+                for (j=0; j<$values.length; j++) {
+                    values.push($($values[j]).text());
+                }
+                $options = $field.children('option');
+                for (j=0; j<$options.length; j++) {
+                    value = $($options[j]).find('value').text();
+                    options.push(templates.select_option({
+                        value: value,
+                        label: $($options[j]).attr('label'),
+                        selected: (values.indexOf(value) >= 0),
+                        required: $field.find('required').length
+                    }));
+                }
+                return templates.form_select({
+                    name: $field.attr('var'),
+                    label: $field.attr('label'),
+                    options: options.join(''),
+                    multiple: ($field.attr('type') == 'list-multi'),
+                    required: $field.find('required').length
+                });
+            } else if ($field.attr('type') == 'fixed') {
+                return $('<p class="form-help">').text($field.find('value').text());
+            } else if ($field.attr('type') == 'jid-multi') {
+                return templates.form_textarea({
+                    name: $field.attr('var'),
+                    label: $field.attr('label') || '',
+                    value: $field.find('value').text(),
+                    required: $field.find('required').length
+                });
+            } else if ($field.attr('type') == 'boolean') {
+                return templates.form_checkbox({
+                    name: $field.attr('var'),
+                    type: XFORM_TYPE_MAP[$field.attr('type')],
+                    label: $field.attr('label') || '',
+                    checked: $field.find('value').text() === "1" && 'checked="1"' || '',
+                    required: $field.find('required').length
+                });
+            } else if ($field.attr('type')) {
+                return templates.form_input({
+                    name: $field.attr('var'),
+                    type: XFORM_TYPE_MAP[$field.attr('type')],
+                    label: $field.attr('label') || '',
+                    value: $field.find('value').text(),
+                    required: $field.find('required').length
+                });
+            } else {
+                if ($field.attr('var') === 'ocr') { // Captcha
+                    return _.reduce(_.map($field.find('uri'),
+                            $.proxy(function (uri) {
+                                return templates.form_captcha({
+                                    label: this.$field.attr('label'),
+                                    name: this.$field.attr('var'),
+                                    data: this.$stanza.find('data[cid="'+uri.textContent.replace(/^cid:/, '')+'"]').text(),
+                                    type: uri.getAttribute('type'),
+                                    required: this.$field.find('required').length
+                                });
+                            }, {'$stanza': $stanza, '$field': $field})
+                        ),
+                        function (memo, num) { return memo + num; }, ''
+                    );
+                }
+            }
         }
     };
     return utils;

+ 0 - 149
tests/main.js

@@ -1,152 +1,3 @@
-config = {
-    baseUrl: '.',
-    paths: {
-        "backbone":                 "components/backbone/backbone",
-        "backbone.browserStorage":  "components/backbone.browserStorage/backbone.browserStorage",
-        "backbone.overview":        "components/backbone.overview/backbone.overview",
-        "bootstrap":                "components/bootstrap/dist/js/bootstrap",           // XXX: Only required for https://conversejs.org website
-        "bootstrapJS":              "components/bootstrapJS/index",                     // XXX: Only required for https://conversejs.org website
-        "converse-dependencies":    "src/deps-website",
-        "converse-templates":       "src/templates",
-        "eventemitter":             "components/otr/build/dep/eventemitter",
-        "jquery":                   "components/jquery/dist/jquery",
-        "jquery-private":           "src/jquery-private",
-        "jquery.browser":           "components/jquery.browser/index",
-        "jquery.easing":            "components/jquery-easing-original/index",          // XXX: Only required for https://conversejs.org website
-        "moment":                   "components/momentjs/moment",
-        "strophe":                  "components/strophe/strophe",
-        "strophe.disco":            "components/strophejs-plugins/disco/strophe.disco",
-        "strophe.muc":              "components/strophe.muc/index",
-        "strophe.roster":           "src/strophe.roster",
-        "strophe.vcard":            "components/strophejs-plugins/vcard/strophe.vcard",
-        "text":                     'components/requirejs-text/text',
-        "tpl":                      'components/requirejs-tpl-jcbrand/tpl',
-        "typeahead":                "components/typeahead.js/index",
-        "underscore":               "components/underscore/underscore",
-        "utils":                    "src/utils",
-
-        // Off-the-record-encryption
-        "bigint":               "src/bigint",
-        "crypto":               "src/crypto",
-        "crypto.aes":           "components/otr/vendor/cryptojs/aes",
-        "crypto.cipher-core":   "components/otr/vendor/cryptojs/cipher-core",
-        "crypto.core":          "components/otr/vendor/cryptojs/core",
-        "crypto.enc-base64":    "components/otr/vendor/cryptojs/enc-base64",
-        "crypto.evpkdf":        "components/crypto-js-evanvosberg/src/evpkdf",
-        "crypto.hmac":          "components/otr/vendor/cryptojs/hmac",
-        "crypto.md5":           "components/crypto-js-evanvosberg/src/md5",
-        "crypto.mode-ctr":      "components/otr/vendor/cryptojs/mode-ctr",
-        "crypto.pad-nopadding": "components/otr/vendor/cryptojs/pad-nopadding",
-        "crypto.sha1":         "components/otr/vendor/cryptojs/sha1",
-        "crypto.sha256":        "components/otr/vendor/cryptojs/sha256",
-        "salsa20":              "components/otr/build/dep/salsa20",
-        "otr":                  "src/otr",
-
-        // Locales paths
-        "locales":   "locale/locales",
-        "jed":       "components/jed/jed",
-        "af":        "locale/af/LC_MESSAGES/af",
-        "de":        "locale/de/LC_MESSAGES/de",
-        "en":        "locale/en/LC_MESSAGES/en",
-        "es":        "locale/es/LC_MESSAGES/es",
-        "fr":        "locale/fr/LC_MESSAGES/fr",
-        "he":        "locale/he/LC_MESSAGES/he",
-        "hu":        "locale/hu/LC_MESSAGES/hu",
-        "id":        "locale/id/LC_MESSAGES/id",
-        "it":        "locale/it/LC_MESSAGES/it",
-        "ja":        "locale/ja/LC_MESSAGES/ja",
-        "nl":        "locale/nl/LC_MESSAGES/nl",
-        "pt_BR":     "locale/pt_BR/LC_MESSAGES/pt_BR",
-        "ru":        "locale/ru/LC_MESSAGES/ru",
-        "zh":        "locale/zh/LC_MESSAGES/zh",
-
-        // Templates
-        "action":                   "src/templates/action",
-        "add_contact_dropdown":     "src/templates/add_contact_dropdown",
-        "add_contact_form":         "src/templates/add_contact_form",
-        "change_status_message":    "src/templates/change_status_message",
-        "chat_status":              "src/templates/chat_status",
-        "chatarea":                 "src/templates/chatarea",
-        "chatbox":                  "src/templates/chatbox",
-        "chatroom":                 "src/templates/chatroom",
-        "chatroom_password_form":   "src/templates/chatroom_password_form",
-        "chatroom_sidebar":         "src/templates/chatroom_sidebar",
-        "chatrooms_tab":            "src/templates/chatrooms_tab",
-        "chats_panel":              "src/templates/chats_panel",
-        "choose_status":            "src/templates/choose_status",
-        "contacts_panel":           "src/templates/contacts_panel",
-        "contacts_tab":             "src/templates/contacts_tab",
-        "controlbox":               "src/templates/controlbox",
-        "controlbox_toggle":        "src/templates/controlbox_toggle",
-        "field":                    "src/templates/field",
-        "form_checkbox":            "src/templates/form_checkbox",
-        "form_input":               "src/templates/form_input",
-        "form_select":              "src/templates/form_select",
-        "group_header":             "src/templates/group_header",
-        "info":                     "src/templates/info",
-        "login_panel":              "src/templates/login_panel",
-        "login_tab":                "src/templates/login_tab",
-        "message":                  "src/templates/message",
-        "new_day":                  "src/templates/new_day",
-        "occupant":                 "src/templates/occupant",
-        "pending_contact":          "src/templates/pending_contact",
-        "pending_contacts":         "src/templates/pending_contacts",
-        "requesting_contact":       "src/templates/requesting_contact",
-        "requesting_contacts":      "src/templates/requesting_contacts",
-        "room_description":         "src/templates/room_description",
-        "room_item":                "src/templates/room_item",
-        "room_panel":               "src/templates/room_panel",
-        "roster":                   "src/templates/roster",
-        "roster_item":              "src/templates/roster_item",
-        "search_contact":           "src/templates/search_contact",
-        "select_option":            "src/templates/select_option",
-        "status_option":            "src/templates/status_option",
-        "toggle_chats":             "src/templates/toggle_chats",
-        "toolbar":                  "src/templates/toolbar",
-        "trimmed_chat":             "src/templates/trimmed_chat"
-    },
-
-    map: {
-        // '*' means all modules will get 'jquery-private'
-        // for their 'jquery' dependency.
-        '*': { 'jquery': 'jquery-private' },
-        // 'jquery-private' wants the real jQuery module
-        // though. If this line was not here, there would
-        // be an unresolvable cyclic dependency.
-        'jquery-private': { 'jquery': 'jquery' }
-    },
-
-    tpl: {
-        // Configuration for requirejs-tpl
-        // Use Mustache style syntax for variable interpolation
-        templateSettings: {
-            evaluate : /\{\[([\s\S]+?)\]\}/g,
-            interpolate : /\{\{([\s\S]+?)\}\}/g
-        }
-    },
-
-    // define module dependencies for modules not using define
-    shim: {
-        'underscore':           { exports: '_' },
-        'crypto.aes':           { deps: ['crypto.cipher-core'] },
-        'crypto.cipher-core':   { deps: ['crypto.enc-base64', 'crypto.evpkdf'] },
-        'crypto.enc-base64':    { deps: ['crypto.core'] },
-        'crypto.evpkdf':        { deps: ['crypto.md5'] },
-        'crypto.hmac':          { deps: ['crypto.core'] },
-        'crypto.md5':           { deps: ['crypto.core'] },
-        'crypto.mode-ctr':      { deps: ['crypto.cipher-core'] },
-        'crypto.pad-nopadding': { deps: ['crypto.cipher-core'] },
-        'crypto.sha1':          { deps: ['crypto.core'] },
-        'crypto.sha256':        { deps: ['crypto.core'] },
-        'bigint':               { deps: ['crypto'] },
-        'strophe':              { exports: 'Strophe' },
-        'strophe.disco':        { deps: ['strophe'] },
-        'strophe.muc':          { deps: ['strophe'] },
-        'strophe.roster':       { deps: ['strophe'] },
-        'strophe.vcard':        { deps: ['strophe'] }
-    }
-};
-
 // Extra test dependencies
 config.paths.mock = "tests/mock";
 config.paths.test_utils = "tests/utils";