From 7142dc58c774aa3ce7801aaa8e95b40d0f8c00e8 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 10 May 2013 21:05:58 +0200 Subject: [PATCH 1/9] Don't call disco#info for all chatrooms It's way to inefficient when there are multiple chatrooms. Instead, I added an info icon that can be clicked and which will fetch and display additional info on the chatroom. --- converse.css | 38 ++++++++++++++++++++-- converse.js | 89 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/converse.css b/converse.css index d2538ef91..4d965f860 100644 --- a/converse.css +++ b/converse.css @@ -441,7 +441,7 @@ form.search-xmpp-contact input { text-overflow: ellipsis; white-space: nowrap; display: inline-block; - width: 160px; + width: 170px; } #available-chatrooms dt, @@ -468,6 +468,38 @@ dd.available-chatroom, text-shadow: 0 1px 0 rgba(250, 250, 250, 1); } +p.room-info { + margin: 0; + padding: 0; + font-size: 11px; + font-style: normal; + font-weight: normal; + display: block; + white-space: normal; +} + +a.room-info { + background: url('images/information.png') no-repeat right top; + width: 22px; + float: right; + display: none; +} + +a.open-room { + display: inline-block; + white-space: nowrap; + text-overflow: ellipsis; + overflow-x: hidden; +} + +dd.available-chatroom:hover a.room-info { + display: inline-block; +} + +dd.available-chatroom:hover a.open-room { + width: 75%; +} + #converse-roster dd a.remove-xmpp-contact { background: url('images/delete_icon.png') no-repeat right top; padding: 0 0 1em 0; @@ -477,11 +509,11 @@ dd.available-chatroom, display: none; } -#converse-roster dd:hover *[class*="remove-xmpp-contact"] { +#converse-roster dd:hover a.remove-xmpp-contact { display: inline-block; } -#converse-roster dd:hover *[class*="open-chat"] { +#converse-roster dd:hover a.open-chat { width: 75%; } diff --git a/converse.js b/converse.js index 39459f0d3..f8af727fb 100644 --- a/converse.js +++ b/converse.js @@ -684,14 +684,24 @@ events: { 'submit form.add-chatroom': 'createChatRoom', 'click input#show-rooms': 'showRooms', - 'click a.open-room': 'createChatRoom' + 'click a.open-room': 'createChatRoom', + 'click a.room-info': 'showRoomInfo' }, room_template: _.template( - '
' + - '' + - '{{name}} {{occ}}
'), + '
'+ + '{{name}}'+ + ' '+ + '
'), + + room_description_template: _.template( + '
'+ + '

Description: {{desc}}

' + + '

Occupants: {{occ}}

