[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
This commit is contained in:
Aaron Bohy
2015-11-12 08:51:56 +01:00
committed by Géry Debongnie
parent dbc1061854
commit 5264ccf0d0
9 changed files with 353 additions and 165 deletions
+1
View File
@@ -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):
+54 -20
View File
@@ -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
+149 -85
View File
@@ -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 = {
+1 -1
View File
@@ -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 () {
+92 -52
View File
@@ -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);
+10 -1
View File
@@ -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;
}
}
}
+13
View File
@@ -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;
+23 -6
View File
@@ -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>
+10
View File
@@ -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">