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/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; diff --git a/converse.css b/converse.css index d2538ef91..df5d76e47 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 { @@ -223,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, @@ -441,7 +446,7 @@ form.search-xmpp-contact input { text-overflow: ellipsis; white-space: nowrap; display: inline-block; - width: 160px; + width: 170px; } #available-chatrooms dt, @@ -468,6 +473,41 @@ dd.available-chatroom, text-shadow: 0 1px 0 rgba(250, 250, 250, 1); } +.room-info { + font-size: 11px; + font-style: normal; + font-weight: normal; +} + +p.room-info { + margin: 0; + padding: 0; + 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 +517,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%; } @@ -541,10 +581,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, diff --git a/converse.js b/converse.js index 39459f0d3..e95536e8b 100644 --- a/converse.js +++ b/converse.js @@ -684,14 +684,56 @@ 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}}

' + + '

Features:

' + ), tab_template: _.template('
  • Rooms
  • '), @@ -720,37 +762,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 +808,53 @@ 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 $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, + 'hidden':hidden, + 'membersonly':membersonly, + 'moderated':moderated, + 'nonanonymous':nonanonymous, + 'open':open, + 'passwordprotected':passwordprotected, + 'persistent':persistent, + 'publicroom': publicroom, + 'semianonymous':semianonymous, + 'temporary':temporary, + 'unmoderated':unmoderated + })); + }, this)); + } + }, + createChatRoom: function (ev) { ev.preventDefault(); var name, server, jid, $name, $server, errors; @@ -986,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) { @@ -1004,22 +1080,84 @@ 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('{{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; @@ -1344,7 +1498,6 @@ this.$el.addClass('current-xmpp-contact'); this.$el.html(this.template(item.toJSON())); } - return this; }, @@ -1802,7 +1955,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({ @@ -1834,7 +1986,6 @@ '' + ''), - renderStatusChangeForm: function (ev) { ev.preventDefault(); var status_message = this.model.get('status') || 'offline'; @@ -1937,6 +2088,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 () { @@ -2114,8 +2266,8 @@ converse.onConnected = function (connection) { this.connection = connection; - // this.connection.xmlInput = function (body) { console.log(body); }; - // this.connection.xmlOutput = function (body) { console.log(body); }; + this.connection.xmlInput = function (body) { console.log(body); }; + this.connection.xmlOutput = function (body) { console.log(body); }; this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid); this.domain = Strophe.getDomainFromJid(this.connection.jid); this.features = new this.Features(); 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..6fa1f252b --- /dev/null +++ b/spec/ChatRoomSpec.js @@ -0,0 +1,239 @@ +(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