Browse Source

Merge branch 'templates' into 0.8

Conflicts:
	converse.js
JC Brand 11 years ago
parent
commit
823f93f006
46 changed files with 580 additions and 514 deletions
  1. 6 2
      bower.json
  2. 182 432
      converse.js
  3. 19 5
      main.js
  4. 4 0
      spec/chatbox.js
  5. 4 0
      spec/chatroom.js
  6. 2 2
      spec/controlbox.js
  7. 77 0
      src/templates.js
  8. 4 0
      src/templates/action.html
  9. 8 0
      src/templates/add_contact_dropdown.html
  10. 9 0
      src/templates/add_contact_form.html
  11. 5 0
      src/templates/change_status_message.html
  12. 11 0
      src/templates/chat_status.html
  13. 13 0
      src/templates/chatarea.html
  14. 17 0
      src/templates/chatbox.html
  15. 9 0
      src/templates/chatroom.html
  16. 1 0
      src/templates/chatrooms_tab.html
  17. 4 0
      src/templates/choose_status.html
  18. 1 0
      src/templates/contacts.html
  19. 10 0
      src/templates/contacts_panel.html
  20. 1 0
      src/templates/contacts_tab.html
  21. 5 0
      src/templates/controlbox.html
  22. 2 0
      src/templates/controlbox_toggle.html
  23. 1 0
      src/templates/field.html
  24. 1 0
      src/templates/form_checkbox.html
  25. 1 0
      src/templates/form_input.html
  26. 1 0
      src/templates/form_select.html
  27. 1 0
      src/templates/info.html
  28. 7 0
      src/templates/login_panel.html
  29. 1 0
      src/templates/login_tab.html
  30. 4 0
      src/templates/message.html
  31. 1 0
      src/templates/new_day.html
  32. 11 0
      src/templates/occupant.html
  33. 1 0
      src/templates/pending_contact.html
  34. 1 0
      src/templates/pending_contacts.html
  35. 3 0
      src/templates/requesting_contact.html
  36. 1 0
      src/templates/requesting_contacts.html
  37. 42 0
      src/templates/room_description.html
  38. 6 0
      src/templates/room_item.html
  39. 11 0
      src/templates/room_panel.html
  40. 2 0
      src/templates/roster_item.html
  41. 9 0
      src/templates/search_contact.html
  42. 1 0
      src/templates/select_option.html
  43. 6 0
      src/templates/status_option.html
  44. 53 0
      src/templates/toolbar.html
  45. 1 0
      tests.html
  46. 20 73
      tests_main.js

+ 6 - 2
bower.json

@@ -3,7 +3,9 @@
   "version": "0.7.2",
   "devDependencies": {
     "jasmine": "https://github.com/jcbrand/jasmine.git#1_3_x",
-    "otr": "0.2.7"
+    "otr": "0.2.7",
+    "requirejs-text": "~2.0.10",
+    "requirejs-tpl-jfparadis": "*"
   },
   "dependencies": {
     "requirejs": "2.1.8",
@@ -20,7 +22,9 @@
     "strophe.muc": "https://raw.github.com/strophe/strophejs-plugins/02310ad1b8da2962cd05b0f4bceaecca134efed4/muc/strophe.muc.js",
     "otr": "0.2.7",
     "crypto-js": "~3.1.2",
-    "almond": "~0.2.6"
+    "almond": "~0.2.6",
+    "requirejs-text": "~2.0.10",
+    "requirejs-tpl-jfparadis": "*"
   },
   "exportsOverride": {}
 }

+ 182 - 432
converse.js

