diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 4980f295e93..110995e0954 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -396,6 +396,7 @@ class Channel(models.Model): if self.channel_message_ids.ids: last_message_id = self.channel_message_ids.ids[0] # zero is the index of the last message self.env['mail.channel.partner'].search([('channel_id', 'in', self.ids), ('partner_id', '=', self.env.user.partner_id.id)]).write({'seen_message_id': last_message_id}) + return last_message_id @api.multi def channel_invite(self, partner_ids): diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 1dff03cc902..3e93973dd82 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -146,52 +146,84 @@ class Message(models.Model): # Notification API #------------------------------------------------------ - @api.multi - def set_message_needaction(self, partner_ids=None): - if not partner_ids: - partner_ids = [self.env.user.partner_id.id] - if set(partner_ids) == set([self.env.user.partner_id.id]): - # a user should be able to mark a message as needaction for him - self = self.sudo() - return self.write({'needaction_partner_ids': [(4, pid) for pid in partner_ids]}) - - @api.multi - def mark_all_as_read(self): - user_id = self.env.user.partner_id.id - + @api.model + def mark_all_as_read(self, channel_ids=None): + """ Remove all needactions of the current partner. If channel_ids is + given, restrict to messages written in one of those channels. """ + partner_id = self.env.user.partner_id.id # possibly horribly inefficient method: # it does one db request for the search, and one for each message in # the result set to remove the current user from the relation. - # unread_messages = self.search([('needaction_partner_ids', 'in', user_id)]) - # unread_messages.write({'needaction_partner_ids': [(3, user_id)]}) + # domain = [('needaction_partner_ids', 'in', partner_id)] + # if channel_ids: + # domain += [('channel_ids', 'in', channel_ids)] + # unread_messages = self.search(domain) + # unread_messages.write({'needaction_partner_ids': [(3, partner_id)]}) # a much faster way to do this is in pure sql: - self.env.cr.execute("DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s", [(user_id,)]) + query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s" + args = [(partner_id,)] + if channel_ids: + query += """ + AND mail_message_id in + (SELECT mail_message_id + FROM mail_message_mail_channel_rel + WHERE mail_channel_id in %s)""" + args += [tuple(channel_ids)] + query += " RETURNING mail_message_id as id" + self._cr.execute(query, args) + self.invalidate_cache() + + ids = [m['id'] for m in self._cr.dictfetchall()] + notification = {'type': 'mark_as_read', 'message_ids': ids, 'channel_ids': channel_ids} + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification) + + return ids + + @api.multi + def mark_as_unread(self, channel_ids=None): + """ Add needactions to messages for the current partner. """ + partner_id = self.env.user.partner_id.id + for message in self: + message.write({'needaction_partner_ids': [(4, partner_id)]}) + + ids = [m.id for m in self] + notification = {'type': 'mark_as_unread', 'message_ids': ids, 'channel_ids': channel_ids} + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification) @api.multi def set_message_done(self, partner_ids=None): + """ Remove the needaction from messages for the current partner, or for + partners in partner_ids if partner_ids is given. """ if not partner_ids: partner_ids = [self.env.user.partner_id.id] if set(partner_ids) == set([self.env.user.partner_id.id]): # a user should be able to mark a message as done for him self = self.sudo() - return self.write({'needaction_partner_ids': [(3, pid) for pid in partner_ids]}) + self.write({'needaction_partner_ids': [(3, pid) for pid in partner_ids]}) + + channel_ids = [c.id for c in self.channel_ids] + notification = {'type': 'mark_as_read', 'message_ids': [self.id], 'channel_ids': channel_ids} + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification) @api.model def unstar_all(self): + """ Unstar messages for the current partner. """ partner_id = self.env.user.partner_id.id starred_messages = self.search([('starred_partner_ids', 'in', partner_id)]) starred_messages.write({'starred_partner_ids': [(3, partner_id)]}) + ids = [m.id for m in starred_messages] + notification = {'type': 'toggle_star', 'message_ids': ids, 'starred': False} + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification) + @api.multi def set_message_starred(self, starred): """ Set messages as (un)starred. Technically, the notifications related to uid are set to (un)starred. :param bool starred: set notification as (un)starred - :param bool create_missing: create notifications for missing entries - (i.e. when acting on displayed messages not notified) """ # a user should always be able to star a message he can read self.check_access_rule('read') @@ -199,7 +231,9 @@ class Message(models.Model): self.sudo().write({'starred_partner_ids': [(4, self.env.user.partner_id.id)]}) else: self.sudo().write({'starred_partner_ids': [(3, self.env.user.partner_id.id)]}) - return starred + + notification = {'type': 'toggle_star', 'message_ids': [self.id], 'starred': starred} + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification) #------------------------------------------------------ # Message loading for web interface diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js index 6bd96fdc32d..01cc6918264 100644 --- a/addons/mail/static/src/js/chat_manager.js +++ b/addons/mail/static/src/js/chat_manager.js @@ -37,7 +37,7 @@ function add_message (data, options) { // Keep the array ordered by date when inserting the new message messages.splice(_.sortedIndex(messages, msg, 'id'), 0, msg); if (options.channel_id) { - var channel = _.findWhere(channels, {id: options.channel_id}); + var channel = chat_manager.get_channel(options.channel_id); if (channel.hidden) { channel.hidden = false; chat_manager.bus.trigger('new_channel', channel); @@ -117,8 +117,8 @@ function make_message (data) { } var real_channels = _.without(msg.channel_ids, 'channel_inbox', 'channel_starred'); var origin = real_channels.length ? real_channels[0] : undefined; - var channel; - if (origin && (channel = _.findWhere(channels, {id: origin}))) { + var channel = origin && chat_manager.get_channel(origin); + if (channel) { msg.origin_id = origin; msg.origin_name = channel.name; } @@ -165,7 +165,7 @@ function post_document_message (model_name, res_id, data) { function add_channel (data, options) { options = typeof options === "object" ? options : {}; - var channel = _.findWhere(channels, {id: data.id}); + var channel = chat_manager.get_channel(data.id); if (channel) { if (channel.is_folded !== (data.state === "folded")) { channel.is_folded = (data.state === "folded"); @@ -197,6 +197,9 @@ function make_channel (data, options) { autoswitch: 'autoswitch' in options ? options.autoswitch : true, hidden: options.hidden, display_needactions: options.display_needactions, + needaction_counter: data.message_needaction_counter || 0, + unread_counter: data.message_unread_counter || 0, + last_seen_message_id: data.seen_message_id, cache: {'[]': { all_history_loaded: false, loaded: false, @@ -299,6 +302,126 @@ function fetch_document_messages (ids, options) { } } +var channel_seen = _.throttle(function (channel) { + return ChannelModel.call('channel_seen', [[channel.id]]).then(function (last_seen_message_id) { + channel.last_seen_message_id = last_seen_message_id; + }); +}, 3000); + +// Notification handlers +// --------------------------------------------------------------------------------- +function on_notification (notification) { + var model = notification[0][1]; + if (model === 'ir.needaction') { + // new message in the inbox + on_needaction_notification(notification[1]); + } else if (model === 'mail.channel') { + // new message in a channel + on_channel_notification(notification[1]); + } else if (model === 'res.partner') { + // channel joined/left, message marked as read/(un)starred, chat open/closed + on_partner_notification(notification[1]); + } +} + +function on_needaction_notification (message) { + message = add_message(message, { channel_id: 'channel_inbox', show_notification: true} ); + needaction_counter++; + var channel = chat_manager.get_channel(message.channel_ids[0]); + if (channel) { + channel.needaction_counter++; + } + chat_manager.bus.trigger('update_needaction', needaction_counter); +} + +function on_channel_notification (message) { + var channel = chat_manager.get_channel(message.channel_ids[0]); + if (channel) { + channel.unread_counter++; + add_message(message, { channel_id: channel.id, show_notification: true }); + } +} + +function on_partner_notification (data) { + if (data.info === "unsubscribe") { + channels = _.without(channels, chat_manager.get_channel(data.id)); + chat_manager.bus.trigger("unsubscribe_from_channel", data.id); + } else if (data.type === 'toggle_star') { + on_toggle_star_notification(data); + } else if (data.type === 'mark_as_read') { + on_mark_as_read_notification(data); + } else if (data.type === 'mark_as_unread') { + on_mark_as_unread_notification(data); + } else { + on_chat_session_notification(data); + } +} + +function on_toggle_star_notification (data) { + _.each(data.message_ids, function (msg_id) { + var message = _.findWhere(messages, { id: msg_id }); + if (message) { + message.is_starred = data.starred; + if (!message.is_starred) { + remove_message_from_channel("channel_starred", message); + } + chat_manager.bus.trigger('update_message', message); + } + }); +} + +function on_mark_as_read_notification (data) { + _.each(data.message_ids, function (msg_id) { + var message = _.findWhere(messages, { id: msg_id }); + if (message) { + remove_message_from_channel("channel_inbox", message); + chat_manager.bus.trigger('update_message', message); + } + }); + if (data.channel_ids.length) { + _.each(data.channel_ids, function (channel_id) { + var channel = chat_manager.get_channel(channel_id); + if (channel) { + channel.needaction_counter -= data.message_ids.length; + } + }); + } else { + _.each(channels, function (channel) { + channel.needaction_counter = 0; + }); + } + needaction_counter -= data.message_ids.length; + chat_manager.bus.trigger('update_needaction', needaction_counter); +} + +function on_mark_as_unread_notification (data) { + _.each(data.channel_ids, function (channel_id) { + var channel = chat_manager.get_channel(channel_id); + if (channel) { + channel.needaction_counter += data.message_ids.length; + } + }); + needaction_counter += data.message_ids.length; + chat_manager.bus.trigger('update_needaction', needaction_counter); +} + +function on_chat_session_notification (chat_session) { + if ((chat_session.channel_type === "channel") && (chat_session.public === "private") && (chat_session.state === "open")) { + add_channel(chat_session, {autoswitch: false}); + if (!chat_session.is_minimized) { + web_client.do_notify(_t("Private Channel"), _t("You have been invited to: ") + chat_session.name); + } + } + // partner specific change (open a detached window for example) + if ((chat_session.state === "open") || (chat_session.state === "folded")) { + add_channel(chat_session, {autoswitch: false}); + if (chat_session.is_minimized) { + chat_manager.bus.trigger("open_chat", chat_session); + } + } else if (chat_session.state === "closed") { + chat_manager.bus.trigger("close_chat", chat_session); + } +} // Public interface //---------------------------------------------------------------------------------- @@ -335,42 +458,28 @@ var chat_manager = { toggle_star_status: function (message_id) { var msg = _.findWhere(messages, { id: message_id }); - return MessageModel.call('set_message_starred', [[message_id], !msg.is_starred]).then(function () { - msg.is_starred = !msg.is_starred; - if (!msg.is_starred) { - remove_message_from_channel("channel_starred", msg); - } - chat_manager.bus.trigger('update_message', msg); - }); + return MessageModel.call('set_message_starred', [[message_id], !msg.is_starred]); + }, + unstar_all: function () { + return MessageModel.call('unstar_all', [[]], {}); }, mark_as_read: function (message_id) { - return MessageModel.call('set_message_done', [[message_id]]).then(function () { - var message = _.findWhere(messages, { id: message_id }); - remove_message_from_channel("channel_inbox", message); - chat_manager.bus.trigger('update_message', message); - needaction_counter = needaction_counter - 1; - chat_manager.bus.trigger('update_needaction', needaction_counter); - }); + return MessageModel.call('set_message_done', [[message_id]]); }, - - mark_all_as_read: function () { - MessageModel.call('mark_all_as_read', [[]], {}).then(function (ids) { - _.each(messages, function (msg) { - remove_message_from_channel("channel_inbox", msg); - chat_manager.bus.trigger('update_message', msg); - }); - needaction_counter = 0; - chat_manager.bus.trigger('update_needaction', needaction_counter); - }); + mark_all_as_read: function (channel) { + if ((!channel && needaction_counter) || (channel && channel.needaction_counter)) { + return MessageModel.call('mark_all_as_read', channel ? [[channel.id]] : [[]]); + } + return $.when(); }, - - unstar_all: function () { - MessageModel.call('unstar_all', [[]], {}).then(function (ids) { - _.each(messages, function (msg) { - remove_message_from_channel("channel_starred", msg); - chat_manager.bus.trigger('update_message', msg); - }); - }); + undo_mark_as_read: function (message_ids, channel) { + return MessageModel.call('mark_as_unread', [message_ids, [channel.id]]); + }, + mark_channel_as_seen: function (channel) { + if (channel.unread_counter > 0) { + channel.unread_counter = 0; + channel_seen(channel); + } }, get_channels: function () { @@ -378,7 +487,7 @@ var chat_manager = { }, get_channel: function (id) { - return _.findWhere(channels, {id: id}) || channels[0]; + return _.findWhere(channels, {id: id}); }, all_history_loaded: function (channel, domain) { @@ -435,11 +544,11 @@ var chat_manager = { }); }, close_chat_session: function (channel_id) { - var channel = _.findWhere(channels, {id: channel_id}); + var channel = this.get_channel(channel_id); ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"}); }, fold_channel: function (channel_id) { - var channel = _.findWhere(channels, {id: channel_id}); + var channel = this.get_channel(channel_id); return ChannelModel.call("channel_fold", [], {uuid : channel.uuid}).then(function () { channel.is_folded = !channel.is_folded; }); @@ -479,52 +588,7 @@ function init () { var load_menu_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"]); var load_action_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"]); - bus.on('notification', null, function (notification) { - var model = notification[0][1]; - if (model === 'ir.needaction') { - // new message in the inbox - var message = notification[1]; - message = add_message(message, { channel_id: 'channel_inbox', show_notification: true} ); - needaction_counter = needaction_counter + 1; - chat_manager.bus.trigger('update_needaction', needaction_counter); - } - if (model === 'mail.channel') { - // new message in a channel - var message = notification[1]; - var channel_id = message.channel_ids[0]; - var channel = _.findWhere(channels, {id: channel_id}); - if (channel) { - add_message(message, { channel_id: channel_id, show_notification: true }); - } - } - if (model === 'res.partner') { - var chat_session = notification[1]; - if (chat_session.info === "unsubscribe") { - var channel = _.findWhere(channels, {id: chat_session.id}); - if (channel) { - channels = _.without(channels, channel); - } - chat_manager.bus.trigger("unsubscribe_from_channel", chat_session.id); - return; - } - if ((chat_session.channel_type === "channel") && (chat_session.public === "private") && (chat_session.state === "open")) { - add_channel(chat_session, {autoswitch: false}); - if (!chat_session.is_minimized) { - web_client.do_notify(_t("Private Channel"), _t("You have been invited to: ") + chat_session.name); - } - } - // partner specific change (open a detached window for example) - if ((chat_session.state === "open") || (chat_session.state === "folded")) { - add_channel(chat_session, {autoswitch: false}); - if (chat_session.is_minimized) { - chat_manager.bus.trigger("open_chat", chat_session); - } - } - if (chat_session.state === "closed") { - chat_manager.bus.trigger("close_chat", chat_session); - } - } - }); + bus.on('notification', null, on_notification); return $.when(load_menu_id, load_action_id, load_channels, load_emojis).then(function (menu_id, action_id) { discuss_ids = { diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index 5b98786ccdc..fae9e294e57 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -776,7 +776,7 @@ var Chatter = form_common.AbstractField.extend({ var def; var channel = chat_manager.get_channel(channel_id); // If not registered to 'channel' yet, do it - if (channel.id !== channel_id) { + if (!channel) { def = chat_manager.join_channel(channel_id); } $.when(def).then(function () { diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 6c5b4ecba5b..7974203abe4 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -109,6 +109,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, { var channel_id = $(event.target).data("channel-id"); this.unsubscribe_from_channel(chat_manager.get_channel(channel_id)); }, + "click .o_snackbar_undo": function (event) { + event.preventDefault(); + var channel = this.channel; + this.$snackbar.remove(); + this.clear_needactions_def.then(function (msgs_ids) { + chat_manager.undo_mark_as_read(msgs_ids, channel); + }); + }, }, on_attach_callback: function () { @@ -146,6 +154,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.action.context.active_id || this.action.params.default_active_id || 'channel_inbox'; + var default_channel = chat_manager.get_channel(default_channel_id) || + chat_manager.get_channel('channel_inbox'); + this.searchview = new SearchView(this, dataset, view_id, {}, options); this.searchview.on('search_data', this, this.on_search); @@ -167,14 +178,15 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.$buttons.on('click', '.o_mail_toggle_channels', function () { self.$('.o_mail_chat_sidebar').slideToggle(200); }); - this.$buttons.on('click', '.o_mail_chat_button_mark_read', chat_manager.mark_all_as_read); + this.$buttons.on('click', '.o_mail_chat_button_mark_read', function () { + chat_manager.mark_all_as_read(); + }); this.$buttons.on('click', '.o_mail_chat_button_unstar_all', chat_manager.unstar_all); - this.thread.on('redirect', this, this.on_redirect); this.thread.on('redirect_to_channel', this, function (channel_id) { var channel = chat_manager.get_channel(channel_id); - if (channel.id === channel_id) { + if (channel) { this.set_channel(channel); } else { chat_manager.join_channel(channel_id); @@ -196,7 +208,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.render_sidebar(); return $.when(def1, def2, def3) - .then(this.set_channel.bind(this, chat_manager.get_channel(default_channel_id))) + .then(this.set_channel.bind(this, default_channel)) .then(function () { chat_manager.bus.on('new_message', self, self.on_new_message); chat_manager.bus.on('update_message', self, self.on_update_message); @@ -204,7 +216,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { chat_manager.bus.on('anyone_listening', self, function (channel, query) { query.is_displayed = query.is_displayed || channel.id === self.channel.id; }); - chat_manager.bus.on('update_needaction', self, self.on_update_needaction); + chat_manager.bus.on('update_needaction', self, self.render_sidebar); chat_manager.bus.on('unsubscribe_from_channel', self, self.render_sidebar); }); }, @@ -212,8 +224,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { render_sidebar: function () { var self = this; var $sidebar = QWeb.render("mail.chat.Sidebar", { + active_channel_id: this.channel ? this.channel.id: undefined, channels: chat_manager.get_channels(), - needaction_counter: chat_manager.get_needaction_counter() + needaction_counter: chat_manager.get_needaction_counter(), }); this.$(".o_mail_chat_sidebar").replaceWith($sidebar); @@ -303,41 +316,59 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop(); } var new_channel_scrolltop = this.channels_scrolltop[channel.id]; + this.channel = channel; - - // Update control panel - this.set("title", channel.name); - // Hide 'detach' button in static channels - this.$buttons - .find('.o_mail_chat_button_detach') - .toggle(channel.type !== "static"); - // Hide 'invite', 'unsubscribe' and 'settings' buttons in static channels and DM - this.$buttons - .find('.o_mail_chat_button_invite, .o_mail_chat_button_unsubscribe, .o_mail_chat_button_settings') - .toggle(channel.type !== "dm" && channel.type !== 'static'); - - this.$buttons - .find('.o_mail_chat_button_mark_read') - .toggle(channel.id === "channel_inbox"); - - this.$buttons - .find('.o_mail_chat_button_unstar_all') - .toggle(channel.id === "channel_starred"); - - this.update_cp(); + this.messages_separator_position = undefined; // reset value on channel change + this.unread_counter = this.channel.unread_counter; + this.last_seen_message_id = this.channel.last_seen_message_id; + this.clear_needactions_def = $.Deferred(); + if (this.$snackbar) { + this.$snackbar.remove(); + } this.action.context.active_id = channel.id; this.action.context.active_ids = [channel.id]; - this.$('.o_mail_chat_channel_item') - .removeClass('o_active') - .filter('[data-channel-id=' + channel.id + ']') - .removeClass('o_unread_message') - .addClass('o_active'); - - this.$('.o_chat_composer').toggle(channel.type !== 'static'); - return this.fetch_and_render_thread().then(function () { + // Mark channel's messages as read and clear needactions + if (channel.type !== 'static') { + // Display snackbar if needactions have been cleared + if (channel.needaction_counter > 0) { + self.$snackbar = $(QWeb.render('mail.chat.UndoSnackbar', { + nb_needactions: channel.needaction_counter, + })); + self.$('.o_mail_chat_content').append(self.$snackbar); + } + chat_manager.mark_channel_as_seen(channel); + self.clear_needactions_def = chat_manager.mark_all_as_read(channel); + } + + // Update control panel + self.set("title", channel.name); + // Hide 'detach' button in static channels + self.$buttons + .find('.o_mail_chat_button_detach') + .toggle(channel.type !== "static"); + // Hide 'invite', 'unsubscribe' and 'settings' buttons in static channels and DM + self.$buttons + .find('.o_mail_chat_button_invite, .o_mail_chat_button_unsubscribe, .o_mail_chat_button_settings') + .toggle(channel.type !== "dm" && channel.type !== 'static'); + self.$buttons + .find('.o_mail_chat_button_mark_read') + .toggle(channel.id === "channel_inbox"); + self.$buttons + .find('.o_mail_chat_button_unstar_all') + .toggle(channel.id === "channel_starred"); + self.update_cp(); + + self.$('.o_chat_composer').toggle(channel.type !== 'static'); + + self.$('.o_mail_chat_channel_item') + .removeClass('o_active') + .filter('[data-channel-id=' + channel.id + ']') + .removeClass('o_unread_message') + .addClass('o_active'); + self.thread.scroll_to({offset: new_channel_scrolltop}); if (!config.device.touch) { self.composer.focus(); @@ -367,11 +398,24 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }); }, - get_thread_rendering_options: function () { + get_thread_rendering_options: function (messages) { + // Compute position of the 'New messages' separator, only once when joining + // a channel to keep it in the thread when new messages arrive + if (_.isUndefined(this.messages_separator_position)) { + var msg_id = this.last_seen_message_id; + if (!this.unread_counter) { + this.messages_separator_position = false; // no unread message -> don't display separator + } else if ((msg_id === false) || !_.findWhere(messages, {id: msg_id})) { + this.messages_separator_position = 'top'; // all displayed messages are unread + } else { + this.messages_separator_position = msg_id; // last read message is msg_id + } + } return { + channel_id: this.channel.id, display_load_more: !chat_manager.all_history_loaded(this.channel, this.domain), display_needactions: this.channel.display_needactions, - channel_id: this.channel.id, + messages_separator_position: this.messages_separator_position, squash_close_messages: this.channel.type !== 'static', }; }, @@ -379,7 +423,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { fetch_and_render_thread: function () { var self = this; return chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function(result) { - self.thread.render(result, self.get_thread_rendering_options()); + self.thread.render(result, self.get_thread_rendering_options(result)); self.update_button_status(result.length === 0); }); }, @@ -405,7 +449,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, { return chat_manager .fetch_more(this.channel, this.domain) .then(function(result) { - self.thread.render(result, self.get_thread_rendering_options()); + if (self.messages_separator_position === 'top') { + self.messages_separator_position = undefined; // reset value to re-compute separator position + } + self.thread.render(result, self.get_thread_rendering_options(result)); offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; self.thread.scroll_to({offset: offset}); }); @@ -493,16 +540,15 @@ var ChatAction = Widget.extend(ControlPanelMixin, { on_new_message: function (message) { var self = this; if (_.contains(message.channel_ids, this.channel.id)) { + if (this.channel.type !== 'static') { + chat_manager.mark_channel_as_seen(this.channel); + } this.fetch_and_render_thread().then(function () { self.thread.scroll_to({id: message.id}); }); } - // Indicate in the sidebar that there is a new message in the corresponding channels - _.each(_.without(message.channel_ids, this.channel.id), function (channel_id) { - self.$('.o_mail_chat_channel_item') - .filter('[data-channel-id=' + channel_id + ']') - .addClass('o_unread_message'); - }); + // Re-render sidebar to indicate that there is a new message in the corresponding channels + this.render_sidebar(); // Dump scroll position of channels in which the new message arrived this.channels_scrolltop = _.omit(this.channels_scrolltop, message.channel_ids); }, @@ -512,7 +558,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { if ((current_channel_id === "channel_starred" && !message.is_starred) || (current_channel_id === "channel_inbox" && !message.is_needaction)) { chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function (messages) { - var options = self.get_thread_rendering_options(); + var options = self.get_thread_rendering_options(messages); self.thread.remove_message_and_render(message.id, messages, options).then(function () { self.update_button_status(messages.length === 0); }); @@ -527,12 +573,6 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.set_channel(channel); } }, - on_update_needaction: function () { - var $needaction_badge = QWeb.render("mail.chat.SidebarNeedaction", { - needaction_counter: chat_manager.get_needaction_counter() - }); - this.$('.o_mail_sidebar_needaction').replaceWith($needaction_badge); - }, on_click_button_invite: function () { var title = _.str.sprintf(_t('Invite people to %s'), this.channel.name); diff --git a/addons/mail/static/src/less/client_action.less b/addons/mail/static/src/less/client_action.less index 9f8d501c3a2..f6194bfc973 100644 --- a/addons/mail/static/src/less/client_action.less +++ b/addons/mail/static/src/less/client_action.less @@ -67,9 +67,10 @@ .o_mail_chat_content { .o-flex(1, 1, 100%); - height: 100%; .o-flex-display(); .o-flex-flow(column, nowrap); + height: 100%; + position: relative; .o_mail_thread { .o-flex(1, 0, 0); @@ -100,6 +101,14 @@ } } } + .o_mail_snackbar { + .o-position-absolute(15px, 15px); + padding-top: 10px; + padding-bottom: 10px; + background-color: lighten(@odoo-brand-optional, 50%); + border-color: lighten(@odoo-brand-optional, 40%); + color: @odoo-brand-optional; + } } } diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less index bcb73d4dd7c..df865d14a4f 100644 --- a/addons/mail/static/src/less/thread.less +++ b/addons/mail/static/src/less/thread.less @@ -22,6 +22,19 @@ } } + .o_thread_new_messages_separator { + border-bottom: solid lighten(@odoo-brand-primary, 15%) 1px; + text-align: right; + .o_thread_separator_label { + position: relative; + top: 8px; + padding: 0 10px; + background: white; + color: lighten(@odoo-brand-primary, 15%); + font-size: smaller; + } + } + .o_thread_message { .o-flex-display(); padding: 4px 0; diff --git a/addons/mail/static/src/xml/client_action.xml b/addons/mail/static/src/xml/client_action.xml index aa6a9bad802..b6dcbb7e18a 100644 --- a/addons/mail/static/src/xml/client_action.xml +++ b/addons/mail/static/src/xml/client_action.xml @@ -10,11 +10,14 @@
-
+
@ Inbox +
-
+
Starred
@@ -47,8 +50,8 @@
- - + + @@ -71,10 +74,14 @@ -
+ +
# - + +
@@ -83,6 +90,16 @@
+ + + +
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml index 94719437441..7d1c28e964a 100644 --- a/addons/mail/static/src/xml/thread.xml +++ b/addons/mail/static/src/xml/thread.xml @@ -17,6 +17,7 @@ +
@@ -102,6 +103,15 @@
+ + + + + + +
+ New messages +