' + + '{[ if (locked) { ]}' + + '

Requires authentication

' + + '{[ } ]}' + + '
' + ), tab_template: _.template('
  • Rooms
  • '), @@ -720,37 +730,23 @@ converse.connection.muc.listRooms( this.muc_domain, $.proxy(function (iq) { // Success - var name, jid, i, that = this, $available_chatrooms = this.$el.find('#available-chatrooms'); - this.rdict = {}; + var name, jid, i, fragment, + that = this, + $available_chatrooms = this.$el.find('#available-chatrooms'); this.rooms = $(iq).find('query').find('item'); - this.rooms.each(function (i) { that.rdict[$(this).attr('jid')] = this; }); - this.fragment = document.createDocumentFragment(); if (this.rooms.length) { $available_chatrooms.html('
    Rooms on '+this.muc_domain+'
    '); - _.each(this.rooms, $.proxy(function (room, idx) { - converse.connection.disco.info( - $(room).attr('jid'), - null, - $.proxy(function (stanza) { - var name = $(stanza).find('identity').attr('name'); - var desc = $(stanza).find('field[var="muc#roominfo_description"] value').text(); - var occ = $(stanza).find('field[var="muc#roominfo_occupants"] value').text(); - var locked = $(stanza).find('feature[var="muc_passwordprotected"]').length; - var jid = $(stanza).attr('from'); - var classes = locked && 'locked' || ''; - delete this.rdict[jid]; - this.$el.find('#available-chatrooms').append( - this.room_template({'name':name, - 'desc':desc, - 'occ':occ, - 'jid':jid, - 'classes': classes - })); - if (_.keys(this.rdict).length === 0) { - $('input#show-rooms').show().siblings('img.spinner').remove(); - } - }, this)); - }, this)); + fragment = document.createDocumentFragment(); + for (i=0; iNo rooms on '+this.muc_domain+''); $('input#show-rooms').show().siblings('img.spinner').remove(); @@ -780,6 +776,31 @@ this.updateRoomsList(); }, + showRoomInfo: function (ev) { + var target = ev.target, + $dd = $(target).parent('dd'), + $div = $dd.find('div.room-info'); + if ($div.length) { + $div.remove(); + } else { + $dd.append(''); + converse.connection.disco.info( + $(target).attr('data-room-jid'), + null, + $.proxy(function (stanza) { + var desc = $(stanza).find('field[var="muc#roominfo_description"] value').text(); + var occ = $(stanza).find('field[var="muc#roominfo_occupants"] value').text(); + var locked = $(stanza).find('feature[var="muc_passwordprotected"]').length; + $dd.find('img.spinner').replaceWith( + this.room_description_template({ + 'desc':desc, + 'occ':occ, + 'locked':locked + })); + }, this)); + } + }, + createChatRoom: function (ev) { ev.preventDefault(); var name, server, jid, $name, $server, errors; From ee47b031ba8a2da4a254d11c2903360de731f38f Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 10 May 2013 21:15:22 +0200 Subject: [PATCH 2/9] Fixed width for inputs in chrome --- converse.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/converse.css b/converse.css index 4d965f860..2ac332211 100644 --- a/converse.css +++ b/converse.css @@ -573,10 +573,12 @@ form#converse-login { form#converse-login input { display: block; + width: 90%; } form#converse-login .login-submit { margin-top: 1em; + width: auto; } form.set-xmpp-status, From c392a4e598b6a37dd5ae3edb5135d15d3951a8f3 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 11 May 2013 08:50:01 +0200 Subject: [PATCH 3/9] Show all available room features when clicking the info icon next to a room's name --- CHANGES.rst | 23 +++++++++++------- converse.css | 9 ++++--- converse.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 18 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index cde69b9f0..44ea02515 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,22 +4,27 @@ Changelog 0.3 (unreleased) ---------------- -- Add vCard support [jcbrand] -- Remember custom status messages upon reload. [jcbrand] -- Remove jquery-ui dependency. [jcbrand] +- Add vCard support + [jcbrand] +- Remember custom status messages upon reload. + [jcbrand] +- Remove jquery-ui dependency. + [jcbrand] - Use backbone.localStorage to store the contacts roster, open chatboxes and - chat messages. [jcbrand] -- Fixed user status handling, which wasn't 100% according to the - spec. [jcbrand] -- Separate messages according to day in chats. [jcbrand] + chat messages. + [jcbrand] +- Fixed user status handling, which wasn't 100% according to the spec. + [jcbrand] +- Separate messages according to day in chats. + [jcbrand] - Add support for specifying the BOSH bind URL as configuration setting. [jcbrand] - Improve the message counter to only increment when the window is not focused [witekdev] - Make fetching of list of chatrooms on a server a configuration option. [jcbrand] -- Use service discovery to show whether a chatroom is password protected as - well as its number of occupents. [jcbrand] +- Use service discovery to show all available features on a room. + [jcbrand] 0.2 (2013-03-28) diff --git a/converse.css b/converse.css index 2ac332211..f18bda248 100644 --- a/converse.css +++ b/converse.css @@ -468,12 +468,15 @@ dd.available-chatroom, text-shadow: 0 1px 0 rgba(250, 250, 250, 1); } -p.room-info { - margin: 0; - padding: 0; +.room-info { font-size: 11px; font-style: normal; font-weight: normal; +} + +p.room-info { + margin: 0; + padding: 0; display: block; white-space: normal; } diff --git a/converse.js b/converse.js index f8af727fb..06e0fa6d5 100644 --- a/converse.js +++ b/converse.js @@ -697,9 +697,41 @@ '
    '+ '

    Description: {{desc}}

    ' + '

    Occupants: {{occ}}

    ' + - '{[ if (locked) { ]}' + - '

    Requires authentication

    ' + + '

    Features:

      '+ + '{[ if (passwordprotected) { ]}' + + '
    • Requires authentication
    • ' + '{[ } ]}' + + '{[ if (hidden) { ]}' + + '
    • Hidden
    • ' + + '{[ } ]}' + + '{[ if (membersonly) { ]}' + + '
    • Requires an invitation
    • ' + + '{[ } ]}' + + '{[ if (moderated) { ]}' + + '
    • Moderated
    • ' + + '{[ } ]}' + + '{[ if (nonanonymous) { ]}' + + '
    • Non-anonymous
    • ' + + '{[ } ]}' + + '{[ if (open) { ]}' + + '
    • Open room
    • ' + + '{[ } ]}' + + '{[ if (persistent) { ]}' + + '
    • Permanent room
    • ' + + '{[ } ]}' + + '{[ if (publicroom) { ]}' + + '
    • Public
    • ' + + '{[ } ]}' + + '{[ if (semianonymous) { ]}' + + '
    • Semi-anonymous
    • ' + + '{[ } ]}' + + '{[ if (temporary) { ]}' + + '
    • Temporary room
    • ' + + '{[ } ]}' + + '{[ if (unmoderated) { ]}' + + '
    • Unmoderated
    • ' + + '{[ } ]}' + + '

      ' + '
    ' ), @@ -788,14 +820,36 @@ $(target).attr('data-room-jid'), null, $.proxy(function (stanza) { - var desc = $(stanza).find('field[var="muc#roominfo_description"] value').text(); - var occ = $(stanza).find('field[var="muc#roominfo_occupants"] value').text(); - var locked = $(stanza).find('feature[var="muc_passwordprotected"]').length; + var $stanza = $(stanza); + // All MUC features shown here: http://xmpp.org/registrar/disco-features.html + var desc = $stanza.find('field[var="muc#roominfo_description"] value').text(); + var occ = $stanza.find('field[var="muc#roominfo_occupants"] value').text(); + var hidden = $stanza.find('feature[var="muc_hidden"]').length; + var membersonly = $stanza.find('feature[var="muc_membersonly"]').length; + var moderated = $stanza.find('feature[var="muc_moderated"]').length; + var nonanonymous = $stanza.find('feature[var="muc_nonanonymous"]').length; + var open = $stanza.find('feature[var="muc_open"]').length; + var passwordprotected = $stanza.find('feature[var="muc_passwordprotected"]').length; + var persistent = $stanza.find('feature[var="muc_persistent"]').length; + var publicroom = $stanza.find('feature[var="muc_public"]').length; + var semianonymous = $stanza.find('feature[var="muc_semianonymous"]').length; + var temporary = $stanza.find('feature[var="muc_temporary"]').length; + var unmoderated = $stanza.find('feature[var="muc_unmoderated"]').length; $dd.find('img.spinner').replaceWith( this.room_description_template({ 'desc':desc, 'occ':occ, - 'locked':locked + 'hidden':hidden, + 'membersonly':membersonly, + 'moderated':moderated, + 'nonanonymous':nonanonymous, + 'open':open, + 'passwordprotected':passwordprotected, + 'persistent':persistent, + 'publicroom': publicroom, + 'semianonymous':semianonymous, + 'temporary':temporary, + 'unmoderated':unmoderated })); }, this)); } @@ -1958,6 +2012,7 @@ * This collection stores Feature Models, representing features * provided by available XMPP entities (e.g. servers) * See XEP-0030 for more details: http://xmpp.org/extensions/xep-0030.html + * All features are shown here: http://xmpp.org/registrar/disco-features.html */ model: converse.Feature, initialize: function () { From 7c1a9242d5229b6a964355c5f288b5f22f941858 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 11 May 2013 09:59:32 +0200 Subject: [PATCH 4/9] Add logic to parse error messages when trying to enter a room --- converse.js | 32 ++++++++-- spec/MainSpec.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++ tests_main.js | 2 +- 3 files changed, 178 insertions(+), 7 deletions(-) diff --git a/converse.js b/converse.js index 06e0fa6d5..95d394599 100644 --- a/converse.js +++ b/converse.js @@ -1092,9 +1092,32 @@ } } } else { - var error = $presence.find('error'); - if ($(error).attr('type') == 'auth') { - this.$el.find('.chat-content').append('Sorry, this chatroom is restricted'); + var $error = $presence.find('error'), + $chat_content = this.$el.find('.chat-content'); + if ($error.attr('type') == 'auth') { + if ($error.find('not-authorized').length) { + $chat_content.append('This chatroom requires a password'); + } else if ($error.find('registration-required').length) { + $chat_content.append('You are not on the member list of this room'); + } else if ($error.find('forbidden').length) { + $chat_content.append('You have been banned from this room'); + } + } else if ($error.attr('type') == 'modify') { + if ($error.find('jid-malformed').length) { + $chat_content.append('No nickname was specified'); + } + } else if ($error.attr('type') == 'cancel') { + if ($error.find('not-allowed').length) { + $chat_content.append('You are not allowed to create new rooms'); + } else if ($error.find('not-acceptable').length) { + $chat_content.append("Your nickname doesn't conform to the room's policies"); + } else if ($error.find('conflict').length) { + $chat_content.append("Your nickname is already taken"); + } else if ($error.find('item-not-found').length) { + $chat_content.append("This room does not (yet) exist"); + } else if ($error.find('service-unavailable').length) { + $chat_content.append("This room has reached it's maximum number of occupants"); + } } } return true; @@ -1419,7 +1442,6 @@ this.$el.addClass('current-xmpp-contact'); this.$el.html(this.template(item.toJSON())); } - return this; }, @@ -1877,7 +1899,6 @@ converse.connection.send($pres().c('show').t(this.get('status')).up().c('status').t(status_message)); this.save({'status_message': status_message}); } - }); converse.XMPPStatusView = Backbone.View.extend({ @@ -1909,7 +1930,6 @@ '' + ''), - renderStatusChangeForm: function (ev) { ev.preventDefault(); var status_message = this.model.get('status') || 'offline'; diff --git a/spec/MainSpec.js b/spec/MainSpec.js index b6f625208..b6ddd8f53 100644 --- a/spec/MainSpec.js +++ b/spec/MainSpec.js @@ -713,5 +713,156 @@ expect(converse.connection.muc.leave).toHaveBeenCalled(); }, converse)); }, converse)); + + describe("When attempting to enter a chatroom", $.proxy(function () { + beforeEach($.proxy(function () { + var roomspanel = this.chatboxesview.views.controlbox.roomspanel; + var $input = roomspanel.$el.find('input.new-chatroom-name'); + var $server = roomspanel.$el.find('input.new-chatroom-server'); + $input.val('problematic'); + $server.val('muc.localhost'); + roomspanel.$el.find('form').submit(); + }, converse)); + + afterEach($.proxy(function () { + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.closeChat(); + }, converse)); + + it("will show an error message if the room requires a password", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'auth'}) + .c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe('This chatroom requires a password'); + }, converse)); + + it("will show an error message if the room is members-only and the user not included", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'auth'}) + .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe('You are not on the member list of this room'); + }, converse)); + + it("will show an error message if the user has been banned", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'auth'}) + .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe('You have been banned from this room'); + }, converse)); + + it("will show an error message if no nickname was specified for the user", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'modify'}) + .c('jid-malformed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe('No nickname was specified'); + }, converse)); + + it("will show an error message if the user is not allowed to have created the room", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'}) + .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe('You are not allowed to create new rooms'); + }, converse)); + + it("will show an error message if the user's nickname doesn't conform to room policy", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'}) + .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe("Your nickname doesn't conform to the room's policies"); + }, converse)); + + it("will show an error message if the user's nickname is already taken", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'}) + .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe("Your nickname is already taken"); + }, converse)); + + it("will show an error message if the room doesn't yet exist", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'}) + .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe("This room does not (yet) exist"); + }, converse)); + + it("will show an error message if the room has reached it's maximum number of occupants", $.proxy(function () { + var presence = $pres().attrs({ +   from:'coven@chat.shakespeare.lit/thirdwitch', +     id:'n13mt3l', +     to:'hag66@shakespeare.lit/pda', +     type:'error'}) + .c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() + .c('error').attrs({by:'coven@chat.shakespeare.lit', type:'cancel'}) + .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + var view = this.chatboxesview.views['problematic@muc.localhost']; + view.onChatRoomPresence(presence, {'nick': 'dummy'}); + var $chat_content = view.$el.find('.chat-content'); + expect($chat_content.text()).toBe("This room has reached it's maximum number of occupants"); + }, converse)); + }, converse)); }, converse)); })); diff --git a/tests_main.js b/tests_main.js index 7aaf75e5b..992b78785 100644 --- a/tests_main.js +++ b/tests_main.js @@ -2,7 +2,7 @@ require(["jquery", "spec/MainSpec"], function($) { $(function($) { var jasmineEnv = jasmine.getEnv(); - jasmineEnv.updateInterval = 500; + jasmineEnv.updateInterval = 250; var htmlReporter = new jasmine.HtmlReporter(); From 283dbd75f4e22531553f5e8ea61aac678e866a68 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 11 May 2013 12:48:32 +0200 Subject: [PATCH 5/9] Add info on roster callback --- Libraries/strophe.muc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/strophe.muc.js b/Libraries/strophe.muc.js index fd7d3bd29..25d87b4af 100644 --- a/Libraries/strophe.muc.js +++ b/Libraries/strophe.muc.js @@ -57,6 +57,7 @@ * (String) nick - Optional nickname to use in the chat room. * (Function) msg_handler_cb - The function call to handle messages from the specified chat room. * (Function) pres_handler_cb - The function call back to handle presence in the chat room. + * (Function) roster_cb - The function call back to handle roster changes in the chat room. * (String) password - The optional password to use. (password protected rooms only) */ var msg, room_nick, _this = this; From fc06d46baa8ce527aa326d1af1bc5f98c92dc5de Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 11 May 2013 14:19:07 +0200 Subject: [PATCH 6/9] Give visual indication/tooltips about MUC user roles --- converse.css | 4 ++++ converse.js | 26 +++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/converse.css b/converse.css index f18bda248..9fc227a74 100644 --- a/converse.css +++ b/converse.css @@ -92,7 +92,11 @@ img.spinner { display: block; font-size: 12px; padding: 0.5em 0 0 0.5em; + cursor: default; +} +ul.participant-list li.moderator { + color: #FE0007; } .chatroom form.sendXMPPMessage { diff --git a/converse.js b/converse.js index 95d394599..9e5680245 100644 --- a/converse.js +++ b/converse.js @@ -1084,8 +1084,8 @@ $presence = $(presence), from = $presence.attr('from'); if ($presence.attr('type') !== 'error') { - // check for status 110 to see if it's our own presence if ($presence.find("status[code='110']").length) { + // check for status 110 to see if it's our own presence // check if server changed our nick if ($presence.find("status[code='210']").length) { this.model.set({'nick': Strophe.getResourceFromJid(from)}); @@ -1206,16 +1206,32 @@ return true; }, + occupant_template: _.template( + '
  • {{nick}}
  • ' + ), + onChatRoomRoster: function (roster, room) { - // underscore size is needed because roster is an object var controlboxview = converse.chatboxesview.views.controlbox, roster_size = _.size(roster), $participant_list = this.$el.find('.participant-list'), - participants = [], - i; + participants = [], keys = _.keys(roster), i; this.$el.find('.participant-list').empty(); for (i=0; i' + Strophe.unescapeNode(_.keys(roster)[i]) + ''); + participants.push( + this.occupant_template({ + role: roster[keys[i]].role, + nick: Strophe.unescapeNode(keys[i]) + })); } $participant_list.append(participants.join("")); return true; From 6d9fe68a2534f1a220293a09ea9fdabe1eccc12f Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 11 May 2013 14:20:07 +0200 Subject: [PATCH 7/9] Split ChatRooms tests out into their own spec file --- mock.js | 43 +++++++ spec/ChatRoomSpec.js | 224 ++++++++++++++++++++++++++++++++++++ spec/MainSpec.js | 267 +++---------------------------------------- tests_main.js | 33 +++--- 4 files changed, 300 insertions(+), 267 deletions(-) create mode 100644 mock.js create mode 100644 spec/ChatRoomSpec.js diff --git a/mock.js b/mock.js new file mode 100644 index 000000000..38cd8fcf0 --- /dev/null +++ b/mock.js @@ -0,0 +1,43 @@ +(function (root, factory) { + define("mock", + ['converse'], + function() { + return factory(); + }); +}(this, function (converse) { + var mock_connection = { + 'muc': { + 'listRooms': function () {}, + 'join': function () {}, + 'leave': function () {} + }, + 'jid': 'dummy@localhost', + 'addHandler': function (handler, ns, name, type, id, from, options) { + return function () {}; + }, + 'send': function () {}, + 'roster': { + 'add': function () {}, + 'authorize': function () {}, + 'unauthorize': function () {}, + 'get': function () {}, + 'subscribe': function () {}, + 'registerCallback': function () {} + }, + 'vcard': { + 'get': function (callback, jid) { + var name = jid.split('@')[0].replace('.', ' ').split(' '); + var firstname = name[0].charAt(0).toUpperCase()+name[0].slice(1); + var lastname = name[1].charAt(0).toUpperCase()+name[1].slice(1); + var fullname = firstname+' '+lastname; + var vcard = $iq().c('vCard').c('FN').t(fullname); + callback(vcard.tree()); + } + }, + 'disco': { + 'info': function () {}, + 'items': function () {} + } + }; + return mock_connection; +})); diff --git a/spec/ChatRoomSpec.js b/spec/ChatRoomSpec.js new file mode 100644 index 000000000..b4f60b986 --- /dev/null +++ b/spec/ChatRoomSpec.js @@ -0,0 +1,224 @@ +(function (root, factory) { + define([ + "converse", + "mock" + ], function (converse, mock_connection) { + return factory(converse, mock_connection); + } + ); +} (this, function (converse, mock_connection) { + return describe("ChatRooms", $.proxy(function() { + var chatroom_names = [ + 'Dyon van de Wege', 'Thomas Kalb', 'Dirk Theissen', 'Felix Hofmann', 'Ka Lek', 'Anne Ebersbacher' + ]; + describe("A Chat Room", $.proxy(function () { + beforeEach($.proxy(function () { + if (!$("div#controlbox").is(':visible')) { + $('.toggle-online-users').click(); + } + var roomspanel = this.chatboxesview.views.controlbox.roomspanel; + var $input = roomspanel.$el.find('input.new-chatroom-name'); + var $server = roomspanel.$el.find('input.new-chatroom-server'); + $input.val('lounge'); + $server.val('muc.localhost'); + roomspanel.$el.find('form').submit(); + $('.toggle-online-users').click(); + }, converse)); + + it("shows users currently present in the room", $.proxy(function () { + var chatroomview = this.chatboxesview.views['lounge@muc.localhost']; + var $participant_list = chatroomview.$el.find('.participant-list'); + var roster = {}, room = {}, i; + for (i=0; i Date: Sat, 11 May 2013 14:34:36 +0200 Subject: [PATCH 8/9] Add a test to check that moderators are highlighted --- spec/ChatRoomSpec.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/ChatRoomSpec.js b/spec/ChatRoomSpec.js index b4f60b986..6fa1f252b 100644 --- a/spec/ChatRoomSpec.js +++ b/spec/ChatRoomSpec.js @@ -29,7 +29,8 @@ var chatroomview = this.chatboxesview.views['lounge@muc.localhost']; var $participant_list = chatroomview.$el.find('.participant-list'); var roster = {}, room = {}, i; - for (i=0; i Date: Sat, 11 May 2013 16:19:42 +0200 Subject: [PATCH 9/9] Add initial support for configuring chatrooms. For now we just create an instant chatroom, but eventually we must provide a way through the UI in which the user can request to configure the room. --- converse.css | 1 + converse.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/converse.css b/converse.css index 9fc227a74..df5d76e47 100644 --- a/converse.css +++ b/converse.css @@ -227,6 +227,7 @@ div.chat-title { text-overflow: ellipsis; white-space: nowrap; text-shadow: rgba(0,0,0,0.51) 0 -1px 0; + height: 1em; } .chat-head-chatbox, diff --git a/converse.js b/converse.js index 9e5680245..e95536e8b 100644 --- a/converse.js +++ b/converse.js @@ -1061,7 +1061,8 @@ this.model.get('nick'), $.proxy(this.onChatRoomMessage, this), $.proxy(this.onChatRoomPresence, this), - $.proxy(this.onChatRoomRoster, this)); + $.proxy(this.onChatRoomRoster, this), + null); this.model.messages.on('add', this.showMessage, this); this.model.on('destroy', function (model, response, options) { @@ -1079,15 +1080,54 @@ onLeave: function () {}, + showRoomConfigOptions: function (stanza) { + // FIXME: Show a proper configuration form + var $chat_content = this.$el.find('.chat-content'), + $stanza = $(stanza), + $fields = $stanza.find('field'), + title = $stanza.find('title').text(), + instructions = $stanza.find('instructions').text(), + i; + $chat_content.append(title); + $chat_content.append(instructions); + for (i=0; i<$fields.length; i++) { + $field = $($fields[i]); + $chat_content.append(''); + // $chat_content.append('