@@ -12,27 +12,26 @@
         console = { log: function () {}, error: function () {} };
     }
     if (typeof define === 'function' && define.amd) {
-        define("converse", ["converse-dependencies"], function(otr) {
-            // Use Mustache style syntax for variable interpolation
-            _.templateSettings = {
-                evaluate : /\{\[([\s\S]+?)\]\}/g,
-                interpolate : /\{\{([\s\S]+?)\}\}/g
-            };
-            if (typeof otr !== "undefined") {
-                return factory(jQuery, _, otr.OTR, otr.DSA, console);
-            } else {
-                return factory(jQuery, _, undefined, undefined, console);
+        define("converse",
+              ["converse-dependencies", "converse-templates"],
+            function(otr, templates) {
+                if (typeof otr !== "undefined") {
+                    return factory(jQuery, _, otr.OTR, otr.DSA, console, templates);
+                } else {
+                    return factory(jQuery, _, undefined, undefined, console, templates);
+                }
             }
-        });
+        );
     } else {
         // Browser globals
+        // FIXME
         _.templateSettings = {
             evaluate : /\{\[([\s\S]+?)\]\}/g,
             interpolate : /\{\{([\s\S]+?)\}\}/g
         };
         root.converse = factory(jQuery, _, OTR, DSA, console || {log: function(){}});
     }
-}(this, function ($, _, OTR, DSA, console) {
+}(this, function ($, _, OTR, DSA, console, templates) {
     $.fn.addHyperlinks = function() {
         if (this.length > 0) {
             this.each(function(i, obj) {
@@ -86,6 +85,7 @@
     };
 
     var converse = {
+        templates: templates,
         emit: function(evt, data) {
             $(this).trigger(evt, data);
         },
@@ -731,99 +731,6 @@
                 'click .toggle-call': 'toggleCall'
             },
 
-            template: _.template(
-                '<div class="chat-head chat-head-chatbox">' +
-                    '<a class="close-chatbox-button icon-close"></a>' +
-                    '<a href="{{url}}" target="_blank" class="user">' +
-                        '<div class="chat-title"> {{ fullname }} </div>' +
-                    '</a>' +
-                    '<p class="user-custom-message"><p/>' +
-                '</div>' +
-                '<div class="chat-content"></div>' +
-                '<form class="sendXMPPMessage" action="" method="post">' +
-                    '{[ if ('+converse.show_toolbar+') { ]}' +
-                        '<ul class="chat-toolbar no-text-select"></ul>'+
-                    '{[ } ]}' +
-                '<textarea ' +
-                    'type="text" ' +
-                    'class="chat-textarea" ' +
-                    'placeholder="'+__('Personal message')+'"/>'+
-                '</form>'
-            ),
-
-
-            toolbar_template: _.template(
-                '{[ if (show_emoticons)  { ]}' +
-                    '<li class="toggle-smiley icon-happy" title="Insert a smilery">' +
-                        '<ul>' +
-                            '<li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>'+
-                            '<li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>'+
-                            '<li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>'+
-                            '<li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>'+
-                            '<li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>'+
-                            '<li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>'+
-                            '<li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>'+
-                            '<li><a class="icon-wondering" href="#" data-emoticon=":\\"></a></li>'+
-                            '<li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>'+
-                            '<li><a class="icon-sad" href="#" data-emoticon=":("></a></li>'+
-                            '<li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>'+
-                            '<li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>'+
-                            '<li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>'+
-                        '</ul>' +
-                    '</li>' +
-                '{[ } ]}' +
-                '{[ if (' + converse.show_call_button + ')  { ]}' +
-                    '<li><a class="toggle-call icon-phone" title="Start a call"></a></li>' +
-                '{[ } ]}' +
-                '{[ if (allow_otr)  { ]}' +
-                    '<li class="toggle-otr {{otr_status_class}}" title="{{otr_tooltip}}">'+
-                        '<span class="chat-toolbar-text">{{otr_translated_status}}</span>'+
-                        '{[ if (otr_status == "'+UNENCRYPTED+'") { ]}' +
-                            '<span class="icon-unlocked"></span>'+
-                        '{[ } ]}' +
-                        '{[ if (otr_status == "'+UNVERIFIED+'") { ]}' +
-                            '<span class="icon-lock"></span>'+
-                        '{[ } ]}' +
-                        '{[ if (otr_status == "'+VERIFIED+'") { ]}' +
-                            '<span class="icon-lock"></span>'+
-                        '{[ } ]}' +
-                        '{[ if (otr_status == "'+FINISHED+'") { ]}' +
-                            '<span class="icon-unlocked"></span>'+
-                        '{[ } ]}' +
-                        '<ul>'+
-                            '{[ if (otr_status == "'+UNENCRYPTED+'") { ]}' +
-                                '<li><a class="start-otr" href="#">'+__('Start encrypted conversation')+'</a></li>'+
-                            '{[ } ]}' +
-                            '{[ if (otr_status != "'+UNENCRYPTED+'") { ]}' +
-                                '<li><a class="start-otr" href="#">'+__('Refresh encrypted conversation')+'</a></li>'+
-                                '<li><a class="end-otr" href="#">'+__('End encrypted conversation')+'</a></li>'+
-                                '<li><a class="auth-otr" data-scheme="smp" href="#">'+__('Verify with SMP')+'</a></li>'+
-                            '{[ } ]}' +
-                            '{[ if (otr_status == "'+UNVERIFIED+'") { ]}' +
-                                '<li><a class="auth-otr" data-scheme="fingerprint" href="#">'+__('Verify with fingerprints')+'</a></li>'+
-                            '{[ } ]}' +
-                            '<li><a href="http://www.cypherpunks.ca/otr/help/3.2.0/levels.php" target="_blank">'+__("What\'s this?")+'</a></li>'+
-                        '</ul>'+
-                    '</li>'+
-                '{[ } ]}'
-            ),
-
-            message_template: _.template(
-                '<div class="chat-message {{extra_classes}}">' +
-                    '<span class="chat-message-{{sender}}">{{time}} {{username}}:&nbsp;</span>' +
-                    '<span class="chat-message-content">{{message}}</span>' +
-                '</div>'),
-
-            action_template: _.template(
-                '<div class="chat-message {{extra_classes}}">' +
-                    '<span class="chat-message-{{sender}}">{{time}} **{{username}} </span>' +
-                    '<span class="chat-message-content">{{message}}</span>' +
-                '</div>'),
-
-            new_day_template: _.template(
-                '<time class="chat-date" datetime="{{isodate}}">{{datestring}}</time>'
-                ),
-
             initialize: function (){
                 this.model.messages.on('add', this.onMessageAdded, this);
                 this.model.on('show', this.show, this);
@@ -849,7 +756,17 @@
 
             render: function () {
                 this.$el.attr('id', this.model.get('box_id'))
-                    .html(this.template(this.model.toJSON()));
+                    .html(
+                        converse.templates.chatbox(
+                            _.extend(
+                                this.model.toJSON(),
+                                {
+                                    show_toolbar: converse.show_toolbar,
+                                    label_personal_message: __('Personal message')
+                                }
+                            )
+                        )
+                    );
                 this.renderToolbar().renderAvatar();
                 return this;
             },
@@ -861,35 +778,6 @@
                 this.scrollDown();
             },
 
-            renderEmoticons: function (text) {
-                if (converse.show_emoticons) {
-                    text = text.replace(/:\)/g, '<span class="emoticon icon-smiley"></span>');
-                    text = text.replace(/:\-\)/g, '<span class="emoticon icon-smiley"></span>');
-                    text = text.replace(/;\)/g, '<span class="emoticon icon-wink"></span>');
-                    text = text.replace(/;\-\)/g, '<span class="emoticon icon-wink"></span>');
-                    text = text.replace(/:D/g, '<span class="emoticon icon-grin"></span>');
-                    text = text.replace(/:\-D/g, '<span class="emoticon icon-grin"></span>');
-                    text = text.replace(/:P/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/:\-P/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/:p/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/:\-p/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/8\)/g, '<span class="emoticon icon-cool"></span>');
-                    text = text.replace(/>:\)/g, '<span class="emoticon icon-evil"></span>');
-                    text = text.replace(/:S/g, '<span class="emoticon icon-confused"></span>');
-                    text = text.replace(/:\\/g, '<span class="emoticon icon-wondering"></span>');
-                    text = text.replace(/:\//g, '<span class="emoticon icon-wondering"></span>');
-                    text = text.replace(/>:\(/g, '<span class="emoticon icon-angry"></span>');
-                    text = text.replace(/:\(/g, '<span class="emoticon icon-sad"></span>');
-                    text = text.replace(/:\-\(/g, '<span class="emoticon icon-sad"></span>');
-                    text = text.replace(/:O/g, '<span class="emoticon icon-shocked"></span>');
-                    text = text.replace(/:\-O/g, '<span class="emoticon icon-shocked"></span>');
-                    text = text.replace(/\=\-O/g, '<span class="emoticon icon-shocked"></span>');
-                    text = text.replace(/\(\^.\^\)b/g, '<span class="emoticon icon-thumbs-up"></span>');
-                    text = text.replace(/<3/g, '<span class="emoticon icon-heart"></span>');
-                }
-                return text;
-            },
-
             showMessage: function ($el, msg_dict) {
                 var this_date = converse.parseISO8601(msg_dict.time),
                     text = msg_dict.message,
@@ -899,10 +787,10 @@
 
                 if ((match) && (match[1] === 'me')) {
                     text = text.replace(/^\/me/, '');
-                    template = this.action_template;
+                    template = converse.templates.action_template;
                     username = msg_dict.fullname;
                 } else  {
-                    template = this.message_template;
+                    template = converse.templates.message;
                     username = sender === 'me' && __('me') || msg_dict.fullname;
                 }
                 $el.find('div.chat-event').remove();
@@ -925,7 +813,7 @@
                 var $el = this.$el.find('.chat-content');
                 $el.find('div.chat-event').remove();
                 $el.append(
-                    this.message_template({
+                    converse.templates.message({
                         'sender': sender,
                         'time': (new Date()).toTimeString().substring(0,5),
                         'message': text,
@@ -961,7 +849,7 @@
                     isodate.setUTCHours(0,0,0,0);
                     isodate = converse.toISOString(isodate);
                     if (this.isDifferentDay(prev_date, this_date)) {
-                        $chat_content.append(this.new_day_template({
+                        $chat_content.append(converse.templates.new_day({
                             isodate: isodate,
                             datestring: this_date.toString().substring(0,15)
                         }));
@@ -1268,11 +1156,27 @@
                     } else if (data.otr_status == FINISHED){
                         data.otr_tooltip = __('Your buddy has closed their end of the private session, you should do the same');
                     }
-                    data.allow_otr = converse.allow_otr && !this.is_chatroom;
-                    data.show_emoticons = converse.show_emoticons;
-                    data.otr_translated_status = OTR_TRANSLATED_MAPPING[data.otr_status];
-                    data.otr_status_class = OTR_CLASS_MAPPING[data.otr_status];
-                    this.$el.find('.chat-toolbar').html(this.toolbar_template(data));
+                    this.$el.find('.chat-toolbar').html(
+                        converse.templates.toolbar(
+                            _.extend(data, {
+                                FINISHED: FINISHED,
+                                UNENCRYPTED: UNENCRYPTED,
+                                UNVERIFIED: UNVERIFIED,
+                                VERIFIED: VERIFIED, 
+                                allow_otr: converse.allow_otr && !this.is_chatroom,
+                                label_end_encrypted_conversation: __('End encrypted conversation'),
+                                label_refresh_encrypted_conversation: __('Refresh encrypted conversation'),
+                                label_start_encrypted_conversation: __('Start encrypted conversation'),
+                                label_verify_with_fingerprints: __('Verify with fingerprints'),
+                                label_verify_with_smp: __('Verify with SMP'),
+                                label_whats_this: __("What\'s this?"),
+                                otr_status_class: OTR_CLASS_MAPPING[data.otr_status],
+                                otr_translated_status: OTR_TRANSLATED_MAPPING[data.otr_status],
+                                show_call_button: converse.show_call_button,
+                                show_emoticons: converse.show_emoticons
+                            })
+                        )
+                    );
                 }
                 return this;
             },
@@ -1351,49 +1255,6 @@
                 'click a.subscribe-to-user': 'addContactFromList'
             },
 
-            tab_template: _.template('<li><a class="s current" href="#users">'+__('Contacts')+'</a></li>'),
-            template: _.template(
-                '<form class="set-xmpp-status" action="" method="post">'+
-                    '<span id="xmpp-status-holder">'+
-                        '<select id="select-xmpp-status" style="display:none">'+
-                            '<option value="online">'+__('Online')+'</option>'+
-                            '<option value="dnd">'+__('Busy')+'</option>'+
-                            '<option value="away">'+__('Away')+'</option>'+
-                            '<option value="offline">'+__('Offline')+'</option>'+
-                        '</select>'+
-                    '</span>'+
-                '</form>'
-            ),
-
-            add_contact_dropdown_template: _.template(
-                '<dl class="add-converse-contact dropdown">' +
-                    '<dt id="xmpp-contact-search" class="fancy-dropdown">' +
-                        '<a class="toggle-xmpp-contact-form" href="#"'+
-                            'title="'+__('Click to add new chat contacts')+'">'+
-                        '<span class="icon-plus"></span>'+__('Add a contact')+'</a>' +
-                    '</dt>' +
-                    '<dd class="search-xmpp" style="display:none"><ul></ul></dd>' +
-                '</dl>'
-            ),
-
-            add_contact_form_template: _.template(
-                '<li>'+
-                    '<form class="add-xmpp-contact">' +
-                        '<input type="text" name="identifier" class="username" placeholder="'+__('Contact username')+'"/>' +
-                        '<button type="submit">'+__('Add')+'</button>' +
-                    '</form>'+
-                '<li>'
-            ),
-
-            search_contact_template: _.template(
-                '<li>'+
-                    '<form class="search-xmpp-contact">' +
-                        '<input type="text" name="identifier" class="username" placeholder="'+__('Contact name')+'"/>' +
-                        '<button type="submit">'+__('Search')+'</button>' +
-                    '</form>'+
-                '<li>'
-            ),
-
             initialize: function (cfg) {
                 cfg.$parent.append(this.$el);
                 this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
@@ -1401,17 +1262,30 @@
 
             render: function () {
                 var markup;
-                var widgets = this.template();
-
-                this.$tabs.append(this.tab_template());
+                var widgets = converse.templates.contacts_panel({
+                    label_online: __('Online'),
+                    label_busy: __('Busy'),
+                    label_away: __('Away'),
+                    label_offline: __('Offline')
+                });
+                this.$tabs.append(converse.templates.contacts_tab({label_contacts: __('Contacts')}));
                 if (converse.xhr_user_search) {
-                    markup = this.search_contact_template();
+                    markup = converse.templates.search_contact({
+                        label_contact_name: __('Contact name'),
+                        label_search: __('Search')
+                    });
                 } else {
-                    markup = this.add_contact_form_template();
-                }
+                    markup = converse.templates.add_contact_form({
+                        label_contact_username: __('Contact username'),
+                        label_add: __('Add')
 
+                    });
+                }
                 if (converse.allow_contact_requests) {
-                    widgets += this.add_contact_dropdown_template();
+                    widgets += converse.templates.add_contact_dropdown({
+                        label_click_to_chat: __('Click to add new chat contacts'),
+                        label_add_contact: __('Add a contact')
+                    });
                 }
                 this.$el.html(widgets);
 
@@ -1492,74 +1366,17 @@
                 'click a.open-room': 'createChatRoom',
                 'click a.room-info': 'showRoomInfo'
             },
-            room_template: _.template(
-                '<dd class="available-chatroom">'+
-                '<a class="open-room" data-room-jid="{{jid}}"'+
-                    'title="'+__('Click to open this room')+'" href="#">{{name}}</a>'+
-                '<a class="room-info icon-room-info" data-room-jid="{{jid}}"'+
-                    'title="'+__('Show more information on this room')+'" href="#">&nbsp;</a>'+
-                '</dd>'),
-
-            // FIXME: check markup in mockup
-            room_description_template: _.template(
-                '<div class="room-info">'+
-                '<p class="room-info"><strong>'+__('Description:')+'</strong> {{desc}}</p>' +
-                '<p class="room-info"><strong>'+__('Occupants:')+'</strong> {{occ}}</p>' +
-                '<p class="room-info"><strong>'+__('Features:')+'</strong> <ul>'+
-                '{[ if (passwordprotected) { ]}' +
-                    '<li class="room-info locked">'+__('Requires authentication')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (hidden) { ]}' +
-                    '<li class="room-info">'+__('Hidden')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (membersonly) { ]}' +
-                    '<li class="room-info">'+__('Requires an invitation')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (moderated) { ]}' +
-                    '<li class="room-info">'+__('Moderated')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (nonanonymous) { ]}' +
-                    '<li class="room-info">'+__('Non-anonymous')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (open) { ]}' +
-                    '<li class="room-info">'+__('Open room')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (persistent) { ]}' +
-                    '<li class="room-info">'+__('Permanent room')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (publicroom) { ]}' +
-                    '<li class="room-info">'+__('Public')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (semianonymous) { ]}' +
-                    '<li class="room-info">'+__('Semi-anonymous')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (temporary) { ]}' +
-                    '<li class="room-info">'+__('Temporary room')+'</li>' +
-                '{[ } ]}' +
-                '{[ if (unmoderated) { ]}' +
-                    '<li class="room-info">'+__('Unmoderated')+'</li>' +
-                '{[ } ]}' +
-                '</p>' +
-                '</div>'
-            ),
-
-            tab_template: _.template('<li><a class="s" href="#chatrooms">'+__('Rooms')+'</a></li>'),
-
-            template: _.template(
-                '<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="{{ server_input_type }}" 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')+'"/>'+
-                '</form>'+
-                '<dl id="available-chatrooms"></dl>'),
 
             initialize: function (cfg) {
                 cfg.$parent.append(
                     this.$el.html(
-                        this.template({
-                            server_input_type: converse.hide_muc_server && 'hidden' || 'text'
+                        converse.templates.room_panel({
+                            'server_input_type': converse.hide_muc_server && 'hidden' || 'text',
+                            'label_room_name': __('Room name'),
+                            'label_nickname': __('Nickname'),
+                            'label_server': __('Server'),
+                            'label_join': __('Join'),
+                            'label_show_rooms': __('Show rooms')
                         })
                     ).hide());
                 this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
@@ -1579,7 +1396,7 @@
             },
 
             render: function () {
-                this.$tabs.append(this.tab_template());
+                this.$tabs.append(converse.templates.chatrooms_tab({label_rooms: __('Rooms')}));
                 return this;
             },
 
@@ -1606,10 +1423,14 @@
                             for (i=0; i<this.rooms.length; i++) {
                                 name = Strophe.unescapeNode($(this.rooms[i]).attr('name')||$(this.rooms[i]).attr('jid'));
                                 jid = $(this.rooms[i]).attr('jid');
-                                fragment.appendChild($(this.room_template({
-                                    'name':name,
-                                    'jid':jid
-                                    }))[0]);
+                                fragment.appendChild($(
+                                    converse.templates.room_item({
+                                        'name':name,
+                                        'jid':jid,
+                                        'open_title': __('Click to open this room'),
+                                        'info_title': __('Show more information on this room')
+                                        })
+                                    )[0]);
                             }
                             $available_chatrooms.append(fragment);
                             $('input#show-rooms').show().siblings('span.spinner').remove();
@@ -1655,7 +1476,7 @@
                             var $stanza = $(stanza);
                             // All MUC features found here: http://xmpp.org/registrar/disco-features.html
                             $dd.find('span.spinner').replaceWith(
-                                this.room_description_template({
+                                converse.templates.room_description({
                                     'desc': $stanza.find('field[var="muc#roominfo_description"] value').text(),
                                     'occ': $stanza.find('field[var="muc#roominfo_occupants"] value').text(),
                                     'hidden': $stanza.find('feature[var="muc_hidden"]').length,
@@ -1668,7 +1489,21 @@
                                     'publicroom': $stanza.find('feature[var="muc_public"]').length,
                                     'semianonymous': $stanza.find('feature[var="muc_semianonymous"]').length,
                                     'temporary': $stanza.find('feature[var="muc_temporary"]').length,
-                                    'unmoderated': $stanza.find('feature[var="muc_unmoderated"]').length
+                                    'unmoderated': jstanza.find('feature[var="muc_unmoderated"]').length,
+                                    'label_desc': __('Description:'),
+                                    'label_occ': __('Occupants:'),
+                                    'label_features': __('Features:'),
+                                    'label_requires_auth': __('Requires authentication'),
+                                    'label_hidden': __('Hidden'),
+                                    'label_requires_invite': __('Requires an invitation'),
+                                    'label_moderated': __('Moderated'),
+                                    'label_non_anon': __('Non-anonymous'),
+                                    'label_open_room': __('Open room'),
+                                    'label_permanent_room': __('Permanent room'),
+                                    'label_public': __('Public'),
+                                    'label_semi_anon':  _('Semi-anonymous'),
+                                    'label_temp_room':  _('Temporary room'),
+                                    'label_unmoderated': __('Unmoderated')
                                 }));
                         }, this));
                 }
