From 910e9bddcdb2be45183c3f21d7f8d96c437d925a Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 2 Dec 2016 18:41:05 +0100 Subject: [PATCH] Refactoring in MUC What started as an attempt to fix a bug in parseXUserElement, turned into another large refactoring of MUC code, and it's not clear how to break this up into multiple atomic commits. So I'm just pushing it all. At least there are two new tests added to the suite. --- config.js | 3 + spec/chatroom.js | 67 ++++++++++ src/converse-bookmarks.js | 27 ++-- src/converse-dragresize.js | 16 ++- src/converse-headline.js | 1 - src/converse-minimize.js | 15 ++- src/converse-muc.js | 141 +++++++++++++------- src/templates/chatroom.html | 12 +- src/templates/chatroom_bookmark_toggle.html | 4 + src/templates/chatroom_dragresize.html | 3 + src/templates/chatroom_head.html | 8 ++ src/templates/dragresize.html | 3 + 12 files changed, 231 insertions(+), 69 deletions(-) create mode 100644 src/templates/chatroom_bookmark_toggle.html create mode 100644 src/templates/chatroom_dragresize.html create mode 100644 src/templates/chatroom_head.html create mode 100644 src/templates/dragresize.html 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 @@ +
+
+