diff --git a/config.js b/config.js index ca533e7ac..0a299d59e 100644 --- a/config.js +++ b/config.js @@ -135,7 +135,9 @@ require.config({ "chatbox_minimize": "src/templates/chatbox_minimize", "chatroom": "src/templates/chatroom", "chatroom_bookmark_form": "src/templates/chatroom_bookmark_form", + "chatroom_bookmark_toggle": "src/templates/chatroom_bookmark_toggle", "chatroom_form": "src/templates/chatroom_form", + "chatroom_head": "src/templates/chatroom_head", "chatroom_nickname_form": "src/templates/chatroom_nickname_form", "chatroom_password_form": "src/templates/chatroom_password_form", "chatroom_sidebar": "src/templates/chatroom_sidebar", @@ -147,6 +149,7 @@ require.config({ "contacts_tab": "src/templates/contacts_tab", "controlbox": "src/templates/controlbox", "controlbox_toggle": "src/templates/controlbox_toggle", + "dragresize": "src/templates/dragresize", "field": "src/templates/field", "form_captcha": "src/templates/form_captcha", "form_checkbox": "src/templates/form_checkbox", diff --git a/spec/chatroom.js b/spec/chatroom.js index 6cbae8750..42022a0a7 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -155,6 +155,36 @@ expect(view instanceof converse.ChatRoomView).toBe(true); })); + it("can be configured if you're its owner", mock.initConverse(function (converse) { + converse_api.rooms.open('room@conference.example.org', {'nick': 'some1'}); + var view = converse.chatboxviews.get('room@conference.example.org'); + spyOn(view, 'showConfigureButtonIfRoomOwner').andCallThrough(); + + /* + * + * + * + * + * + */ + var presence = $pres({ + to: 'dummy@localhost/converse.js-29092160', + from: 'room@conference.example.org/some1' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'owner', + 'jid': 'dummy@localhost/converse.js-29092160', + 'role': 'moderator' + }).up() + .c('status', {code: '110'}); + converse.connection._dataRecv(test_utils.createRequest(presence)); + expect(view.showConfigureButtonIfRoomOwner).toHaveBeenCalled(); + expect(view.$('.configure-chatroom-button').is(':visible')).toBeTruthy(); + expect(view.$('.toggle-chatbox-button').is(':visible')).toBeTruthy(); + expect(view.$('.toggle-bookmark').is(':visible')).toBeTruthy(); + })); + it("shows users currently present in the room", mock.initConverse(function (converse) { test_utils.openAndEnterChatRoom(converse, 'lounge', 'localhost', 'dummy'); var name; @@ -575,6 +605,43 @@ expect($occupants.children().first(0).text()).toBe("newnick"); })); + it("indicates when a room is no longer anonymous", mock.initConverse(function (converse) { + converse_api.rooms.open('room@conference.example.org', { + 'nick': 'some1', + 'roomconfig': { + 'changesubject': false, + 'membersonly': true, + 'persistentroom': true, + 'publicroom': true, + 'roomdesc': 'Welcome to this room', + 'whois': 'anyone' + } + }); + /* + * + * + * + * + * + */ + var message = $msg({ + type:'groupchat', + to: 'dummy@localhost/converse.js-27854181', + from: 'room@conference.example.org' + }).c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('status', {code: '104'}).up() + .c('status', {code: '172'}); + converse.connection._dataRecv(test_utils.createRequest(message)); + var view = converse.chatboxviews.get('room@conference.example.org'); + var $chat_body = view.$('.chatroom-body'); + expect($chat_body.html().trim().indexOf( + '
This room is now no longer anonymous
' + )).not.toBe(-1); + })); + it("informs users if they have been kicked out of the chat room", mock.initConverse(function (converse) { /* '; - this.$el.find('.chat-head-chatroom .icon-wrench').before(button); + var div = document.createElement('div'); + div.innerHTML = html; + var bookmark_button = converse.templates.chatroom_bookmark_toggle( + _.extend( + this.model.toJSON(), + { + info_toggle_bookmark: __('Bookmark this room'), + bookmarked: this.model.get('bookmarked') + } + )); + var close_button = div.querySelector('.close-chatbox-button'); + close_button.insertAdjacentHTML('afterend', bookmark_button); + return div.innerHTML; } - return this; + return html; }, checkForReservedNick: function () { diff --git a/src/converse-dragresize.js b/src/converse-dragresize.js index e0859c555..1b4670bf9 100644 --- a/src/converse-dragresize.js +++ b/src/converse-dragresize.js @@ -10,14 +10,16 @@ define("converse-dragresize", [ "converse-core", "converse-api", + "tpl!dragresize", "converse-chatview", "converse-muc", // XXX: would like to remove this "converse-controlbox" ], factory); -}(this, function (converse, converse_api) { +}(this, function (converse, converse_api, tpl_dragresize) { "use strict"; var $ = converse_api.env.jQuery, _ = converse_api.env._; + converse.templates.dragresize = tpl_dragresize; converse_api.plugins.add('converse-dragresize', { @@ -260,13 +262,23 @@ render: function () { var result = this.__super__.render.apply(this, arguments); + this.renderDragResizeHandles(); this.setWidth(); return result; + }, + + renderDragResizeHandles: function () { + var flyout = this.el.querySelector('.box-flyout'); + var div = document.createElement('div'); + div.innerHTML = converse.templates.dragresize(); + flyout.insertBefore( + div.firstChild, + flyout.firstChild + ); } } }, - initialize: function () { /* The initialize function gets called as soon as the plugin is * loaded by converse.js's plugin machinery. diff --git a/src/converse-headline.js b/src/converse-headline.js index 061b73693..48403a3e8 100644 --- a/src/converse-headline.js +++ b/src/converse-headline.js @@ -98,7 +98,6 @@ title: this.model.get('fullname'), unread_msgs: __('You have unread messages'), info_close: __('Close this box'), - info_minimize: __('Minimize this box'), label_personal_message: '' } ) diff --git a/src/converse-minimize.js b/src/converse-minimize.js index 7d855c9e2..445ab9f72 100644 --- a/src/converse-minimize.js +++ b/src/converse-minimize.js @@ -181,6 +181,18 @@ this.hide(); } return result; + }, + + generateHeadingHTML: function () { + var html = this.__super__.generateHeadingHTML.apply(this, arguments); + var div = document.createElement('div'); + div.innerHTML = html; + var el = converse.templates.chatbox_minimize( + {info_minimize: __('Minimize this chat box')} + ); + var button = div.querySelector('.close-chatbox-button'); + button.insertAdjacentHTML('afterend', el); + return div.innerHTML; } }, @@ -497,7 +509,7 @@ // Inserts a "minimize" button in the chatview's header var $el = view.$el.find('.toggle-chatbox-button'); var $new_el = converse.templates.chatbox_minimize( - _.extend({info_minimize: __('Minimize this chat box')}) + {info_minimize: __('Minimize this chat box')} ); if ($el.length) { $el.replaceWith($new_el); @@ -506,7 +518,6 @@ } }; converse.on('chatBoxOpened', renderMinimizeButton); - converse.on('chatRoomOpened', renderMinimizeButton); converse.on('controlBoxOpened', function (evt, chatbox) { // Wrapped in anon method because at scan time, chatboxviews diff --git a/src/converse-muc.js b/src/converse-muc.js index 6b0c1dfed..2fd81be74 100755 --- a/src/converse-muc.js +++ b/src/converse-muc.js @@ -20,6 +20,7 @@ "tpl!chatroom_password_form", "tpl!chatroom_sidebar", "tpl!chatroom_toolbar", + "tpl!chatroom_head", "tpl!chatrooms_tab", "tpl!info", "tpl!occupant", @@ -39,6 +40,7 @@ tpl_chatroom_password_form, tpl_chatroom_sidebar, tpl_chatroom_toolbar, + tpl_chatroom_head, tpl_chatrooms_tab, tpl_info, tpl_occupant, @@ -53,6 +55,7 @@ converse.templates.chatroom_nickname_form = tpl_chatroom_nickname_form; converse.templates.chatroom_password_form = tpl_chatroom_password_form; converse.templates.chatroom_sidebar = tpl_chatroom_sidebar; + converse.templates.chatroom_head = tpl_chatroom_head; converse.templates.chatrooms_tab = tpl_chatrooms_tab; converse.templates.info = tpl_info; converse.templates.occupant = tpl_occupant; @@ -109,7 +112,7 @@ 104: __('Non-privacy-related room configuration has changed'), 170: __('Room logging is now enabled'), 171: __('Room logging is now disabled'), - 172: __('This room is now non-anonymous'), + 172: __('This room is now no longer anonymous'), 173: __('This room is now semi-anonymous'), 174: __('This room is now fully-anonymous'), 201: __('A new room has been created') @@ -293,6 +296,14 @@ }, }); + converse.createChatRoom = function (settings) { + return converse.chatboxviews.showChat( + _.extend(settings, { + 'type': 'chatroom', + 'affiliation': undefined + }) + ); + }; converse.ChatRoomView = converse.ChatBoxView.extend({ /* Backbone View which renders a chat room, based upon the view @@ -320,6 +331,8 @@ this.model.on('show', this.show, this); this.model.on('destroy', this.hide, this); this.model.on('change:chat_state', this.sendChatState, this); + this.model.on('change:affiliation', this.renderHeading, this); + this.model.on('change:name', this.renderHeading, this); this.occupantsview = new converse.ChatRoomOccupantsView({ model: new converse.ChatRoomOccupants({nick: this.model.get('nick')}) @@ -358,16 +371,25 @@ render: function () { this.$el.attr('id', this.model.get('box_id')) - .html(converse.templates.chatroom( - _.extend(this.model.toJSON(), { - info_close: __('Close and leave this room'), - info_configure: __('Configure this room'), - }))); + .html(converse.templates.chatroom()); + this.renderHeading(); this.renderChatArea(); utils.refreshWebkit(); return this; }, + generateHeadingHTML: function () { + return converse.templates.chatroom_head( + _.extend(this.model.toJSON(), { + info_close: __('Close and leave this room'), + info_configure: __('Configure this room'), + })); + }, + + renderHeading: function () { + this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML(); + }, + renderChatArea: function () { if (!this.$('.chat-area').length) { this.$('.chatroom-body').empty() @@ -1044,13 +1066,25 @@ this.$('.chatroom-body').append($('

'+msg+'

')); }, - getMessageFromStatus: function (stat, is_self, from_nick, item) { - var code = stat.getAttribute('code'); + getMessageFromStatus: function (stat, stanza, is_self) { + /* Parameters: + * (XMLElement) stat: A element. + * (Boolean) is_self: Whether the element refers to the + * current user. + * (XMLElement) stanza: The original stanza received. + */ + var code = stat.getAttribute('code'), + from_nick; if (is_self && code === "210") { + from_nick = Strophe.unescapeNode(Strophe.getResourceFromJid(stanza.getAttribute('from'))); return __(converse.muc.new_nickname_messages[code], from_nick); } else if (is_self && code === "303") { - return __(converse.muc.new_nickname_messages[code], item.getAttribute('nick')); + return __( + converse.muc.new_nickname_messages[code], + stanza.querySelector('x item').getAttribute('nick') + ); } else if (!is_self && (code in converse.muc.action_info_messages)) { + from_nick = Strophe.unescapeNode(Strophe.getResourceFromJid(stanza.getAttribute('from'))); return __(converse.muc.action_info_messages[code], from_nick); } else if (code in converse.muc.info_messages) { return converse.muc.info_messages[code]; @@ -1063,37 +1097,42 @@ return; }, - parseXUserElement: function (x, is_self, from_nick) { + showConfigureButtonIfRoomOwner: function (pres) { + /* Show the configure button if the user is the room owner. + * + * Parameters: + * (XMLElement) pres: A stanza. + */ + // XXX: For some inexplicable reason, the following line of + // code works in tests, but not with live data, even though + // the passed in stanza looks exactly the same to me: + // var item = pres.querySelector('x[xmlns="'+Strophe.NS.MUC_USER+'"] item'); + // If we want to eventually get rid of jQuery altogether, + // then the Sizzle selector library might still be needed + // here. + var item = $(pres).find('x[xmlns="'+Strophe.NS.MUC_USER+'"] item').get(0); + if (_.isUndefined(item)) { + return; + } + var jid = item.getAttribute('jid'); + var affiliation = item.getAttribute('affiliation'); + if (Strophe.getBareJidFromJid(jid) === converse.bare_jid && affiliation) { + this.model.save({'affiliation': affiliation}); + } + }, + + parseXUserElement: function (x, stanza, is_self) { /* Parse the passed-in * element and construct a map containing relevant * information. */ - // By using querySelector, we assume here there is one - // per - // element. This appears to be a safe assumption, since - // each element pertains to a single user. - var item = x.querySelector('item'); - // Show the configure button if user is the room owner. - var jid = item.getAttribute('jid'); - var affiliation = item.getAttribute('affiliation'); - if (Strophe.getBareJidFromJid(jid) === converse.bare_jid && affiliation === 'owner') { - this.$el.find('a.configure-chatroom-button').show(); - } - // Extract notification messages, reasons and - // disconnection messages from the node. + // 1. Get notification messages based on the elements. var statuses = x.querySelectorAll('status'); - var mapper = _.partial(this.getMessageFromStatus, _, is_self, from_nick, item); + var mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self); var notification = { 'messages': _.reject(_.map(statuses, mapper), _.isUndefined), }; - var reason = item.querySelector('reason'); - if (reason) { - notification.reason = reason ? reason.textContent : undefined; - } - var actor = item.querySelector('actor'); - if (actor) { - notification.actor = actor ? actor.getAttribute('nick') : undefined; - } + // 2. Get disconnection messages based on the elements var codes = _.map(statuses, function (stat) { return stat.getAttribute('code'); }); var disconnection_codes = _.intersection(codes, _.keys(converse.muc.disconnect_messages)); var disconnected = is_self && disconnection_codes.length > 0; @@ -1101,6 +1140,22 @@ notification.disconnected = true; notification.disconnection_message = converse.muc.disconnect_messages[disconnection_codes[0]]; } + // 3. Find the reason and actor from the element + var item = x.querySelector('item'); + // By using querySelector above, we assume here there is + // one per + // element. This appears to be a safe assumption, since + // each element pertains to a single user. + if (!_.isNull(item)) { + var reason = item.querySelector('reason'); + if (reason) { + notification.reason = reason ? reason.textContent : undefined; + } + var actor = item.querySelector('actor'); + if (actor) { + notification.actor = actor ? actor.getAttribute('nick') : undefined; + } + } return notification; }, @@ -1132,22 +1187,18 @@ } }, - showStatusMessages: function (presence, is_self) { + showStatusMessages: function (stanza, is_self) { /* Check for status codes and communicate their purpose to the user. * Allows user to configure chat room if they are the owner. * See: http://xmpp.org/registrar/mucstatus.html */ - var from_nick = Strophe.unescapeNode(Strophe.getResourceFromJid(presence.getAttribute('from'))); - // XXX: Unfortunately presence.querySelectorAll('x[xmlns="'+Strophe.NS.MUC_USER+'"]') returns [] - var elements = _.filter(presence.querySelectorAll('x'), function (x) { - return x.getAttribute('xmlns') === Strophe.NS.MUC_USER; - }); + var elements = stanza.querySelectorAll('x[xmlns="'+Strophe.NS.MUC_USER+'"]'); var notifications = _.map( elements, - _.partial(this.parseXUserElement.bind(this), _, is_self, from_nick) + _.partial(this.parseXUserElement.bind(this), _, stanza, is_self) ); _.each(notifications, this.displayNotificationsforUser.bind(this)); - return presence; + return stanza; }, showErrorMessage: function (presence) { @@ -1221,6 +1272,7 @@ this.configureChatRoom(); } else { this.hideSpinner().showStatusMessages(pres, is_self); + this.showConfigureButtonIfRoomOwner(pres); } } } @@ -1702,7 +1754,7 @@ return; } } - converse.chatboxviews.showChat({ + converse.createChatRoom({ 'id': jid, 'jid': jid, 'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)), @@ -1750,7 +1802,7 @@ } } if (result === true) { - var chatroom = converse.chatboxviews.showChat({ + var chatroom = converse.createChatRoom({ 'id': room_jid, 'jid': room_jid, 'name': Strophe.unescapeNode(Strophe.getNodeFromJid(room_jid)), @@ -1837,16 +1889,15 @@ if (_.isUndefined(attrs.maximize)) { attrs.maximize = false; } - var fetcher = converse.chatboxviews.showChat.bind(converse.chatboxviews); if (!attrs.nick && converse.muc_nickname_from_jid) { attrs.nick = Strophe.getNodeFromJid(converse.bare_jid); } if (typeof jids === "undefined") { throw new TypeError('rooms.open: You need to provide at least one JID'); } else if (typeof jids === "string") { - return _transform(jids, attrs, fetcher); + return _transform(jids, attrs, converse.createChatRoom); } - return _.map(jids, _.partial(_transform, _, attrs, fetcher)); + return _.map(jids, _.partial(_transform, _, attrs, converse.createChatRoom)); }, 'get': function (jids, attrs, create) { if (typeof attrs === "string") { diff --git a/src/templates/chatroom.html b/src/templates/chatroom.html index 6f3fc823a..70e8ad20c 100644 --- a/src/templates/chatroom.html +++ b/src/templates/chatroom.html @@ -1,14 +1,4 @@
-
-
-
-
- - -
- {{ _.escape(name) }} -

-

-
+
diff --git a/src/templates/chatroom_bookmark_toggle.html b/src/templates/chatroom_bookmark_toggle.html new file mode 100644 index 000000000..a2aac26e9 --- /dev/null +++ b/src/templates/chatroom_bookmark_toggle.html @@ -0,0 +1,4 @@ + diff --git a/src/templates/chatroom_dragresize.html b/src/templates/chatroom_dragresize.html new file mode 100644 index 000000000..8bf87c39a --- /dev/null +++ b/src/templates/chatroom_dragresize.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/src/templates/chatroom_head.html b/src/templates/chatroom_head.html new file mode 100644 index 000000000..f36203ffb --- /dev/null +++ b/src/templates/chatroom_head.html @@ -0,0 +1,8 @@ + +{[ if (affiliation == 'owner') { ]} + +{[ } ]} +
+ {{ _.escape(name) }} +

+

diff --git a/src/templates/dragresize.html b/src/templates/dragresize.html new file mode 100644 index 000000000..8bf87c39a --- /dev/null +++ b/src/templates/dragresize.html @@ -0,0 +1,3 @@ +
+
+