summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2015-11-10 12:19:38 +0100
committerGéry Debongnie <ged@odoo.com>2015-11-12 08:51:56 +0100
commit5264ccf0d0ebbfc6600ba5d3c0efc16bbe05c4d4 (patch)
tree00a61360f7c5855b37f16d9bc728e64becdd9b2c
parentdbc1061854c42578905f2aa6ecd0e77e3732c01a (diff)
[IMP] mail: needactions and messages read
Mainly concern the client action: - Display needactions per channels - For all channels except Inbox, clear needactions when opening the channel - Add snackbar to undo needactions clearing - Display a separator between read and unread messages in threads - Fix active channel (for Inbox and Starred) display in sidebar Various minor changes in chat_manager: - Code refactoring of bus notification handler - get_channel() don't fallback anymore on first channel
-rw-r--r--addons/mail/models/mail_channel.py1
-rw-r--r--addons/mail/models/mail_message.py74
-rw-r--r--addons/mail/static/src/js/chat_manager.js234
-rw-r--r--addons/mail/static/src/js/chatter.js2
-rw-r--r--addons/mail/static/src/js/client_action.js142
-rw-r--r--addons/mail/static/src/less/client_action.less11
-rw-r--r--addons/mail/static/src/less/thread.less13
-rw-r--r--addons/mail/static/src/xml/client_action.xml29
-rw-r--r--addons/mail/static/src/xml/thread.xml10
9 files changed, 352 insertions, 164 deletions
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.channel = channel;
+ 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');
+ 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);
+ }
- this.$('.o_chat_composer').toggle(channel.type !== 'static');
+ // 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');
- return this.fetch_and_render_thread().then(function () {
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 @@
</t>
<t t-name="mail.chat.Sidebar">
<div class="o_mail_chat_sidebar">
- <div class="o_mail_chat_channel_item" data-channel-id="channel_inbox">
+ <div t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_inbox') ? 'o_active': ''}"
+ data-channel-id="channel_inbox">
@ Inbox
+ <t t-set="counter" t-value="needaction_counter"/>
<t t-call="mail.chat.SidebarNeedaction"/>
</div>
- <div class="o_mail_chat_channel_item" data-channel-id="channel_starred">
+ <div t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_starred') ? 'o_active': ''}"
+ data-channel-id="channel_starred">
<span class="fa fa-star-o"/> Starred
</div>
@@ -47,8 +50,8 @@
</div>
</t>
<t t-name="mail.chat.SidebarNeedaction">
- <span t-attf-class="o_mail_sidebar_needaction badge pull-right #{(!needaction_counter ? 'hide' : '')}">
- <t t-esc="needaction_counter"/>
+ <span t-attf-class="o_mail_sidebar_needaction badge pull-right #{(!counter ? 'hide' : '')}">
+ <t t-esc="counter"/>
</span>
</t>
<t t-name="mail.chat.SidebarTitle">
@@ -71,10 +74,14 @@
</t>
<t t-name="mail.chat.SidebarItems">
<t t-foreach="channels" t-as="channel">
- <div t-if="channel.type === channel_type" class="o_mail_chat_channel_item" t-att-data-channel-id="channel.id">
+ <t t-set="counter" t-value="channel.needaction_counter"/>
+ <div t-if="channel.type === channel_type" t-att-data-channel-id="channel.id"
+ t-attf-class="o_mail_chat_channel_item #{channel.unread_counter ? ' o_unread_message' : ''} #{(active_channel_id == channel.id) ? 'o_active': ''}">
<span><i t-if="display_status" t-att-class="'o_user_status fa ' + (channel.status == 'online' ? 'fa-circle' : 'fa-circle-o')"/></span>
<span t-if="display_hash" class="o_mail_hash">#</span> <t t-esc="channel.name"/>
- <span class="fa fa-times pull-right o_mail_partner_unpin" t-att-title="_t('Close')" t-att-data-channel-id="channel.id"/>
+ <t t-call="mail.chat.SidebarNeedaction"/>
+ <span t-attf-class="fa fa-times pull-right o_mail_partner_unpin #{counter ? 'hide' : ''}"
+ t-att-title="_t('Close')" t-att-data-channel-id="channel.id"/>
</div>
</t>
<div t-if="!disable_add_channel" class="o_mail_add_channel" t-attf-data-type="#{channel_type}">
@@ -83,6 +90,16 @@
</div>
</t>
+ <t t-name="mail.chat.UndoSnackbar">
+ <div class="alert alert-dismissible o_mail_snackbar" role="alert">
+ <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+ <span aria-hidden="true">&amp;times;</span>
+ </button>
+ <t t-esc="nb_needactions"/> message(s) marked as read.
+ <a href="#" class="alert-link o_snackbar_undo">Undo</a>
+ </div>
+ </t>
+
<!-- Buttons of the Control Panel -->
<t t-name="mail.chat.ControlButtons">
<div>
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 @@
</t>
<t t-name="mail.ChatThread.Content">
+ <t t-if="options.messages_separator_position == 'top'" t-call="mail.MessagesSeparator"/>
<t t-set="current_day" t-value="0"/>
<t t-foreach="messages" t-as="message">
<div t-if="current_day != message.date.split(' ')[0]" class="o_thread_date_separator">
@@ -102,6 +103,15 @@
</t>
</div>
</div>
+ <t t-if="options.messages_separator_position == message.id">
+ <t t-call="mail.MessagesSeparator"/>
+ </t>
+ </t>
+
+ <t t-name="mail.MessagesSeparator">
+ <div class="o_thread_new_messages_separator">
+ <span class="o_thread_separator_label">New messages</span>
+ </div>
</t>
<t t-name="mail.ChatThread.MessageTracking">