@@ -1770,14 +1605,6 @@
                 }
             },
 
-            template: _.template(
-                '<div class="chat-head controlbox-head">'+
-                    '<ul id="controlbox-tabs"></ul>'+
-                    '<a class="close-chatbox-button icon-close"></a>'+
-                '</div>'+
-                '<div class="controlbox-panes"></div>'
-            ),
-
             switchTab: function (ev) {
                 ev.preventDefault();
                 var $tab = $(ev.target),
@@ -1801,11 +1628,11 @@
             render: function () {
                 if ((!converse.prebind) && (!converse.connection)) {
                     // Add login panel if the user still has to authenticate
-                    this.$el.html(this.template(this.model.toJSON()));
+                    this.$el.html(converse.templates.controlbox(this.model.toJSON()));
                     this.loginpanel = new converse.LoginPanel({'$parent': this.$el.find('.controlbox-panes'), 'model': this});
                     this.loginpanel.render();
                 } else if (!this.contactspanel) {
-                    this.$el.html(this.template(this.model.toJSON()));
+                    this.$el.html(converse.templates.controlbox(this.model.toJSON()));
                     this.contactspanel = new converse.ContactsPanel({'$parent': this.$el.find('.controlbox-panes')});
                     this.contactspanel.render();
                     converse.xmppstatusview = new converse.XMPPStatusView({'model': converse.xmppstatus});
@@ -1830,7 +1657,6 @@
                 'click .toggle-smiley ul li': 'insertEmoticon',
                 'keypress textarea.chat-textarea': 'keyPressed'
             },
-            info_template: _.template('<div class="chat-info">{{message}}</div>'),
             is_chatroom: true,
 
             sendChatRoomMessage: function (body) {
@@ -1876,42 +1702,20 @@
                 }
             },
 
-            template: _.template(
-                '<div class="chat-head chat-head-chatroom">' +
-                    '<a class="close-chatbox-button icon-close"></a>' +
-                    '<a class="configure-chatroom-button icon-wrench" style="display:none"></a>' +
-                    '<div class="chat-title"> {{ name }} </div>' +
-                    '<p class="chatroom-topic"><p/>' +
-                '</div>' +
-                '<div class="chat-body">' +
-                '<span class="spinner centered"/>' +
-                '</div>'),
-
-            chatarea_template: _.template(
-                '<div class="chat-area">' +
-                    '<div class="chat-content"></div>' +
-                    '<form class="sendXMPPMessage" action="" method="post">' +
-                        '{[ if ('+converse.show_toolbar+') { ]}' +
-                            '<ul class="chat-toolbar no-text-select"></ul>'+
-                        '{[ } ]}' +
-                        '<textarea type="text" class="chat-textarea" ' +
-                            'placeholder="'+__('Message')+'"/>' +
-                    '</form>' +
-                '</div>' +
-                '<div class="participants">' +
-                    '<ul class="participant-list"></ul>' +
-                '</div>'
-            ),
-
             render: function () {
                 this.$el.attr('id', this.model.get('box_id'))
-                        .html(this.template(this.model.toJSON()));
+                        .html(converse.templates.chatroom(this.model.toJSON()));
                 return this;
             },
 
             renderChatArea: function () {
                 if (!this.$el.find('.chat-area').length) {
-                    this.$el.find('.chat-body').empty().append(this.chatarea_template());
+                    this.$el.find('.chat-body').empty().append(
+                        converse.templates.chatarea({
+                            'show_toolbar': converse.show_toolbar,
+                            'label_message': __('Message')
+                        })
+                    );
                     this.renderToolbar();
                 }
                 return this;
@@ -1954,11 +1758,6 @@
                 this.model.set('connected', false);
             },
 
-            form_input_template: _.template('<label>{{label}}<input name="{{name}}" type="{{type}}" value="{{value}}"></label>'),
-            select_option_template: _.template('<option value="{{value}}">{{label}}</option>'),
-            form_select_template: _.template('<label>{{label}}<select name="{{name}}">{{options}}</select></label>'),
-            form_checkbox_template: _.template('<label>{{label}}<input name="{{name}}" type="{{type}}" {{checked}}"></label>'),
-
             renderConfigurationForm: function (stanza) {
                 var $form= this.$el.find('form.chatroom-form'),
                     $stanza = $(stanza),
@@ -1984,7 +1783,7 @@
                         options = [];
                         $options = $field.find('option');
                         for (j=0; j<$options.length; j++) {
-                            options.push(this.select_option_template({
+                            options.push(converse.templates.select_option({
                                 value: $($options[j]).find('value').text(),
                                 label: $($options[j]).attr('label')
                             }));
@@ -1995,14 +1794,14 @@
                             options: options.join('')
                         }));
                     } else if ($field.attr('type') == 'boolean') {
-                        $form.append(this.form_checkbox_template({
+                        $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(this.form_input_template({
+                        $form.append(converse.templates.form_input({
                             name: $field.attr('var'),
                             type: input_types[$field.attr('type')],
                             label: $field.attr('label') || '',
@@ -2016,8 +1815,6 @@
                 $form.find('input[type=button]').on('click', $.proxy(this.cancelConfiguration, this));
             },
 
-            field_template: _.template('<field var="{{name}}"><value>{{value}}</value></field>'),
-
             saveConfiguration: function (ev) {
                 ev.preventDefault();
                 var that = this;
@@ -2031,7 +1828,7 @@
                     } else {
                         value = $input.val();
                     }
-                    var cnode = $(that.field_template({
+                    var cnode = $(converse.templates.field({
                         name: $input.attr('name'),
                         value: value
                     }))[0];
@@ -2193,10 +1990,10 @@
                 }
                 this.renderChatArea();
                 for (i=0; i<info_msgs.length; i++) {
-                    $chat_content.append(this.info_template({message: info_msgs[i]}));
+                    $chat_content.append(converse.templates.info({message: info_msgs[i]}));
                 }
                 for (i=0; i<action_msgs.length; i++) {
-                    $chat_content.append(this.info_template({message: action_msgs[i]}));
+                    $chat_content.append(converse.templates.info({message: action_msgs[i]}));
                 }
                 return this.scrollDown();
             },
@@ -2294,7 +2091,7 @@
                 message_date.setUTCHours(0,0,0,0);
                 isodate = converse.toISOString(message_date);
                 if (_.indexOf(dates, isodate) == -1) {
-                    $chat_content.append(this.new_day_template({
+                    $chat_content.append(converse.templates.new_day({
                         isodate: isodate,
                         datestring: message_date.toString().substring(0,15)
                     }));
@@ -2305,7 +2102,7 @@
                     // # For translators: the %1$s and %2$s parts will get replaced by the user and topic text respectively
                     // # Example: Topic set by JC Brand to: Hello World!
                     $chat_content.append(
-                        this.info_template({
+                        converse.templates.info({
                             'message': __('Topic set by %1$s to: %2$s', sender, subject)
                         }));
                 }
@@ -2324,20 +2121,6 @@
                 return true;
             },
 
-            occupant_template: _.template(
-                '<li class="{{role}}" '+
-                    '{[ if (role === "moderator") { ]}' +
-                        'title="'+__('This user is a moderator')+'"' +
-                    '{[ } ]}'+
-                    '{[ if (role === "participant") { ]}' +
-                        'title="'+__('This user can send messages in this room')+'"' +
-                    '{[ } ]}'+
-                    '{[ if (role === "visitor") { ]}' +
-                        'title="'+__('This user can NOT send messages in this room')+'"' +
-                    '{[ } ]}'+
-                '>{{nick}}</li>'
-            ),
-
             onChatRoomRoster: function (roster, room) {
                 this.renderChatArea();
                 var controlboxview = converse.chatboxesview.views.controlbox,
@@ -2347,9 +2130,12 @@
                 this.$el.find('.participant-list').empty();
                 for (i=0; i<roster_size; i++) {
                     participants.push(
-                        this.occupant_template({
-                            role: roster[keys[i]].role,
-                            nick: Strophe.unescapeNode(keys[i])
+                        converse.templates.occupant({
+                            'role': roster[keys[i]].role,
+                            'nick': Strophe.unescapeNode(keys[i]),
+                            'desc_moderator': __('This user is a moderator'),
+                            'desc_participant': __('This user can send messages in this room'),
+                            'desc_visitor': __('This user can NOT send messages in this room')
                         }));
                 }
                 $participant_list.append(participants.join(""));
@@ -2558,23 +2344,6 @@
                 this.model.destroy();
             },
 
-            template: _.template(
-                '<a class="open-chat" title="'+__('Click to chat with this contact')+'" href="#">'+
-                    '<span class="icon-{{ chat_status }}" title="{{ status_desc }}"></span>{{ fullname }}'+
-                '</a>' +
-                '<a class="remove-xmpp-contact icon-remove" title="'+__('Click to remove this contact')+'" href="#"></a>'),
-
-            pending_template: _.template(
-                '<span>{{ fullname }}</span>' +
-                '<a class="remove-xmpp-contact icon-remove" title="'+__('Click to remove this contact')+'" href="#"></a>'),
-
-            request_template: _.template('<div>{{ fullname }}</div>' +
-                '<button type="button" class="accept-xmpp-request">' +
-                'Accept</button>' +
-                '<button type="button" class="decline-xmpp-request">' +
-                'Decline</button>' +
-                ''),
-
             render: function () {
                 var item = this.model,
                     ask = item.get('ask'),
@@ -2598,15 +2367,28 @@
 
                 if (ask === 'subscribe') {
                     this.$el.addClass('pending-xmpp-contact');
-                    this.$el.html(this.pending_template(item.toJSON()));
+                    this.$el.html(converse.templates.pending_contact(
+                        _.extend(item.toJSON(), {
+                            'desc_remove': __('Click to remove this contact')
+                        })
+                    ));
                 } else if (requesting === true) {
                     this.$el.addClass('requesting-xmpp-contact');
-                    this.$el.html(this.request_template(item.toJSON()));
+                    this.$el.html(converse.templates.requesting_contact(
+                        _.extend(item.toJSON(), {
+                            'label_accept': __('Accept'),
+                            'label_decline': __('Decline')
+                        })
+                    ));
                     converse.controlboxtoggle.showControlBox();
                 } else if (subscription === 'both' || subscription === 'to') {
                     this.$el.addClass('current-xmpp-contact');
-                    this.$el.html(this.template(
-                        _.extend(item.toJSON(), {'status_desc': STATUSES[item.get('chat_status')||'offline']})
+                    this.$el.html(converse.templates.roster_item(
+                        _.extend(item.toJSON(), {
+                            'desc_status': STATUSES[item.get('chat_status')||'offline'],
+                            'desc_chat': __('Click to chat with this contact'),
+                            'desc_remove': __('Click to remove this contact')
+                        })
                     ));
                 }
                 return this;
@@ -2915,15 +2697,6 @@
             id: 'converse-roster',
             rosteritemviews: {},
 
-            requesting_contacts_template: _.template(
-                '<dt id="xmpp-contact-requests">'+__('Contact requests')+'</dt>'),
-
-            contacts_template: _.template(
-                '<dt id="xmpp-contacts">'+__('My contacts')+'</dt>'),
-
-            pending_contacts_template: _.template(
-                '<dt id="pending-xmpp-contacts">'+__('Pending contacts')+'</dt>'),
-
             initialize: function () {
                 this.model.on("add", function (item) {
                     this.addRosterItemView(item).render(item);
@@ -2944,9 +2717,18 @@
                 this.model.on("remove", function (item) { this.removeRosterItemView(item); }, this);
                 this.model.on("destroy", function (item) { this.removeRosterItemView(item); }, this);
 
-                var roster_markup = this.contacts_template();
+                var roster_markup = converse.templates.contacts({
+                    'label_contacts': __('My contacts')
+                });
                 if (converse.allow_contact_requests) {
-                    roster_markup = this.requesting_contacts_template() + roster_markup + this.pending_contacts_template();
+                    roster_markup = 
+                        converse.templates.requesting_contacts({
+                            'label_contact_requests': __('Contact requests')
+                        }) + 
+                        roster_markup + 
+                        converse.templates.pending_contacts({
+                            'label_pending_contacts': __('Pending contacts')
+                        });
                 }
                 this.$el.hide().html(roster_markup);
 
@@ -3161,26 +2943,14 @@
                 $(ev.target).parent().parent().siblings('dd').find('ul').toggle('fast');
             },
 
-            change_status_message_template: _.template(
-                '<form id="set-custom-xmpp-status">' +
-                    '<input type="text" class="custom-xmpp-status" {{ status_message }}"'+
-                        'placeholder="'+__('Custom status')+'"/>' +
-                    '<button type="submit">'+__('Save')+'</button>' +
-                '</form>'),
-
-            status_template: _.template(
-                '<div class="xmpp-status">' +
-                    '<a class="choose-xmpp-status {{ chat_status }}" data-value="{{status_message}}" href="#" title="'+__('Click to change your chat status')+'">' +
-                        '<span class="icon-{{ chat_status }}"></span>'+
-                        '{{ status_message }}' +
-                    '</a>' +
-                    '<a class="change-xmpp-status-message icon-pencil" href="#" title="'+__('Click here to write a custom status message')+'"></a>' +
-                '</div>'),
-
             renderStatusChangeForm: function (ev) {
                 ev.preventDefault();
                 var status_message = this.model.get('status') || 'offline';
-                var input = this.change_status_message_template({'status_message': status_message});
+                var input = converse.templates.change_status_message({
+                    'status_message': status_message,
+                    'label_custom_status': __('Custom status'),
+                    'label_save': __('Save')
+                });
                 this.$el.find('.xmpp-status').replaceWith(input);
                 this.$el.find('.custom-xmpp-status').focus().focus();
             },
@@ -3225,26 +2995,14 @@
                 // # Example, I am online
                 var status_message = model.get('status_message') || __("I am %1$s", this.getPrettyStatus(stat));
                 this.$el.find('#fancy-xmpp-status-select').html(
-                    this.status_template({
+                    converse.templates.chat_status({
                         'chat_status': stat,
-                        'status_message': status_message
+                        'status_message': status_message,
+                        'desc_custom_status': __('Click here to write a custom status message'),
+                        'desc_change_status': __('Click to change your chat status')
                     }));
             },
 
-            choose_template: _.template(
-                '<dl id="target" class="dropdown">' +
-                    '<dt id="fancy-xmpp-status-select" class="fancy-dropdown"></dt>' +
-                    '<dd><ul class="xmpp-status-menu"></ul></dd>' +
-                '</dl>'),
-
-            option_template: _.template(
-                '<li>' +
-                    '<a href="#" class="{{ value }}" data-value="{{ value }}">'+
-                        '<span class="icon-{{ value }}"></span>'+
-                        '{{ text }}'+
-                    '</a>' +
-                '</li>'),
-
             initialize: function () {
                 this.model.on("change", this.updateStatusUI, this);
             },
@@ -3257,17 +3015,20 @@
                     $options_target,
                     options_list = [],
                     that = this;
-                this.$el.html(this.choose_template());
+                this.$el.html(converse.templates.choose_status());
                 this.$el.find('#fancy-xmpp-status-select')
-                        .html(this.status_template({
+                        .html(converse.templates.chat_status({
                             'status_message': this.model.get('status_message') || __("I am %1$s", this.getPrettyStatus(chat_status)),
-                            'chat_status': chat_status
+                            'chat_status': chat_status,
+                            'desc_custom_status': __('Click here to write a custom status message'),
+                            'desc_change_status': __('Click to change your chat status')
                             }));
                 // iterate through all the <option> elements and add option values
                 options.each(function(){
-                    options_list.push(that.option_template({'value': $(this).val(),
-                                                            'text': this.text
-                                                            }));
+                    options_list.push(converse.templates.status_option({
+                        'value': $(this).val(),
+                        'text': this.text
+                    }));
                 });
                 $options_target = this.$el.find("#target dd ul").hide();
                 $options_target.append(options_list.join(''));
@@ -3330,22 +3091,6 @@
             events: {
                 'submit form#converse-login': 'authenticate'
             },
-            tab_template: _.template(
-                '<li><a class="current" href="#login">'+__('Sign in')+'</a></li>'
-            ),
-            template: _.template(
-                '<form id="converse-login">' +
-                '<label>'+__('XMPP/Jabber Username:')+'</label>' +
-                '<input type="username" name="jid">' +
-                '<label>'+__('Password:')+'</label>' +
-                '<input type="password" name="password">' +
-                '<input class="login-submit" type="submit" value="'+__('Log In')+'">' +
-                '</form">'
-            ),
-            bosh_url_input: _.template(
-                '<label>'+__('BOSH Service URL:')+'</label>' +
-                '<input type="text" id="bosh_service_url">'
-            ),
 
             connect: function ($form, jid, password) {
                 if ($form) {
@@ -3364,14 +3109,20 @@
             },
 
             initialize: function (cfg) {
-                cfg.$parent.html(this.$el.html(this.template()));
+                cfg.$parent.html(this.$el.html(
+                    converse.templates.login_panel({
+                        'label_username': __('XMPP/Jabber Username:'),
+                        'label_password': __('Password:'),
+                        'label_login': __('Log In')
+                    })
+                ));
                 this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
                 this.model.on('connection-fail', function () { this.showConnectButton(); }, this);
                 this.model.on('auth-fail', function () { this.showConnectButton(); }, this);
             },
 
             render: function () {
-                this.$tabs.append(this.tab_template());
+                this.$tabs.append(converse.templates.login_tab({label_sign_in: __('Sign in')}));
                 this.$el.find('input#jid').focus();
                 return this;
             },
@@ -3423,17 +3174,16 @@
                 'href': "#"
             },
 
-            template: _.template(
-                '<strong class="conn-feedback">Toggle chat</strong>'+
-                '<strong style="display: none" id="online-count">(0)</strong>'
-            ),
-
             initialize: function () {
                 this.render();
             },
 
             render: function () {
-                $('#conversejs').append(this.$el.html(this.template()));
+                $('#conversejs').append(this.$el.html(
+                    converse.templates.controlbox_toggle({
+                        'label_toggle': __('Toggle chat')
+                    })
+                ));
                 return this;
             },
 

+ 19 - 5
main.js

@@ -1,4 +1,4 @@
-require.config({
+config = {
     paths: {
         "jquery": "components/jquery/jquery",
         "locales": "locale/locales",
@@ -6,6 +6,9 @@ require.config({
         "underscore": "components/underscore/underscore",
         "backbone": "components/backbone/backbone",
         "backbone.localStorage": "components/backbone.localStorage/backbone.localStorage",
+        "text": 'components/requirejs-text/text',
+        "tpl": 'components/requirejs-tpl-jfparadis/tpl',
+        "converse-templates": "src/templates",
         "strophe": "components/strophe/strophe",
         "strophe.muc": "components/strophe.muc/index",
         "strophe.roster": "components/strophe.roster/index",
@@ -30,6 +33,14 @@ require.config({
         "converse-dependencies": "src/deps-full"
     },
 
+    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: {
         'backbone': {
@@ -51,8 +62,11 @@ require.config({
         'strophe.vcard':    { deps: ['strophe'] },
         'strophe.disco':    { deps: ['strophe'] }
     }
-});
+};
 
-require(["jquery", "converse"], function(require, $, converse) {
-    window.converse = converse;
-});
+if (typeof(require) !== 'undefined') {
+    require.config(config);
+    require(["jquery", "converse"], function(require, $, converse) {
+        window.converse = converse;
+    });
+}

+ 4 - 0
spec/chatbox.js

@@ -19,6 +19,10 @@
                 utils.openContactsPanel();
             });
 
+            afterEach(function () {
+                utils.closeAllChatBoxes();
+            });
+
             it("is created when you click on a roster item", $.proxy(function () {
                 var i, $el, click, jid, view;
                 // openControlBox was called earlier, so the controlbox is

+ 4 - 0
spec/chatroom.js

@@ -14,6 +14,10 @@
                 utils.createNewChatRoom('lounge', 'dummy');
             });
 
+            afterEach(function () {
+                utils.closeAllChatBoxes();
+            });
+
             it("shows users currently present in the room", $.proxy(function () {
                 var chatroomview = this.chatboxesview.views['lounge@muc.localhost'],
                     $participant_list;

+ 2 - 2
spec/controlbox.js

@@ -164,7 +164,7 @@
                     expect(this.rosterview.render).toHaveBeenCalled();
                     expect(converse.emit).toHaveBeenCalledWith('onRosterViewUpdated');
                     // Check that they are sorted alphabetically
-                    t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').text();
+                    t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').find('span').text();
                     expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
                 }
             }, converse));
@@ -351,7 +351,7 @@
                     });
                     expect(this.rosterview.render).toHaveBeenCalled();
                     // Check that they are sorted alphabetically
-                    t = this.rosterview.$el.find('dt#xmpp-contact-requests').siblings('dd.requesting-xmpp-contact').text().replace(/AcceptDecline/g, '');
+                    t = this.rosterview.$el.find('dt#xmpp-contact-requests').siblings('dd.requesting-xmpp-contact').children('div').text().replace(/AcceptDecline/g, '');
                     expect(t).toEqual(mock.req_names.slice(0,i+1).sort().join(''));
                     // When a requesting contact is added, the controlbox must
                     // be opened.

+ 77 - 0
src/templates.js

@@ -0,0 +1,77 @@
+define("converse-templates", [
+    "tpl!src/templates/action",
+    "tpl!src/templates/message",
+    "tpl!src/templates/new_day",
+    "tpl!src/templates/info",
+    "tpl!src/templates/controlbox",
+    "tpl!src/templates/chatbox",
+    "tpl!src/templates/toolbar",
+    "tpl!src/templates/contacts_tab",
+    "tpl!src/templates/contacts_panel",
+    "tpl!src/templates/chatrooms_tab",
+    "tpl!src/templates/login_tab",
+    "tpl!src/templates/add_contact_dropdown",
+    "tpl!src/templates/add_contact_form",
+    "tpl!src/templates/room_item",
+    "tpl!src/templates/room_description",
+    "tpl!src/templates/room_panel",
+    "tpl!src/templates/chatroom",
+    "tpl!src/templates/chatarea",
+    "tpl!src/templates/form_input",
+    "tpl!src/templates/select_option",
+    "tpl!src/templates/form_select",
+    "tpl!src/templates/form_checkbox",
+    "tpl!src/templates/field",
+    "tpl!src/templates/occupant",
+    "tpl!src/templates/roster_item",
+    "tpl!src/templates/pending_contact",
+    "tpl!src/templates/requesting_contact",
+    "tpl!src/templates/requesting_contacts",
+    "tpl!src/templates/pending_contacts",
+    "tpl!src/templates/contacts",
+    "tpl!src/templates/chat_status",
+    "tpl!src/templates/change_status_message",
+    "tpl!src/templates/choose_status",
+    "tpl!src/templates/status_option",
+    "tpl!src/templates/login_panel",
+    "tpl!src/templates/controlbox_toggle"
+], function () {
+    return {
+        action: arguments[0],
+        message: arguments[1],
+        new_day: arguments[2],
+        info: arguments[3],
+        controlbox: arguments[4],
+        chatbox: arguments[5],
+        toolbar: arguments[6],
+        contacts_tab: arguments[7],
+        contacts_panel: arguments[8],
+        chatrooms_tab: arguments[9],
+        login_tab: arguments[10],
+        add_contact_dropdown: arguments[11],
+        add_contact_form: arguments[12],
+        room_item: arguments[13],
+        room_description: arguments[14],
+        room_panel: arguments[15],
+        chatroom: arguments[16],
+        chatarea: arguments[17],
+        form_input: arguments[18],
+        select_option: arguments[19],
+        form_select: arguments[20],
+        form_checkbox: arguments[21],
+        field: arguments[22],
+        occupant: arguments[23],
+        roster_item: arguments[24],
+        pending_contact: arguments[25],
+        requesting_contact: arguments[26],
+        requesting_contacts: arguments[27],
+        pending_contacts: arguments[28],
+        contacts: arguments[29],
+        chat_status: arguments[30],
+        change_status_message: arguments[31],
+        choose_status: arguments[32],
+        status_option: arguments[33],
+        login_panel: arguments[34],
+        controlbox_toggle: arguments[35]
+    };
+});

+ 4 - 0
src/templates/action.html

@@ -0,0 +1,4 @@
+<div class="chat-message {{extra_classes}}">
+    <span class="chat-message-{{sender}}">{{time}} **{{username}} </span>
+    <span class="chat-message-content">{{message}}</span>
+</div>

+ 8 - 0
src/templates/add_contact_dropdown.html

@@ -0,0 +1,8 @@
+<dl class="add-converse-contact dropdown">
+    <dt id="xmpp-contact-search" class="fancy-dropdown">
+        <a class="toggle-xmpp-contact-form" href="#"
+            title="{{label_click_to_chat}}">
+        <span class="icon-plus"></span>{{label_add_contact}}</a>
+    </dt>
+    <dd class="search-xmpp" style="display:none"><ul></ul></dd>
+</dl>

+ 9 - 0
src/templates/add_contact_form.html

@@ -0,0 +1,9 @@
+<li>
+    <form class="add-xmpp-contact">
+        <input type="text"
+            name="identifier"
+            class="username"
+            placeholder="{{label_contact_username}}"/>
+        <button type="submit">{{label_add}}</button>
+    </form>
+<li>

+ 5 - 0
src/templates/change_status_message.html

@@ -0,0 +1,5 @@
+<form id="set-custom-xmpp-status">
+    <input type="text" class="custom-xmpp-status" {{status_message}}
+        placeholder="{{label_custom_status}}"/>
+    <button type="submit">{{label_save}}</button>
+</form>

+ 11 - 0
src/templates/chat_status.html

@@ -0,0 +1,11 @@
+<div class="xmpp-status">
+    <a class="choose-xmpp-status {{chat_status}}"
+       data-value="{{status_message}}"
+       href="#" title="{{desc_change_status}}">
+
+        <span class="icon-{{chat_status}}"></span>{{status_message}}
+    </a>
+    <a class="change-xmpp-status-message icon-pencil"
+        href="#"
+        title="{{desc_custom_status}}"></a>
+</div>

+ 13 - 0
src/templates/chatarea.html

@@ -0,0 +1,13 @@
+<div class="chat-area">
+    <div class="chat-content"></div>
+    <form class="sendXMPPMessage" action="" method="post">
+        {[ if (show_toolbar) { ]}
+            <ul class="chat-toolbar no-text-select"></ul>
+        {[ } ]}
+        <textarea type="text" class="chat-textarea" 
+            placeholder="{{label_message}}"/>
+    </form>
+</div>
+<div class="participants">
+    <ul class="participant-list"></ul>
+</div>

+ 17 - 0
src/templates/chatbox.html

@@ -0,0 +1,17 @@
+<div class="chat-head chat-head-chatbox">
+    <a class="close-chatbox-button icon-close"></a>
+    <a href="{{url}}" target="_blank" class="user">
+        <div class="chat-title"> {{ fullname }} </div>
+    </a>
+    <p class="user-custom-message"><p/>
+</div>
+<div class="chat-content"></div>
+<form class="sendXMPPMessage" action="" method="post">
+    {[ if (show_toolbar) { ]}
+        <ul class="chat-toolbar no-text-select"></ul>
+    {[ } ]}
+<textarea
+    type="text"
+    class="chat-textarea"
+    placeholder="{{label_personal_message}}"/>
+</form>

+ 9 - 0
src/templates/chatroom.html

@@ -0,0 +1,9 @@
+<div class="chat-head chat-head-chatroom">
+    <a class="close-chatbox-button icon-close"></a>
+    <a class="configure-chatroom-button icon-wrench" style="display:none"></a>
+    <div class="chat-title"> {{ name }} </div>
+    <p class="chatroom-topic"><p/>
+</div>
+<div class="chat-body">
+<span class="spinner centered"/>
+</div>

+ 1 - 0
src/templates/chatrooms_tab.html

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

+ 4 - 0
src/templates/choose_status.html

@@ -0,0 +1,4 @@
+<dl id="target" class="dropdown">
+    <dt id="fancy-xmpp-status-select" class="fancy-dropdown"></dt>
+    <dd><ul class="xmpp-status-menu"></ul></dd>
+</dl>

+ 1 - 0
src/templates/contacts.html

@@ -0,0 +1 @@
+<dt id="xmpp-contacts">{{label_contacts}}</dt>

+ 10 - 0
src/templates/contacts_panel.html

@@ -0,0 +1,10 @@
+<form class="set-xmpp-status" action="" method="post">
+    <span id="xmpp-status-holder">
+        <select id="select-xmpp-status" style="display:none">
+            <option value="online">{{label_online}}</option>
+            <option value="dnd">{{label_busy}}</option>
+            <option value="away">{{label_away}}</option>
+            <option value="offline">{{label_offline}}</option>
+        </select>
+    </span>
+</form>

+ 1 - 0
src/templates/contacts_tab.html

@@ -0,0 +1 @@
+<li><a class="s current" href="#users">{{label_contacts}}</a></li>

+ 5 - 0
src/templates/controlbox.html

@@ -0,0 +1,5 @@
+<div class="chat-head oc-chat-head">
+    <ul id="controlbox-tabs"></ul>
+    <a class="close-chatbox-button icon-close"></a>
+</div>
+<div class="controlbox-panes"></div>

+ 2 - 0
src/templates/controlbox_toggle.html

@@ -0,0 +1,2 @@
+<strong class="conn-feedback">{{label_toggle}}</strong>
+<strong style="display: none" id="online-count">(0)</strong>

+ 1 - 0
src/templates/field.html

@@ -0,0 +1 @@
+<field var="{{name}}"><value>{{value}}</value></field>

+ 1 - 0
src/templates/form_checkbox.html

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

+ 1 - 0
src/templates/form_input.html

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

+ 1 - 0
src/templates/form_select.html

@@ -0,0 +1 @@
+<label>{{label}}<select name="{{name}}">{{options}}</select></label>

+ 1 - 0
src/templates/info.html

@@ -0,0 +1 @@
+<div class="chat-info">{{message}}</div>

+ 7 - 0
src/templates/login_panel.html

@@ -0,0 +1,7 @@
+<form id="converse-login">
+    <label>{{label_username}}</label>
+    <input type="username" name="jid">
+    <label>{{label_password}}</label>
+    <input type="password" name="password">
+    <input class="login-submit" type="submit" value="{{label_login}}">
+</form">

+ 1 - 0
src/templates/login_tab.html

@@ -0,0 +1 @@
+<li><a class="current" href="#login">{{label_sign_in}}</a></li>

+ 4 - 0
src/templates/message.html

@@ -0,0 +1,4 @@
+<div class="chat-message {{extra_classes}}">
+    <span class="chat-message-{{sender}}">{{time}} {{username}}:&nbsp;</span>
+    <span class="chat-message-content">{{message}}</span>
+</div>

+ 1 - 0
src/templates/new_day.html

@@ -0,0 +1 @@
+<time class="chat-date" datetime="{{isodate}}">{{datestring}}</time>

+ 11 - 0
src/templates/occupant.html

@@ -0,0 +1,11 @@
+<li class="{{role}}"
+    {[ if (role === "moderator") { ]}
+       title="{{desc_moderator}}"
+    {[ } ]}
+    {[ if (role === "participant") { ]}
+       title="{{desc_participant}}"
+    {[ } ]}
+    {[ if (role === "visitor") { ]}
+       title="{{desc_visitor}}"
+    {[ } ]}
+>{{nick}}</li>

+ 1 - 0
src/templates/pending_contact.html

@@ -0,0 +1 @@
+<span>{{fullname}}</span> <a class="remove-xmpp-contact icon-remove" title="{{desc_remove}}" href="#"></a>

+ 1 - 0
src/templates/pending_contacts.html

@@ -0,0 +1 @@
+<dt id="pending-xmpp-contacts">{{label_pending_contacts}}</dt>

+ 3 - 0
src/templates/requesting_contact.html

@@ -0,0 +1,3 @@
+<div>{{fullname}}</div>
+<button type="button" class="accept-xmpp-request">{{label_accept}}</button>
+<button type="button" class="decline-xmpp-request">{{label_decline}}</button>

+ 1 - 0
src/templates/requesting_contacts.html

@@ -0,0 +1 @@
+<dt id="xmpp-contact-requests">{{label_contact_requests}}</dt>

+ 42 - 0
src/templates/room_description.html

@@ -0,0 +1,42 @@
+<!-- FIXME: check markup in mockup -->
+<div class="room-info">
+<p class="room-info"><strong>{{label_desc}}</strong> {{desc}}</p>
+<p class="room-info"><strong>{{label_occ}}</strong> {{occ}}</p>
+<p class="room-info"><strong>{{label_features}}</strong>
+    <ul>
+        {[ if (passwordprotected) { ]}
+        <li class="room-info locked">{{label_requires_auth}}</li>
+        {[ } ]}
+        {[ if (hidden) { ]}
+        <li class="room-info">{{label_hidden}}</li>
+        {[ } ]}
+        {[ if (membersonly) { ]}
+        <li class="room-info">{{label_requires_invite}}</li>
+        {[ } ]}
+        {[ if (moderated) { ]}
+        <li class="room-info">{{label_moderated}}</li>
+        {[ } ]}
+        {[ if (nonanonymous) { ]}
+        <li class="room-info">{{label_non_anon}}</li>
+        {[ } ]}
+        {[ if (open) { ]}
+        <li class="room-info">{{label_open_room}}</li>
+        {[ } ]}
+        {[ if (persistent) { ]}
+        <li class="room-info">{{label_permanent_room}}</li>
+        {[ } ]}
+        {[ if (publicroom) { ]}
+        <li class="room-info">{{label_public}}</li>
+        {[ } ]}
+        {[ if (semianonymous) { ]}
+        <li class="room-info">{{label_semi_anon}}</li>
+        {[ } ]}
+        {[ if (temporary) { ]}
+        <li class="room-info">{{label_temp_room}}</li>
+        {[ } ]}
+        {[ if (unmoderated) { ]}
+        <li class="room-info">{{label_unmoderated}}</li>
+        {[ } ]}
+    </ul>
+</p>
+</div>

+ 6 - 0
src/templates/room_item.html

@@ -0,0 +1,6 @@
+<dd class="available-chatroom">
+<a class="open-room" data-room-jid="{{jid}}"
+   title="{{open_title}}" href="#">{{name}}</a>
+<a class="room-info icon-room-info" data-room-jid="{{jid}}"
+   title="{{info_title}}" href="#">&nbsp;</a>
+</dd>

+ 11 - 0
src/templates/room_panel.html

@@ -0,0 +1,11 @@
+<form class="add-chatroom" action="" method="post">
+    <input type="text" name="chatroom" class="new-chatroom-name"
+        placeholder="{{label_room_name}}"/>
+    <input type="text" name="nick" class="new-chatroom-nick"
+        placeholder="{{label_nickname}}"/>
+    <input type="{{server_input_type}}" name="server" class="new-chatroom-server"
+        placeholder="{{label_server}}"/>
+    <input type="submit" name="join" value="{{label_join}}"/>
+    <input type="button" name="show" id="show-rooms" value="{{label_show_rooms}}"/>
+</form>
+<dl id="available-chatrooms"></dl>

+ 2 - 0
src/templates/roster_item.html

@@ -0,0 +1,2 @@
+<a class="open-chat" title="{{desc_chat}}" href="#"><span class="icon-{{chat_status}}" title="{{desc_status}}"></span>{{fullname}}</a>
+<a class="remove-xmpp-contact icon-remove" title="{{desc_remove}}" href="#"></a>

+ 9 - 0
src/templates/search_contact.html

@@ -0,0 +1,9 @@
+<li>
+    <form class="search-xmpp-contact">
+        <input type="text"
+            name="identifier"
+            class="username"
+            placeholder="{{label_contact_name}}"/>
+        <button type="submit">{{label_search}}</button>
+    </form>
+<li>

+ 1 - 0
src/templates/select_option.html

@@ -0,0 +1 @@
+<option value="{{value}}">{{label}}</option>

+ 6 - 0
src/templates/status_option.html

@@ -0,0 +1,6 @@
+<li>
+    <a href="#" class="{{ value }}" data-value="{{ value }}">
+        <span class="icon-{{ value }}"></span>
+        {{ text }}
+    </a>
+</li>

+ 53 - 0
src/templates/toolbar.html

@@ -0,0 +1,53 @@
+{[ if (show_emoticons)  { ]}
+    <li class="toggle-smiley icon-happy" title="Insert a smilery">
+        <ul>
+            <li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>
+            <li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>
+            <li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>
+            <li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>
+            <li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>
+            <li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>
+            <li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>
+            <li><a class="icon-wondering" href="#" data-emoticon=":\"></a></li>
+            <li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>
+            <li><a class="icon-sad" href="#" data-emoticon=":("></a></li>
+            <li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>
+            <li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>
+            <li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>
+        </ul>
+    </li>
+{[ } ]}
+{[ if (show_call_button)  { ]}
+    <li><a class="toggle-call icon-phone" title="Start a call"></a></li>
+{[ } ]}
+{[ if (allow_otr)  { ]}
+    <li class="toggle-otr {{otr_status_class}}" title="{{otr_tooltip}}">
+        <span class="chat-toolbar-text">{{otr_translated_status}}</span>
+        {[ if (otr_status == UNENCRYPTED) { ]}
+            <span class="icon-unlocked"></span>
+        {[ } ]}
+        {[ if (otr_status == UNVERIFIED) { ]}
+            <span class="icon-lock"></span>
+        {[ } ]}
+        {[ if (otr_status == VERIFIED) { ]}
+            <span class="icon-lock"></span>
+        {[ } ]}
+        {[ if (otr_status == FINISHED) { ]}
+            <span class="icon-unlocked"></span>
+        {[ } ]}
+        <ul>
+            {[ if (otr_status == UNENCRYPTED) { ]}
+               <li><a class="start-otr" href="#">{{label_start_encrypted_conversation}}</a></li>
+            {[ } ]}
+            {[ if (otr_status != UNENCRYPTED) { ]}
+               <li><a class="start-otr" href="#">{{label_start_encrypted_conversation}}</a></li>
+               <li><a class="end-otr" href="#">{{label_end_encrypted_conversation}}</a></li>
+               <li><a class="auth-otr" data-scheme="smp" href="#">{{label_verify_with_smp}}</a></li>
+            {[ } ]}
+            {[ if (otr_status == UNVERIFIED) { ]}
+               <li><a class="auth-otr" data-scheme="fingerprint" href="#">{{label_verify_with_fingerprints}}</a></li>
+            {[ } ]}
+            <li><a href="http://www.cypherpunks.ca/otr/help/3.2.0/levels.php" target="_blank">{{label_whats_this}}</a></li>
+        </ul>
+    </li>
+{[ } ]}

+ 1 - 0
tests.html

@@ -8,6 +8,7 @@
     <link rel="stylesheet" type="text/css" href="components/jasmine/src/html/jasmine.css">
     <link rel="stylesheet" type="text/css" media="screen" href="stylesheets/stylesheet.css">
     <link rel="stylesheet" type="text/css" media="screen" href="converse.css">
+    <script src="main.js"></script>
     <script data-main="tests_main" src="components/requirejs/require.js"></script>
 </head>
 

+ 20 - 73
tests_main.js

@@ -1,77 +1,24 @@
-require.config({
-    paths: {
-        "jquery": "components/jquery/jquery",
-        "locales": "locale/locales",
-        "jquery.tinysort": "components/tinysort/src/jquery.tinysort",
-        "underscore": "components/underscore/underscore",
-        "backbone": "components/backbone/backbone",
-        "backbone.localStorage": "components/backbone.localStorage/backbone.localStorage",
-        "strophe": "components/strophe/strophe",
-        "strophe.muc": "components/strophe.muc/index",
-        "strophe.roster": "components/strophe.roster/index",
-        "strophe.vcard": "components/strophe.vcard/index",
-        "strophe.disco": "components/strophe.disco/index",
-        "salsa20": "components/otr/build/dep/salsa20",
-        "bigint": "src/bigint",
-        "crypto.core": "components/otr/vendor/cryptojs/core",
-        "crypto.enc-base64": "components/otr/vendor/cryptojs/enc-base64",
-        "crypto.md5": "components/crypto-js/src/md5",
-        "crypto.evpkdf": "components/crypto-js/src/evpkdf",
-        "crypto.cipher-core": "components/otr/vendor/cryptojs/cipher-core",
-        "crypto.aes": "components/otr/vendor/cryptojs/aes",
-        "crypto.sha1": "components/otr/vendor/cryptojs/sha1",
-        "crypto.sha256": "components/otr/vendor/cryptojs/sha256",
-        "crypto.hmac": "components/otr/vendor/cryptojs/hmac",
-        "crypto.pad-nopadding": "components/otr/vendor/cryptojs/pad-nopadding",
-        "crypto.mode-ctr": "components/otr/vendor/cryptojs/mode-ctr",
-        "crypto": "src/crypto",
-        "eventemitter": "components/otr/build/dep/eventemitter",
-        "otr": "components/otr/build/otr",
-        "converse-dependencies": "src/deps-full",
-        // Extra test dependencies
-        "mock": "tests/mock",
-        "utils": "tests/utils",
-        "jasmine": "components/jasmine/lib/jasmine-core/jasmine",
-        "jasmine-html": "components/jasmine/lib/jasmine-core/jasmine-html",
-        "jasmine-console-reporter": "node_modules/jasmine-reporters/src/jasmine.console_reporter",
-        "jasmine-junit-reporter": "node_modules/jasmine-reporters/src/jasmine.junit_reporter"
-    },
+// Extra test dependencies
+config.paths.mock = "tests/mock";
+config.paths.utils = "tests/utils";
+config.paths.jasmine = "components/jasmine/lib/jasmine-core/jasmine";
+config.paths["jasmine-html"] = "components/jasmine/lib/jasmine-core/jasmine-html";
+config.paths["jasmine-console-reporter"] = "node_modules/jasmine-reporters/src/jasmine.console_reporter";
+config.paths["jasmine-junit-reporter"] = "node_modules/jasmine-reporters/src/jasmine.junit_reporter";
 
-    // define module dependencies for modules not using define
-    shim: {
-        'backbone': {
-            //These script dependencies should be loaded before loading
-            //backbone.js
-            deps: [
-                'underscore',
-                'jquery'
-                ],
-            //Once loaded, use the global 'Backbone' as the
-            //module value.
-            exports: 'Backbone'
-        },
-        'jquery.tinysort': { deps: ['jquery'] },
-        'strophe': { deps: ['jquery'] },
-        'underscore':   { exports: '_' },
-        'strophe.muc':  { deps: ['strophe', 'jquery'] },
-        'strophe.roster':   { deps: ['strophe'] },
-        'strophe.vcard':    { deps: ['strophe'] },
-        'strophe.disco':    { deps: ['strophe'] },
-        // Extra test dependencies
-        'jasmine-html': {
-            deps: ['jasmine'],
-            exports: 'jasmine'
-        },
-        'jasmine-console-reporter': {
-            deps: ['jasmine-html'],
-            exports: 'jasmine'
-        },
-        'jasmine-junit-reporter': {
-            deps: ['jasmine-html'],
-            exports: 'jasmine'
-        }
-    }
-});
+config.shim['jasmine-html'] = {
+    deps: ['jasmine'],
+    exports: 'jasmine'
+};
+config.shim['jasmine-console-reporter'] = {
+    deps: ['jasmine-html'],
+    exports: 'jasmine'
+};
+config.shim['jasmine-junit-reporter'] = {
+    deps: ['jasmine-html'],
+    exports: 'jasmine'
+};
+require.config(config);
 
 // Polyfill 'bind' which is not available in phantomjs < 2.0
 if (!Function.prototype.bind) {