summaryrefslogtreecommitdiff
path: root/addons/mail/static/src
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-01-27 12:24:04 +0100
committerChristophe Simonis <chs@odoo.com>2016-01-27 12:24:04 +0100
commit78d20b552b4374b94aec7097c0e6a48a3dd2d961 (patch)
tree5acd7cbf1cc9e2f277034f44fc0ebebaa3a1a79c /addons/mail/static/src
parent740a6eadd379cc3fa26aefb9350f1c95b3e05ebd (diff)
parent7e44ddcda3f21c7e6401173539dc0fe93f9a7dd2 (diff)
[MERGE] forward port of branch saas-8 up to 7e44ddc
Diffstat (limited to 'addons/mail/static/src')
-rw-r--r--addons/mail/static/src/img/odoo_o.pngbin0 -> 556 bytes
-rw-r--r--addons/mail/static/src/js/chat_manager.js242
-rw-r--r--addons/mail/static/src/js/chat_window.js49
-rw-r--r--addons/mail/static/src/js/chatter.js25
-rw-r--r--addons/mail/static/src/js/client_action.js71
-rw-r--r--addons/mail/static/src/js/composer.js33
-rw-r--r--addons/mail/static/src/js/systray.js137
-rw-r--r--addons/mail/static/src/js/thread.js3
-rw-r--r--addons/mail/static/src/js/window_manager.js289
-rw-r--r--addons/mail/static/src/less/chat_window.less86
-rw-r--r--addons/mail/static/src/less/client_action.less140
-rw-r--r--addons/mail/static/src/less/composer.less2
-rw-r--r--addons/mail/static/src/less/thread.less13
-rw-r--r--addons/mail/static/src/xml/chat_window.xml56
-rw-r--r--addons/mail/static/src/xml/client_action.xml21
-rw-r--r--addons/mail/static/src/xml/composer.xml6
-rw-r--r--addons/mail/static/src/xml/systray.xml57
-rw-r--r--addons/mail/static/src/xml/thread.xml8
18 files changed, 1011 insertions, 227 deletions
diff --git a/addons/mail/static/src/img/odoo_o.png b/addons/mail/static/src/img/odoo_o.png
new file mode 100644
index 00000000000..d1839f6df41
--- /dev/null
+++ b/addons/mail/static/src/img/odoo_o.png
Binary files differ
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js
index 0bed20620b3..6fd85e33719 100644
--- a/addons/mail/static/src/js/chat_manager.js
+++ b/addons/mail/static/src/js/chat_manager.js
@@ -16,12 +16,15 @@ var preview_msg_max_size = 350; // optimal for native english speakers
var MessageModel = new Model('mail.message', session.context);
var ChannelModel = new Model('mail.channel', session.context);
var UserModel = new Model('res.users', session.context);
+var PartnerModel = new Model('res.partner', session.context);
// Private model
//----------------------------------------------------------------------------------
var messages = [];
var channels = [];
+var channels_preview_def;
var channel_defs = {};
+var chat_unread_counter = 0;
var emojis = [];
var emoji_substitutions = {};
var needaction_counter = 0;
@@ -50,9 +53,9 @@ bus.on("window_focus", null, function() {
function notify_incoming_message (msg, options) {
var title = _t('New message');
if (msg.author_id[1]) {
- title += _t(' from ') + _.escape(msg.author_id[1]);
+ title = _.escape(msg.author_id[1]);
}
- var content = $(msg.body).text().substr(0, preview_msg_max_size);
+ var content = parse_and_transform(msg.body, strip_html).substr(0, preview_msg_max_size);
if (bus.is_odoo_focused()) {
if (!options.is_displayed) {
@@ -65,7 +68,7 @@ function notify_incoming_message (msg, options) {
if (Notification && Notification.permission === "granted") {
if (bus.is_master) {
- new Notification(title, {body: content, icon: "/web/static/src/img/odoo.png", silent: false});
+ new Notification(title, {body: content, icon: "/mail/static/src/img/odoo_o.png", silent: false});
}
} else {
web_client.do_notify(title, content);
@@ -98,7 +101,7 @@ function add_link (node, transform_children) {
if (node.nodeType === 3) { // text node
return node.data.replace(url_regexp, function (url) {
var href = (!/^(f|ht)tps?:\/\//i.test(url)) ? "http://" + url : url;
- return '<a href="' + href + '">' + url + '</a>';
+ return '<a target="_blank" href="' + href + '">' + url + '</a>';
});
}
if (node.tagName === "A") return node.outerHTML;
@@ -106,6 +109,20 @@ function add_link (node, transform_children) {
return node.outerHTML;
}
+function strip_html (node, transform_children) {
+ if (node.nodeType === 3) return node.data; // text node
+ if (node.tagName === "BR") return "\n";
+ return transform_children();
+}
+
+function inline (node, transform_children) {
+ if (node.nodeType === 3) return node.data;
+ if (node.tagName === "BR") return " ";
+ if (node.tagName.match(/^(A|P|DIV|PRE)$/)) return transform_children();
+ node.innerHTML = transform_children();
+ return node.outerHTML;
+}
+
// Message and channel manipulation helpers
//----------------------------------------------------------------------------------
@@ -116,28 +133,29 @@ function add_message (data, options) {
if (!msg) {
msg = make_message(data);
- // Keep the array ordered by date when inserting the new message
+ // Keep the array ordered by id when inserting the new message
messages.splice(_.sortedIndex(messages, msg, 'id'), 0, msg);
_.each(msg.channel_ids, function (channel_id) {
var channel = chat_manager.get_channel(channel_id);
- var is_chat = channel && !_.contains(["public", "private"], channel.type);
if (channel) {
add_to_cache(msg, []);
if (options.domain && options.domain !== []) {
add_to_cache(msg, options.domain);
}
- if (options.increment_unread) {
- update_channel_unread_counter(channel, channel.unread_counter+1);
+ if (channel.hidden) {
+ channel.hidden = false;
+ chat_manager.bus.trigger('new_channel', channel);
+ }
+ if (!msg.author_id || msg.author_id[0] !== session.partner_id) {
+ if (options.increment_unread) {
+ update_channel_unread_counter(channel, channel.unread_counter+1);
+ }
+ if (channel.is_chat && options.show_notification) {
+ var query = {is_displayed: false};
+ chat_manager.bus.trigger('anyone_listening', channel, query);
+ notify_incoming_message(msg, query);
+ }
}
- }
- if (channel && channel.hidden) {
- channel.hidden = false;
- chat_manager.bus.trigger('new_channel', channel);
- }
- if (is_chat && options.show_notification && (!msg.author_id || msg.author_id[0] !== session.partner_id)) {
- var query = {is_displayed: false};
- chat_manager.bus.trigger('anyone_listening', channel, query);
- notify_incoming_message(msg, query);
}
});
if (!options.silent) {
@@ -289,7 +307,7 @@ function make_channel (data, options) {
display_needactions: options.display_needactions,
mass_mailing: data.mass_mailing,
needaction_counter: data.message_needaction_counter || 0,
- unread_counter: data.message_unread_counter || 0,
+ unread_counter: 0,
last_seen_message_id: data.seen_message_id,
cache: {'[]': {
all_history_loaded: false,
@@ -309,6 +327,12 @@ function make_channel (data, options) {
channel.status = data.direct_partner[0].im_status;
pinned_dm_partners.push(channel.direct_partner_id);
bus.update_option('bus_presence_partner_ids', pinned_dm_partners);
+ } else if ('anonymous_name' in data) {
+ channel.name = data.anonymous_name;
+ }
+ channel.is_chat = !channel.type.match(/^(public|private|static)$/);
+ if (data.message_unread_counter) {
+ update_channel_unread_counter(channel, data.message_unread_counter);
}
return channel;
}
@@ -425,37 +449,56 @@ function fetch_document_messages (ids, options) {
}
function update_channel_unread_counter (channel, counter) {
+ if (channel.is_chat) {
+ chat_unread_counter = chat_unread_counter - channel.unread_counter + counter;
+ }
channel.unread_counter = counter;
chat_manager.bus.trigger("update_channel_unread_counter", channel);
}
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;
- });
+ return ChannelModel.call('channel_seen', [[channel.id]], {}, {shadow: true});
}, 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]);
- } else if (model === 'bus.presence') {
- // update presence of users
- on_presence_notification(notification[1]);
+function on_notification (notifications) {
+ // sometimes, the web client receives unsubscribe notification and an extra
+ // notification on that channel. This is then followed by an attempt to
+ // rejoin the channel that we just left. The next few lines remove the
+ // extra notification to prevent that situation to occur.
+ var unsubscribed_notif = _.find(notifications, function (notif) {
+ return notif[1].info === "unsubscribe";
+ });
+ if (unsubscribed_notif) {
+ notifications = _.reject(notifications, function (notif) {
+ return notif[0][1] === "mail.channel" && notif[0][2] === unsubscribed_notif[1].id;
+ });
}
+ _.each(notifications, function (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]);
+ } else if (model === 'bus.presence') {
+ // update presence of users
+ on_presence_notification(notification[1]);
+ }
+ });
}
function on_needaction_notification (message) {
- message = add_message(message, { channel_id: 'channel_inbox', show_notification: true} );
+ message = add_message(message, {
+ channel_id: 'channel_inbox',
+ show_notification: true,
+ increment_unread: true,
+ });
invalidate_caches(message.channel_ids);
needaction_counter++;
_.each(message.channel_ids, function (channel_id) {
@@ -490,6 +533,8 @@ function on_partner_notification (data) {
on_mark_as_read_notification(data);
} else if (data.type === 'mark_as_unread') {
on_mark_as_unread_notification(data);
+ } else if (data.info === 'channel_seen') {
+ on_channel_seen_notification(data);
} else {
on_chat_session_notification(data);
}
@@ -526,7 +571,7 @@ function on_mark_as_read_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;
+ channel.needaction_counter = Math.max(channel.needaction_counter - data.message_ids.length, 0);
}
});
} else { // if no channel_ids specified, this is a 'mark all read' in the inbox
@@ -534,7 +579,7 @@ function on_mark_as_read_notification (data) {
channel.needaction_counter = 0;
});
}
- needaction_counter -= data.message_ids.length;
+ needaction_counter = Math.max(needaction_counter - data.message_ids.length, 0);
chat_manager.bus.trigger('update_needaction', needaction_counter);
}
@@ -560,6 +605,16 @@ function on_mark_as_unread_notification (data) {
chat_manager.bus.trigger('update_needaction', needaction_counter);
}
+function on_channel_seen_notification (data) {
+ var channel = chat_manager.get_channel(data.id);
+ if (channel) {
+ channel.last_seen_message_id = data.last_message_id;
+ if (channel.unread_counter) {
+ update_channel_unread_counter(channel, 0);
+ }
+ }
+}
+
function on_chat_session_notification (chat_session) {
var channel;
if ((chat_session.channel_type === "channel") && (chat_session.state === "open")) {
@@ -572,18 +627,21 @@ function on_chat_session_notification (chat_session) {
if ((chat_session.state === "open") || (chat_session.state === "folded")) {
channel = chat_session.is_minimized && chat_manager.get_channel(chat_session.id);
if (channel) {
+ channel.is_detached = true;
+ channel.is_folded = (chat_session.state === "folded");
chat_manager.bus.trigger("open_chat", channel);
}
} else if (chat_session.state === "closed") {
channel = chat_manager.get_channel(chat_session.id);
if (channel) {
+ channel.is_detached = false;
chat_manager.bus.trigger("close_chat", channel);
}
}
}
function on_presence_notification (data) {
- var dm = _.findWhere(channels, {direct_partner_id: data.id});
+ var dm = chat_manager.get_dm_from_partner_id(data.id);
if (dm) {
dm.status = data.im_status;
chat_manager.bus.trigger('update_dm_presence', dm);
@@ -696,7 +754,7 @@ var chat_manager = {
return MessageModel.call('mark_as_unread', [message_ids, [channel.id]]);
},
mark_channel_as_seen: function (channel) {
- if (channel.unread_counter > 0) {
+ if (channel.unread_counter > 0 && channel.type !== 'static') {
update_channel_unread_counter(channel, 0);
channel_seen(channel);
}
@@ -710,6 +768,10 @@ var chat_manager = {
return _.findWhere(channels, {id: id});
},
+ get_dm_from_partner_id: function (partner_id) {
+ return _.findWhere(channels, {direct_partner_id: partner_id});
+ },
+
all_history_loaded: function (channel, domain) {
return get_channel_cache(channel, domain).all_history_loaded;
},
@@ -720,7 +782,7 @@ var chat_manager = {
}
if (!channel.members_deferred) {
channel.members_deferred = ChannelModel
- .call("channel_fetch_listeners", [channel.uuid])
+ .call("channel_fetch_listeners", [channel.uuid], {}, {shadow: true})
.then(function (members) {
var suggestions = [];
_.each(mention_partner_suggestions, function (partners) {
@@ -729,7 +791,7 @@ var chat_manager = {
}));
});
- return [members].concat(suggestions);
+ return [members];
});
}
return channel.members_deferred;
@@ -742,13 +804,31 @@ var chat_manager = {
get_needaction_counter: function () {
return needaction_counter;
},
+ get_chat_unread_counter: function () {
+ return chat_unread_counter;
+ },
+
+ get_last_seen_message: function (channel) {
+ if (channel.last_seen_message_id) {
+ var messages = channel.cache['[]'].messages;
+ var msg = _.findWhere(messages, {id: channel.last_seen_message_id});
+ if (msg) {
+ var i = _.sortedIndex(messages, msg, 'id') + 1;
+ while (i < messages.length && messages[i].author_id && messages[i].author_id[0] === session.partner_id) {
+ msg = messages[i];
+ i++;
+ }
+ return msg;
+ }
+ }
+ },
get_discuss_ids: function () {
return discuss_ids;
},
detach_channel: function (channel) {
- return ChannelModel.call("channel_minimize", [channel.uuid, true]);
+ return ChannelModel.call("channel_minimize", [channel.uuid, true], {}, {shadow: true});
},
remove_chatter_messages: function (model) {
messages = _.reject(messages, function (message) {
@@ -783,6 +863,9 @@ var chat_manager = {
}
return channel_defs[channel_id];
},
+ open_and_detach_dm: function (partner_id) {
+ return ChannelModel.call('channel_get_and_minimize', [[partner_id]]).then(add_channel);
+ },
unsubscribe: function (channel) {
var def;
@@ -797,13 +880,16 @@ var chat_manager = {
},
close_chat_session: function (channel_id) {
var channel = this.get_channel(channel_id);
- ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"});
+ ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"}, {shadow: true});
},
- fold_channel: function (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;
- });
+ fold_channel: function (channel_id, folded) {
+ var args = {
+ uuid: this.get_channel(channel_id).uuid,
+ };
+ if (_.isBoolean(folded)) {
+ args.state = folded ? 'folded' : 'open';
+ }
+ return ChannelModel.call("channel_fold", [], args, {shadow: true});
},
/**
* Special redirection handling for given model and id
@@ -839,6 +925,58 @@ var chat_manager = {
});
}
},
+
+ get_channels_preview: function (channels) {
+ var channels_preview = _.map(channels, function (channel) {
+ var info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
+ info.last_message = _.last(channel.cache['[]'].messages);
+ if (!info.is_chat) {
+ info.image_src = '/web/image/mail.channel/'+channel.id+'/image_small';
+ } else if (channel.direct_partner_id) {
+ info.image_src = '/web/image/res.partner/'+channel.direct_partner_id+'/image_small';
+ } else {
+ info.image_src = '/mail/static/src/img/smiley/avatar.jpg';
+ }
+ return info;
+ });
+ if (!channels_preview_def) {
+ var missing_channel_ids = _.pluck(_.where(channels_preview, {last_message: undefined}), 'id');
+ if (missing_channel_ids.length) {
+ channels_preview_def = ChannelModel
+ .call('channel_fetch_preview', [missing_channel_ids], {}, {shadow: true})
+ .then(function (channels) {
+ _.each(channels, function (channel) {
+ var msg = add_message(channel.last_message);
+ _.findWhere(channels_preview, {id: channel.id}).last_message = msg;
+ });
+ });
+ } else {
+ channels_preview_def = $.when();
+ }
+ }
+ return channels_preview_def.then(function () {
+ return _.filter(channels_preview, function (channel) {
+ return channel.last_message; // remove empty channels
+ });
+ });
+ },
+ get_message_body_preview: function (message_body) {
+ return parse_and_transform(message_body, inline);
+ },
+
+ search_partner: function (search_val, limit) {
+ return PartnerModel.call('im_search', [search_val, limit || 20], {}, {shadow: true}).then(function(result) {
+ var values = [];
+ _.each(result, function(user) {
+ var escaped_name = _.escape(user.name);
+ values.push(_.extend(user, {
+ 'value': escaped_name,
+ 'label': escaped_name,
+ }));
+ });
+ return values;
+ });
+ },
};
// Initialization
@@ -864,13 +1002,13 @@ function init () {
mention_partner_suggestions = result.mention_partner_suggestions;
emojis = result.emoji;
_.each(emojis, function(emoji) {
- emoji_substitutions[emoji.source] = emoji.substitution;
+ emoji_substitutions[_.escape(emoji.source)] = emoji.substitution;
});
});
var ir_model = new Model("ir.model.data");
- 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"]);
+ var load_menu_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"], {}, {shadow: true});
+ var load_action_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"], {}, {shadow: true});
bus.on('notification', null, on_notification);
diff --git a/addons/mail/static/src/js/chat_window.js b/addons/mail/static/src/js/chat_window.js
index ca36b430791..95ac9dd8897 100644
--- a/addons/mail/static/src/js/chat_window.js
+++ b/addons/mail/static/src/js/chat_window.js
@@ -3,6 +3,7 @@ odoo.define('mail.ChatWindow', function (require) {
var ChatThread = require('mail.ChatThread');
+var config = require('web.config');
var core = require('web.core');
var Widget = require('web.Widget');
@@ -23,15 +24,15 @@ return Widget.extend({
this._super(parent);
this.title = title;
this.channel_id = channel_id;
- this.placeholder = _t("Say something");
this.folded = is_folded;
this.options = _.defaults(options || {}, {
display_stars: true,
+ placeholder: _t("Say something"),
});
this.unread_msgs = unread_msgs || 0;
+ this.is_hidden = false;
},
start: function () {
- this.$content = this.$('.o_chat_content');
this.$input = this.$('.o_chat_input input');
this.thread = new ChatThread(this, {
@@ -45,28 +46,21 @@ return Widget.extend({
if (this.folded) {
this.$el.css('height', HEIGHT_FOLDED);
+ } else {
+ this.focus_input();
}
- var def = this.thread.appendTo(this.$content);
+ var def = this.thread.replace(this.$('.o_chat_content'));
return $.when(this._super(), def);
},
render: function (messages) {
- this.update_header();
+ this.update_unread(this.unread_msgs);
this.thread.render(messages, {display_load_more: false});
},
- update_header: function () {
- var title = this.unread_msgs > 0 ?
- this.title + ' (' + this.unread_msgs + ')' : this.title;
- this.$('.o_chat_title').text(title);
- },
update_unread: function (counter) {
this.unread_msgs = counter;
- this.update_header();
- },
- scrollBottom: function () {
- this.$content.scrollTop(this.$content[0].scrollHeight);
+ this.$('.o_unread_counter').text(counter > 0 ? '(' + counter + ')' : '');
},
fold: function () {
- this.update_header();
this.$el.animate({
height: this.folded ? HEIGHT_FOLDED : HEIGHT_OPEN
});
@@ -74,11 +68,28 @@ return Widget.extend({
toggle_fold: function (fold) {
this.folded = _.isBoolean(fold) ? fold : !this.folded;
if (!this.folded) {
- this.unread_msgs = 0;
- this.trigger('messages_read');
+ this.thread.scroll_to();
+ this.focus_input();
}
this.fold();
},
+ focus_input: function () {
+ if (!config.device.touch) {
+ this.$input.focus();
+ }
+ },
+ do_show: function () {
+ this.is_hidden = false;
+ this._super.apply(this, arguments);
+ },
+ do_hide: function () {
+ this.is_hidden = true;
+ this._super.apply(this, arguments);
+ },
+ do_toggle: function (display) {
+ this.is_hidden = _.isBoolean(display) ? !display : !this.is_hidden;
+ this._super.apply(this, arguments);
+ },
on_keydown: function (event) {
// ENTER key (avoid requiring jquery ui for external livechat)
if (event.which === 13) {
@@ -100,8 +111,10 @@ return Widget.extend({
this.trigger("close_chat_session");
},
on_click_fold: function () {
- this.trigger("fold_channel", this.channel_id);
- this.toggle_fold();
+ if (config.device.size_class !== config.device.SIZES.XS) {
+ this.toggle_fold();
+ this.trigger("fold_channel", this.channel_id, this.folded);
+ }
},
});
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index 28a269379df..dc528b2b1aa 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -451,6 +451,7 @@ var ChatterComposer = composer.BasicComposer.extend({
record_name: false,
is_log: false,
internal_subtypes: [],
+ default_body: '',
});
if (this.options.is_log) {
this.options.send_text = _('Log');
@@ -467,6 +468,17 @@ var ChatterComposer = composer.BasicComposer.extend({
return $.when(this._super.apply(this, arguments), this.message_get_suggested_recipients());
},
+ start: function () {
+ var self = this;
+ return this._super().then(function () {
+ self.$input.val(self.options.default_body);
+ });
+ },
+
+ should_send: function () {
+ return false;
+ },
+
preprocess_message: function () {
var self = this;
var def = $.Deferred();
@@ -862,6 +874,8 @@ var Chatter = form_common.AbstractField.extend({
// destroy current composer, if any
if (this.composer) {
this.composer.destroy();
+ this.composer = undefined;
+ this.mute_new_message_button(false);
}
// fetch and render messages of current document
@@ -891,6 +905,7 @@ var Chatter = form_common.AbstractField.extend({
internal_subtypes: this.options.internal_subtypes,
is_log: options && options.is_log,
record_name: this.record_name,
+ default_body: old_composer && old_composer.$input.val(),
});
this.composer.on('input_focused', this, function () {
this.composer.mention_set_prefetched_partners(this.mention_suggestions || []);
@@ -906,11 +921,19 @@ var Chatter = form_common.AbstractField.extend({
self.composer.on('post_message', self, self.on_post_message);
self.composer.on('need_refresh', self, self.refresh_followers);
});
-
+ this.mute_new_message_button(true);
},
close_composer: function () {
if (this.composer.is_empty()) {
this.composer.do_hide();
+ this.mute_new_message_button(false);
+ }
+ },
+ mute_new_message_button: function (mute) {
+ if (mute) {
+ this.$('.o_chatter_button_new_message').removeClass('btn-primary').addClass('btn-default');
+ } else if (!mute) {
+ this.$('.o_chatter_button_new_message').removeClass('btn-default').addClass('btn-primary');
}
},
diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js
index 9856e8f3e75..d6a5b5ac128 100644
--- a/addons/mail/static/src/js/client_action.js
+++ b/addons/mail/static/src/js/client_action.js
@@ -182,8 +182,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
this.searchview = new SearchView(this, dataset, view_id, {}, options);
this.searchview.on('search_data', this, this.on_search);
- this.basic_composer = new composer.BasicComposer(this);
- this.extended_composer = new composer.ExtendedComposer(this);
+ this.basic_composer = new composer.BasicComposer(this, {mention_partners_restricted: true});
+ this.extended_composer = new composer.ExtendedComposer(this, {mention_partners_restricted: true});
this.thread = new ChatThread(this, {
display_help: true,
shorten_messages: false,
@@ -202,6 +202,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
chat_manager.mark_all_as_read();
});
this.$buttons.on('click', '.o_mail_chat_button_unstar_all', chat_manager.unstar_all);
+ this.$buttons.on('click', '.o_mail_chat_button_new_message', this.on_click_new_message);
this.thread.on('redirect', this, function (res_model, res_id) {
chat_manager.redirect(res_model, res_id, this.set_channel.bind(this));
@@ -224,7 +225,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
var def1 = this.thread.appendTo(this.$('.o_mail_chat_content'));
var def2 = this.basic_composer.appendTo(this.$('.o_mail_chat_content'));
var def3 = this.extended_composer.appendTo(this.$('.o_mail_chat_content'));
- var def4 = this.searchview.appendTo($("<div>"));
+ var def4 = this.searchview.appendTo($("<div>")).then(function () {
+ self.$searchview_buttons = self.searchview.$buttons.contents();
+ });
this.render_sidebar();
@@ -235,12 +238,17 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
chat_manager.bus.on('update_message', self, self.on_update_message);
chat_manager.bus.on('new_channel', self, self.on_new_channel);
chat_manager.bus.on('anyone_listening', self, function (channel, query) {
- query.is_displayed = query.is_displayed || channel.id === self.channel.id;
+ query.is_displayed = query.is_displayed || (channel.id === self.channel.id && self.thread.is_at_bottom());
});
chat_manager.bus.on('unsubscribe_from_channel', self, self.render_sidebar);
chat_manager.bus.on('update_needaction', self, self.throttled_render_sidebar);
chat_manager.bus.on('update_channel_unread_counter', self, self.throttled_render_sidebar);
chat_manager.bus.on('update_dm_presence', self, self.throttled_render_sidebar);
+ self.thread.$el.on("scroll", null, _.debounce(function () {
+ if (self.thread.is_at_bottom()) {
+ chat_manager.mark_channel_as_seen(self.channel);
+ }
+ }, 100));
});
},
@@ -282,9 +290,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
this.$('.o_mail_add_channel[data-type=dm]').find("input").autocomplete({
source: function(request, response) {
self.last_search_val = _.escape(request.term);
- self.do_search_partner(self.last_search_val).done(function(result){
- response(result);
- });
+ chat_manager.search_partner(self.last_search_val).done(response);
},
select: function(event, ui) {
var partner_id = ui.item.id;
@@ -329,21 +335,6 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
});
},
- do_search_partner: function (search_val) {
- var Partner = new Model("res.partner");
- return Partner.call('im_search', [search_val, 20]).then(function(result){
- var values = [];
- _.each(result, function(user){
- var escaped_name = _.escape(user.name);
- values.push(_.extend(user, {
- 'value': escaped_name,
- 'label': escaped_name,
- }));
- });
- return values;
- });
- },
-
set_channel: function (channel) {
var self = this;
// Store scroll position of previous channel
@@ -380,13 +371,13 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
// Hide 'detach' button in static channels
self.$buttons
.find('.o_mail_chat_button_detach')
- .toggle(channel.type !== "static");
+ .toggle(channel.type !== "static" && !channel.mass_mailing);
// 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')
+ .find('.o_mail_chat_button_mark_read, .o_mail_chat_button_new_message')
.toggle(channel.id === "channel_inbox");
self.$buttons
.find('.o_mail_chat_button_unstar_all')
@@ -443,13 +434,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
// 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
+ var msg = chat_manager.get_last_seen_message(this.channel);
+ this.messages_separator_position = msg ? msg.id : 'top';
}
}
return {
@@ -508,7 +497,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
cp_content: {
$buttons: this.$buttons,
$searchview: this.searchview.$el,
- $searchview_buttons: this.searchview.$buttons.contents(),
+ $searchview_buttons: this.$searchview_buttons,
},
searchview: this.searchview,
});
@@ -533,9 +522,13 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
},
on_post_message: function (message) {
+ var self = this;
var options = {channel_id: this.channel.id};
chat_manager
.post_message(message, options)
+ .then(function() {
+ self.thread.scroll_to();
+ })
.fail(function () {
// todo: display notification
});
@@ -543,11 +536,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') {
+ if (this.channel.type !== 'static' && this.thread.is_at_bottom()) {
chat_manager.mark_channel_as_seen(this.channel);
}
+
+ var should_scroll = this.thread.is_at_bottom();
this.fetch_and_render_thread().then(function () {
- self.thread.scroll_to({id: message.id});
+ if (should_scroll) {
+ self.thread.scroll_to({id: message.id});
+ }
});
}
// Re-render sidebar to indicate that there is a new message in the corresponding channels
@@ -583,7 +580,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
},
on_click_button_invite: function () {
- var title = _.str.sprintf(_t('Invite people to %s'), this.channel.name);
+ var title = _.str.sprintf(_t('Invite people to #%s'), this.channel.name);
new PartnerInviteDialog(this, title, this.channel.id).open();
},
on_click_button_detach: function () {
@@ -602,6 +599,16 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
target: 'current'
});
},
+ on_click_new_message: function () {
+ this.do_action({
+ type: 'ir.actions.act_window',
+ res_model: 'mail.compose.message',
+ view_mode: 'form',
+ view_type: 'form',
+ views: [[false, 'form']],
+ target: 'new',
+ });
+ },
});
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index a02a3966333..5d6de2bad50 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -315,7 +315,7 @@ var BasicComposer = Widget.extend({
template: "mail.ChatComposer",
events: {
- "keydown .o_composer_input": "on_keydown",
+ "keydown .o_composer_input textarea": "on_keydown",
"keyup .o_composer_input": "on_keyup",
"change input.o_form_input_file": "on_attachment_change",
"click .o_composer_button_send": "send_message",
@@ -331,6 +331,7 @@ var BasicComposer = Widget.extend({
input_max_height: 150,
input_min_height: 28,
mention_fetch_limit: 8,
+ mention_partners_restricted: false, // set to true to only suggest prefetched partners
send_text: _('Send'),
});
this.context = this.options.context;
@@ -354,6 +355,10 @@ var BasicComposer = Widget.extend({
model: 'mail.channel',
redirect_classname: 'o_channel_redirect',
});
+
+ // Emojis
+ this.emoji_container_classname = 'o_composer_emoji';
+
this.PartnerModel = new Model('res.partner');
this.ChannelModel = new Model('mail.channel');
},
@@ -363,7 +368,7 @@ var BasicComposer = Widget.extend({
this.$attachment_button = this.$(".o_composer_button_add_attachment");
this.$attachments_list = this.$('.o_composer_attachments_list');
- this.$input = this.$('.o_composer_input');
+ this.$input = this.$('.o_composer_input textarea');
this.$input.focus(function () {
self.trigger('input_focused');
});
@@ -386,7 +391,7 @@ var BasicComposer = Widget.extend({
return self.$emojis;
},
html: true,
- container: '.o_composer_emoji',
+ container: '.' + self.emoji_container_classname,
trigger: 'focus',
});
@@ -443,8 +448,8 @@ var BasicComposer = Widget.extend({
/**
* Send the message on ENTER, but go to new line on SHIFT+ENTER
*/
- prevent_send: function (event) {
- return event.shiftKey;
+ should_send: function (event) {
+ return !event.shiftKey;
},
on_keydown: function (event) {
@@ -465,9 +470,12 @@ var BasicComposer = Widget.extend({
case $.ui.keyCode.ENTER:
if (this.mention_manager.is_open()) {
event.preventDefault();
- } else if (!this.prevent_send(event)) {
- event.preventDefault();
- this.send_message();
+ } else {
+ var send_message = event.ctrlKey || this.should_send(event);
+ if (send_message) {
+ event.preventDefault();
+ this.send_message();
+ }
}
break;
}
@@ -613,7 +621,7 @@ var BasicComposer = Widget.extend({
}
}
});
- if (!suggestions.length) {
+ if (!suggestions.length && !self.options.mention_partners_restricted) {
// no result found among prefetched partners, fetch other suggestions
var kwargs = {
limit: limit,
@@ -648,8 +656,9 @@ var ExtendedComposer = BasicComposer.extend({
options = _.defaults(options || {}, {
input_min_height: 120,
});
+ this._super(parent, options);
this.extended = true;
- return this._super(parent, options);
+ this.emoji_container_classname = 'o_extended_composer_emoji';
},
start: function () {
@@ -667,8 +676,8 @@ var ExtendedComposer = BasicComposer.extend({
});
},
- prevent_send: function () {
- return true;
+ should_send: function () {
+ return false;
},
});
diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js
index 19e225c364f..21d22a12eac 100644
--- a/addons/mail/static/src/js/systray.js
+++ b/addons/mail/static/src/js/systray.js
@@ -3,71 +3,128 @@ odoo.define('mail.systray', function (require) {
var core = require('web.core');
var SystrayMenu = require('web.SystrayMenu');
-var web_client = require('web.web_client');
var Widget = require('web.Widget');
var chat_manager = require('mail.chat_manager');
+var QWeb = core.qweb;
+
/**
- * Widget Top Menu Notification Counter
+ * Menu item appended in the systray part of the navbar
*
- * Counter of notification in the Systray Menu. Need to know if InstantMessagingView is displayed to
- * increment (or not) the counter. On click, should redirect to the client action.
+ * The menu item indicates the counter of needactions + unread messages in chat channels. When
+ * clicking on it, it toggles a dropdown containing a preview of each pinned channels (except
+ * static and mass mailing channels) with a quick link to open them in chat windows. It also
+ * contains a direct link to the Inbox in Discuss.
**/
-var NotificationTopButton = Widget.extend({
- template:'mail.chat.NotificationTopButton',
+var MessagingMenu = Widget.extend({
+ template:'mail.chat.MessagingMenu',
events: {
"click": "on_click",
+ "click .o_filter_button": "on_click_filter_button",
+ "click .o_new_message": "on_click_new_message",
+ "click .o_mail_channel_preview": "on_click_channel",
+ "click .o_redirect_to_discuss": "on_click_redirect_to_discuss",
},
start: function () {
+ this.$filter_buttons = this.$('.o_filter_button');
+ this.$channels_preview = this.$('.o_mail_navbar_dropdown_channels');
+ this.filter = false;
chat_manager.bus.on("update_needaction", this, this.update_counter);
- this.update_counter(chat_manager.get_needaction_counter());
+ chat_manager.bus.on("update_channel_unread_counter", this, this.update_counter);
+ this.update_counter();
return this._super();
},
- update_counter: function (counter) {
- this.$('.o_notification_counter').html(counter);
+ is_open: function () {
+ return this.$el.hasClass('open');
+ },
+ update_counter: function () {
+ var counter = chat_manager.get_needaction_counter() + chat_manager.get_chat_unread_counter();
+ this.$('.o_notification_counter').text(counter);
+ this.$el.toggleClass('o_no_notification', !counter);
+ if (this.is_open()) {
+ this.render_channels_preview();
+ }
+ },
+ render_channels_preview: function () {
+ var self = this;
+ var channels = _.filter(chat_manager.get_channels(), function (channel) {
+ if (self.filter === 'chat') {
+ return channel.is_chat;
+ } else if (self.filter === 'channels') {
+ return !channel.is_chat && channel.type !== 'static' && !channel.mass_mailing;
+ } else {
+ return channel.type !== 'static' && !channel.mass_mailing;
+ }
+ });
+
+ // Update inbox counter and display spinner while waiting for channels preview
+ var inbox_counter = chat_manager.get_needaction_counter();
+ this.$('.o_inbox_counter').html(inbox_counter ? '(' + inbox_counter + ')' : '');
+ this.$channels_preview.html(QWeb.render('mail.chat.Spinner'));
+
+ chat_manager.get_channels_preview(channels).then(function (channels_preview) {
+ // Sort channels: 1. channels with unread messages, 2. chat, 3. by date of last msg
+ channels_preview.sort(function (c1, c2) {
+ return Math.min(1, c2.unread_counter) - Math.min(1, c1.unread_counter) ||
+ c2.is_chat - c1.is_chat ||
+ c2.last_message.date.diff(c1.last_message.date);
+ });
+
+ // Generate last message preview (inline message body and compute date to display)
+ _.each(channels_preview, function (channel) {
+ channel.last_message_preview =
+ channel.last_message.displayed_author + ': ' +
+ chat_manager.get_message_body_preview(channel.last_message.body);
+ if (channel.last_message.date.isSame(new Date(), 'd')) { // today
+ channel.last_message_date = channel.last_message.date.format('LT');
+ } else {
+ channel.last_message_date = channel.last_message.date.format('lll');
+ }
+ });
+
+ self.$channels_preview.html(QWeb.render('mail.chat.ChannelsPreview', {
+ channels: channels_preview,
+ }));
+ });
+ },
+ on_click: function () {
+ if (!this.is_open()) {
+ this.render_channels_preview(); // we are opening the dropdown so render its content
+ }
},
- on_click: function (event) {
+ on_click_filter_button: function (event) {
+ event.stopPropagation();
+ this.$filter_buttons.removeClass('o_selected');
+ var $target = $(event.currentTarget);
+ $target.addClass('o_selected');
+ this.filter = $target.data('filter');
+ this.render_channels_preview();
+ },
+ on_click_new_message: function () {
+ chat_manager.bus.trigger('open_chat');
+ },
+ on_click_channel: function (event) {
+ var channel_id = $(event.currentTarget).data('channel_id');
+ var channel = chat_manager.get_channel(channel_id);
+ if (channel) {
+ chat_manager.bus.trigger('detach_channel', channel);
+ }
+ },
+ on_click_redirect_to_discuss: function (event) {
event.preventDefault();
this.discuss_redirect();
},
discuss_redirect: _.debounce(function () {
+ var self = this;
var discuss_ids = chat_manager.get_discuss_ids();
this.do_action(discuss_ids.action_id, {clear_breadcrumbs: true}).then(function () {
+ self.trigger_up('hide_app_switcher');
core.bus.trigger('change_menu_section', discuss_ids.menu_id);
});
}, 1000, true),
});
-SystrayMenu.Items.push(NotificationTopButton);
-
-
-
-/**
- * * Global ComposeMessage Top Button
- * *
- * * Add a link on the top user bar to write a full mail. It opens the form view
- * * of the mail.compose.message (in a modal).
- * */
-var ComposeMessageTopButton = Widget.extend({
- template:'mail.ComposeMessageTopButton',
- events: {
- "click": "on_compose_message",
- },
- on_compose_message: function (ev) {
- ev.preventDefault();
- web_client.action_manager.do_action({
- type: 'ir.actions.act_window',
- res_model: 'mail.compose.message',
- view_mode: 'form',
- view_type: 'form',
- views: [[false, 'form']],
- target: 'new',
- });
- },
-});
-
-// Put the ComposeMessageTopButton widget in the systray menu
-SystrayMenu.Items.push(ComposeMessageTopButton);
+SystrayMenu.Items.push(MessagingMenu);
});
diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js
index 99b4d6ba1c2..439b1a8beb3 100644
--- a/addons/mail/static/src/js/thread.js
+++ b/addons/mail/static/src/js/thread.js
@@ -165,6 +165,9 @@ var Thread = Widget.extend({
get_scrolltop: function () {
return this.$el.scrollTop();
},
+ is_at_bottom: function () {
+ return this.el.scrollHeight - this.$el.scrollTop() - this.$el.outerHeight() < 5;
+ },
});
Thread.ORDER = ORDER;
diff --git a/addons/mail/static/src/js/window_manager.js b/addons/mail/static/src/js/window_manager.js
index f217d929c20..65488129884 100644
--- a/addons/mail/static/src/js/window_manager.js
+++ b/addons/mail/static/src/js/window_manager.js
@@ -2,29 +2,49 @@ odoo.define('mail.window_manager', function (require) {
"use strict";
var chat_manager = require('mail.chat_manager');
-var ChatWindow = require('mail.ChatWindow');
+var ExtendedChatWindow = require('mail.ExtendedChatWindow');
+var config = require('web.config');
var core = require('web.core');
+var utils = require('web.utils');
var web_client = require('web.web_client');
+var _t = core._t;
+var QWeb = core.qweb;
// chat window management
//----------------------------------------------------------------
+var CHAT_WINDOW_WIDTH = 300 + 5; // 5 pixels between windows
var chat_sessions = [];
-var CHAT_WINDOW_WIDTH = 300;
+var new_chat_session;
+var display_state = {
+ hidden_sessions: [],
+ hidden_unread_counter: 0, // total number of unread msgs in hidden chat windows
+ nb_slots: 0,
+ space_left: 0,
+ windows_dropdown_is_open: false, // used to keep dropdown open when closing chat windows
+};
+
+function add_chat_session (chat_session) {
+ // adds chat_session such that it will be the left-most visible window
+ compute_available_slots(chat_sessions.length+1);
+ chat_sessions.splice(display_state.nb_slots-1, 0, chat_session);
+}
function open_chat (session) {
- if (!_.findWhere(chat_sessions, {id: session.id})) {
- var channel = chat_manager.get_channel(session.id);
- var chat_session = {
+ if (!session) {
+ open_chat_without_session();
+ return;
+ }
+ var chat_session = _.findWhere(chat_sessions, {id: session.id});
+ if (!chat_session) {
+ var prefix = !session.is_chat ? "#" : "";
+ chat_session = {
id: session.id,
uuid: session.uuid,
name: session.name,
- window: new ChatWindow(web_client, session.id, session.name, session.is_folded, channel.unread_counter),
+ window: new ExtendedChatWindow(web_client, session.id, prefix + session.name, session.is_folded, session.unread_counter),
};
- if (!session.is_folded) {
- chat_manager.mark_channel_as_seen(channel);
- }
chat_session.window.on("close_chat_session", null, function () {
close_chat(chat_session);
chat_manager.close_chat_session(chat_session.id);
@@ -33,16 +53,20 @@ function open_chat (session) {
chat_manager.toggle_star_status(message_id);
});
- chat_session.window.on("fold_channel", null, function (channel_id) {
- chat_manager.fold_channel(channel_id);
+ chat_session.window.on("fold_channel", null, function (channel_id, folded) {
+ chat_manager.fold_channel(channel_id, folded);
});
chat_session.window.on("post_message", null, function (message, channel_id) {
message.content = _.escape(message.content);
- chat_manager.post_message(message, {channel_id: channel_id});
+ chat_manager
+ .post_message(message, {channel_id: channel_id})
+ .then(function () {
+ chat_session.window.thread.scroll_to();
+ });
});
chat_session.window.on("messages_read", null, function () {
- chat_manager.mark_channel_as_seen(channel);
+ chat_manager.mark_channel_as_seen(session);
});
chat_session.window.on("redirect", null, function (res_model, res_id) {
chat_manager.redirect(res_model, res_id, open_chat);
@@ -58,50 +82,198 @@ function open_chat (session) {
}
});
- chat_sessions.push(chat_session);
+ var remove_new_chat = false;
+ if (new_chat_session && new_chat_session.partner_id && new_chat_session.partner_id === session.direct_partner_id) {
+ chat_sessions[_.indexOf(chat_sessions, new_chat_session)] = chat_session;
+ remove_new_chat = true;
+ } else {
+ add_chat_session(chat_session);
+ }
+
chat_session.window.appendTo($('body'))
- .then(reposition_windows)
.then(function () {
+ reposition_windows({remove_new_chat: remove_new_chat});
return chat_manager.get_messages({channel_id: chat_session.id});
}).then(function (messages) {
chat_session.window.render(messages);
- chat_session.window.scrollBottom();
+ chat_session.window.thread.scroll_to();
+ setTimeout(function () {
+ chat_session.window.thread.$el.on("scroll", null, _.debounce(function () {
+ if (chat_session.window.thread.is_at_bottom()) {
+ chat_manager.mark_channel_as_seen(session);
+ }
+ }, 100));
+ }, 0); // setTimeout to prevent to execute handler on first scroll_to, which is asynchronous
+ if (!session.is_folded) {
+ chat_manager.mark_channel_as_seen(session);
+ }
});
+ } else {
+ if (chat_session.window.is_hidden) {
+ make_session_visible(chat_session);
+ } else if (session.is_folded !== chat_session.window.folded) {
+ chat_session.window.toggle_fold(session.is_folded);
+ }
}
}
-function close_chat (chat_session) {
- var session = _.find(chat_sessions, {id: chat_session.id});
- if (session) {
- chat_sessions = _.without(chat_sessions, session);
- session.window.destroy();
- reposition_windows();
+function open_chat_without_session () {
+ if (!new_chat_session) {
+ new_chat_session = {
+ id: '_open',
+ window: new ExtendedChatWindow(web_client, undefined, _t('New message'), false, 0, {thread_less: true}),
+ };
+ new_chat_session.window.on("close_chat_session", null, close_new_chat);
+ new_chat_session.window.on('open_dm_session', null, function (partner_id) {
+ new_chat_session.partner_id = partner_id;
+ var dm = chat_manager.get_dm_from_partner_id(partner_id);
+ if (!dm) {
+ chat_manager.open_and_detach_dm(partner_id);
+ } else {
+ var dm_session = _.findWhere(chat_sessions, {id: dm.id});
+ if (!dm_session) {
+ chat_manager.detach_channel(dm);
+ } else {
+ close_chat(dm_session);
+ dm.is_folded = false;
+ open_chat(dm);
+ }
+ }
+ });
+ add_chat_session(new_chat_session);
+ new_chat_session.window.appendTo($('body')).then(reposition_windows);
+ } else {
+ if (new_chat_session.window.is_hidden) {
+ make_session_visible(new_chat_session);
+ } else if (new_chat_session.window.folded) {
+ new_chat_session.window.toggle_fold(false);
+ }
}
}
+function close_chat (chat_session) {
+ chat_sessions = _.without(chat_sessions, chat_session);
+ chat_session.window.destroy();
+ reposition_windows();
+}
+
+function close_new_chat () {
+ chat_sessions = _.without(chat_sessions, new_chat_session);
+ reposition_windows({remove_new_chat: true});
+}
+
+function destroy_new_chat () {
+ new_chat_session.window.destroy();
+ new_chat_session = undefined;
+}
+
function toggle_fold_chat (channel) {
var session = _.find(chat_sessions, {id: channel.id});
if (session) {
- session.window.toggle_fold();
+ session.window.toggle_fold(channel.is_folded);
}
}
-function reposition_windows () {
+function compute_available_slots (nb_windows) {
+ if (config.device.size_class === config.device.SIZES.XS) {
+ display_state.nb_slots = 1; // one chat window full screen in mobile
+ return;
+ }
+ var width = window.innerWidth;
+ var nb_slots = Math.floor(width/CHAT_WINDOW_WIDTH);
+ var space_left = width - (Math.min(nb_slots, nb_windows)*CHAT_WINDOW_WIDTH);
+ if (nb_slots < nb_windows && space_left < 50) {
+ nb_slots--; // leave at least 50px for the hidden windows dropdown button
+ space_left += CHAT_WINDOW_WIDTH;
+ }
+ display_state.nb_slots = nb_slots;
+ display_state.space_left = space_left;
+}
+
+var reposition_windows = function (options) {
+ if (options && options.remove_new_chat) {
+ destroy_new_chat();
+ }
+ compute_available_slots(chat_sessions.length);
+ var hidden_sessions = [];
+ var hidden_unread_counter = 0;
+ var nb_slots = display_state.nb_slots;
_.each(chat_sessions, function (session, index) {
- session.window.$el.css({right: (CHAT_WINDOW_WIDTH + 5) * index, bottom: 0});
+ if (index < nb_slots) {
+ session.window.$el.css({right: CHAT_WINDOW_WIDTH*index, bottom: 0});
+ session.window.do_show();
+ } else {
+ hidden_sessions.push(session);
+ hidden_unread_counter += session.window.unread_msgs;
+ session.window.do_hide();
+ }
});
+ display_state.hidden_sessions = hidden_sessions;
+ display_state.hidden_unread_counter = hidden_unread_counter;
+
+ if (display_state.$hidden_windows_dropdown) {
+ display_state.$hidden_windows_dropdown.remove();
+ }
+ if (hidden_sessions.length) {
+ display_state.$hidden_windows_dropdown = render_hidden_sessions_dropdown();
+ var $hidden_windows_dropdown = display_state.$hidden_windows_dropdown;
+ $hidden_windows_dropdown.css({right: CHAT_WINDOW_WIDTH * nb_slots, bottom: 0})
+ .appendTo($('body'));
+ reposition_hidden_sessions_dropdown();
+ display_state.windows_dropdown_is_open = false;
+
+ $hidden_windows_dropdown.on('click', '.o_chat_header', function (event) {
+ var session_id = $(event.currentTarget).data('session-id');
+ var session = _.findWhere(hidden_sessions, {id: session_id});
+ if (session) {
+ make_session_visible(session);
+ }
+ });
+ $hidden_windows_dropdown.on('click', '.o_chat_window_close', function (event) {
+ var session_id = $(event.currentTarget).closest('.o_chat_header').data('session-id');
+ var session = _.findWhere(hidden_sessions, {id: session_id});
+ if (session) {
+ session.window.on_click_close(event);
+ display_state.windows_dropdown_is_open = true; // keep the dropdown open
+ }
+ });
+ }
+};
+
+function make_session_visible (session) {
+ utils.swap(chat_sessions, session, chat_sessions[display_state.nb_slots-1]);
+ reposition_windows();
+ session.window.toggle_fold(false);
+}
+
+function render_hidden_sessions_dropdown () {
+ var $dropdown = $(QWeb.render("mail.ChatWindowsDropdown", {
+ sessions: display_state.hidden_sessions,
+ open: display_state.windows_dropdown_is_open,
+ unread_counter: display_state.hidden_unread_counter,
+ }));
+ return $dropdown;
+}
+
+function reposition_hidden_sessions_dropdown () {
+ // Unfold dropdown to the left if there is enough place
+ var $dropdown_ul = display_state.$hidden_windows_dropdown.children('ul');
+ if (display_state.space_left > $dropdown_ul.width() + 10) {
+ $dropdown_ul.addClass('dropdown-menu-right');
+ }
}
function update_sessions (message, scrollBottom) {
_.each(chat_sessions, function (session) {
if (_.contains(message.channel_ids, session.id)) {
- if (!session.window.folded) {
+ var message_visible = !session.window.folded && !session.window.is_hidden && session.window.thread.is_at_bottom();
+ if (message_visible) {
chat_manager.mark_channel_as_seen(chat_manager.get_channel(session.id));
}
chat_manager.get_messages({channel_id: session.id}).then(function (messages) {
session.window.render(messages);
- if (scrollBottom) {
- session.window.scrollBottom();
+ if (scrollBottom && message_visible) {
+ session.window.thread.scroll_to();
}
});
}
@@ -111,7 +283,12 @@ function update_sessions (message, scrollBottom) {
core.bus.on('web_client_ready', null, function () {
chat_manager.bus.on('open_chat', null, open_chat);
- chat_manager.bus.on('close_chat', null, close_chat);
+ chat_manager.bus.on('close_chat', null, function (channel) {
+ var session = _.find(chat_sessions, {id: channel.id});
+ if (session) {
+ close_chat(session);
+ }
+ });
chat_manager.bus.on('channel_toggle_fold', null, toggle_fold_chat);
chat_manager.bus.on('new_message', null, function (message) {
@@ -124,7 +301,7 @@ core.bus.on('web_client_ready', null, function () {
chat_manager.bus.on('anyone_listening', null, function (channel, query) {
_.each(chat_sessions, function (session) {
- if (channel.id === session.id) {
+ if (channel.id === session.id && session.window.thread.is_at_bottom()) {
query.is_displayed = true;
}
});
@@ -139,11 +316,19 @@ core.bus.on('web_client_ready', null, function () {
});
chat_manager.bus.on('update_channel_unread_counter', null, function (channel) {
+ display_state.hidden_unread_counter = 0;
_.each(chat_sessions, function (session) {
if (channel.id === session.id) {
session.window.update_unread(channel.unread_counter);
}
+ if (session.window.is_hidden) {
+ display_state.hidden_unread_counter += session.window.unread_msgs;
+ }
});
+ if (display_state.$hidden_windows_dropdown) {
+ display_state.$hidden_windows_dropdown.html(render_hidden_sessions_dropdown().html());
+ reposition_hidden_sessions_dropdown();
+ }
});
chat_manager.is_ready.then(function() {
@@ -153,6 +338,50 @@ core.bus.on('web_client_ready', null, function () {
}
});
});
+
+ chat_manager.bus.on('detach_channel', null, function (channel) {
+ var chat_session = _.findWhere(chat_sessions, {id: channel.id});
+ if (!chat_session || chat_session.window.folded) {
+ chat_manager.detach_channel(channel);
+ } else if (chat_session.window.is_hidden) {
+ make_session_visible(chat_session);
+ } else {
+ chat_session.window.focus_input();
+ }
+ });
+
+ core.bus.on('resize', null, _.debounce(reposition_windows, 100));
+});
+
+});
+
+// FIXME: move this to its own file in master
+odoo.define('mail.ExtendedChatWindow', function (require) {
+"use strict";
+
+var chat_manager = require('mail.chat_manager');
+var ChatWindow = require('mail.ChatWindow');
+
+return ChatWindow.extend({
+ template: "mail.ExtendedChatWindow",
+ start: function () {
+ var self = this;
+ return this._super().then(function () {
+ if (self.options.thread_less) {
+ self.$el.addClass('o_thread_less');
+ self.$('.o_chat_search_input input')
+ .autocomplete({
+ source: function(request, response) {
+ chat_manager.search_partner(request.term, 10).done(response);
+ },
+ select: function(event, ui) {
+ self.trigger('open_dm_session', ui.item.id);
+ },
+ })
+ .focus();
+ }
+ });
+ },
});
});
diff --git a/addons/mail/static/src/less/chat_window.less b/addons/mail/static/src/less/chat_window.less
index ca2e8ca74f1..a4024c93dc1 100644
--- a/addons/mail/static/src/less/chat_window.less
+++ b/addons/mail/static/src/less/chat_window.less
@@ -1,10 +1,11 @@
@o-chat-window-bg: white;
+@o-chat-window-width: 300px;
.o_chat_window {
.o-flex-display();
.o-flex-flow(column, nowrap);
position: fixed;
- width: 300px;
+ width: @o-chat-window-width;
max-width: 100%;
height: 400px;
max-height: 100%;
@@ -15,7 +16,11 @@
z-index: 2;
@media (max-width: @screen-xs-max) {
- display:none;
+ width: 100%;
+ height: 100%!important;
+ &.o_folded {
+ display: none;
+ }
}
.o_chat_header {
@@ -25,26 +30,35 @@
background-color: @odoo-brand-primary;
padding: 5px;
+ @media (max-width: @screen-xs-max) {
+ height: @navbar-height;
+ .o_chat_title, .o_chat_window_close {
+ font-size: 22px;
+ }
+ }
+
.o_chat_title {
cursor: pointer;
.o-flex(1, 1, auto);
+ .o-text-overflow();
}
.o_chat_window_close {
color: white;
padding: 3px;
+ margin-left: 5px;
&:hover {
background-color: darken(@odoo-brand-primary, 10%);
}
}
}
- .o_chat_content {
+ .o_mail_thread {
.o-flex(1, 1, auto);
overflow: auto;
padding: 5px;
- .o_mail_thread .o_thread_date_separator {
+ .o_thread_date_separator {
margin-top: 0px;
margin-bottom: 15px;
border-color: lightgray;
@@ -67,3 +81,67 @@
}
}
}
+
+.o_chat_window_dropdown {
+ width: auto;
+ height: 28px;
+ color: white;
+ background-color: @odoo-brand-primary;
+ cursor: pointer;
+
+ @media (max-width: @screen-xs-max) {
+ display: none;
+ }
+
+ .o_chat_window_dropdown_toggler {
+ padding: 5px;
+
+ .o_total_unread_counter {
+ .o-position-absolute(-10px, 0, auto, auto);
+ background-color: @odoo-brand-optional;
+ padding: 0 2px;
+ font-size: smaller;
+ }
+ }
+ &.open .o_chat_window_dropdown_toggler .o_total_unread_counter {
+ display: none;
+ }
+
+ > ul {
+ max-width: @o-chat-window-width;
+ padding: 0;
+
+ > li.o_chat_header {
+ font-size: 12px;
+ padding: 3px 5px;
+ &~li.o_chat_header {
+ border-top: 1px solid white;
+ }
+ &:hover {
+ background-color: darken(@odoo-brand-primary, 10%);
+ color: white;
+ }
+ }
+ }
+}
+
+// FIXME: move this to its own file in master
+.o_chat_window.o_thread_less {
+ .o_chat_search_input {
+ .o-flex-display();
+ padding: 5px;
+ > span {
+ .o-flex(0, 0, auto);
+ }
+ > input {
+ margin-left: 5px;
+ .o-flex(1, 1, 100%);
+ }
+ }
+ .o_mail_thread {
+ background-color: lightgray;
+ }
+ .o_chat_input {
+ display: none;
+ }
+}
diff --git a/addons/mail/static/src/less/client_action.less b/addons/mail/static/src/less/client_action.less
index 6ad0c80a785..aa469649f2a 100644
--- a/addons/mail/static/src/less/client_action.less
+++ b/addons/mail/static/src/less/client_action.less
@@ -131,9 +131,11 @@
.o_composer_input {
.o-flex(1, 1, auto);
- padding: 5px;
margin-right: 5px;
- border: none;
+ > textarea {
+ padding: 5px;
+ border: none;
+ }
}
&.o_extended_composer .o_composer_input {
margin-right: 0;
@@ -265,3 +267,137 @@
color: orange;
}
}
+
+// Navbar icon and dropdown (FIXME: move to systray.less)
+.o_mail_navbar_item {
+ > a {
+ opacity: 1;
+ > i {
+ font-size: larger;
+ }
+ }
+ &.o_no_notification > a {
+ opacity: 0.5;
+ .o_notification_counter {
+ display: none;
+ }
+ }
+
+ &.open .o_mail_navbar_dropdown {
+ .o-flex-display();
+ .o-flex-flow(column, nowrap);
+ }
+ .o_mail_navbar_dropdown {
+ width: 350px;
+ padding: 0;
+ @media (max-width: @screen-xs-max) {
+ background-color: white!important;
+ max-height: none!important;
+ position: fixed!important;
+ top: @navbar-height;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ }
+
+ .o_spinner {
+ .o-flex-display();
+ .o-align-items(center);
+ .o-justify-content(center);
+ height: 50px;
+ }
+
+ .o_filter_button,
+ .o_new_message,
+ .o_redirect_to_discuss {
+ .btn-link;
+ padding: 5px;
+ }
+
+ .o_mail_navbar_dropdown_top {
+ .o-flex-display();
+ .o-flex(0, 0, auto);
+ justify-content: space-between;
+ border-bottom: 1px solid lightgray;
+
+ .o_filter_button {
+ color: @odoo-main-color-muted;
+ &:hover, &.o_selected {
+ color: @odoo-brand-optional;
+ }
+ &.o_selected {
+ cursor: default;
+ font-weight: bold;
+ }
+ }
+ }
+ .o_mail_navbar_dropdown_bottom {
+ .o-flex(0, 0, auto);
+ border-top: 1px solid lightgray;
+ height: 35px;
+ .o_redirect_to_discuss {
+ height: 100%;
+ width: 100%;
+ }
+ }
+
+ .o_mail_navbar_dropdown_channels {
+ .o-flex(0, 1, auto);
+ max-height: 400px;
+ min-height: 50px;
+ overflow-y: auto;
+
+ @media (max-width: @screen-xs-max) {
+ max-height: none;
+ }
+
+ .o_mail_channel_preview {
+ .o-flex-display();
+ height: 50px;
+ align-items: center;
+ padding: 5px;
+ cursor: pointer;
+ font-size: 12px;
+ overflow: hidden;
+ &~.o_mail_channel_preview {
+ border-top: 1px solid lightgray;
+ }
+ &.o_channel_unread {
+ background-color: lighten(lightgray, 10%);
+ }
+ &:hover {
+ background-color: lighten(lightgray, 5%);
+ }
+
+ .o_channel_image {
+ .o-flex(0, 0, 36px);
+ max-height: 36px;
+ }
+ .o_channel_info {
+ .o-flex(1, 1, 100%);
+ margin-left: 5px;
+ overflow: hidden;
+
+ .o_channel_title {
+ .o-flex-display();
+ .o_channel_name {
+ .o-flex(1, 1, auto);
+ .o-text-overflow();
+ }
+ .o_last_message_date {
+ .o-flex(0, 0, auto);
+ padding-top: 3px;
+ font-size: xx-small;
+ color: @odoo-main-color-muted;
+ }
+ }
+ .o_last_message_preview {
+ width: 100%;
+ color: @odoo-main-color-muted;
+ .o-text-overflow();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/addons/mail/static/src/less/composer.less b/addons/mail/static/src/less/composer.less
index 6dad1dac3d2..88b44fec311 100644
--- a/addons/mail/static/src/less/composer.less
+++ b/addons/mail/static/src/less/composer.less
@@ -10,7 +10,7 @@
}
.o_composer {
- .o_composer_input {
+ .o_composer_input > textarea {
resize: none;
}
}
diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less
index f2971dcf17e..be86e577b08 100644
--- a/addons/mail/static/src/less/thread.less
+++ b/addons/mail/static/src/less/thread.less
@@ -97,6 +97,9 @@
.o_thread_message_core {
.o-flex(1, 1, auto);
+ min-width: 0;
+ max-width: 100%;
+ word-wrap: break-word;
&.o_mail_note {
background-color: @gray-lighter;
@@ -111,12 +114,20 @@
color: gray;
}
- > p {
+ [summary~=o_mail_notification] { // name conflicts with channel notifications, but is odoo notification buttons to hide in chatter if present
+ display: none;
+ }
+
+ p {
margin: 0 0 9px; // Required by the old design to override a general rule on p's
&:last-child {
margin-bottom: 0;
}
}
+ a {
+ display: inline-block;
+ word-break: break-all;
+ }
.o_mail_body_long {
display: none;
diff --git a/addons/mail/static/src/xml/chat_window.xml b/addons/mail/static/src/xml/chat_window.xml
index 01e475a6eb8..eb003cd59cd 100644
--- a/addons/mail/static/src/xml/chat_window.xml
+++ b/addons/mail/static/src/xml/chat_window.xml
@@ -1,23 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
-
<t t-name="mail.ChatWindow">
<div class="o_chat_window">
<div class="o_chat_header">
- <span class="o_chat_title">
- <t t-esc="widget.title"/>
- </span>
- <span>
- <a href="#" class="o_chat_window_close fa fa-close"></a>
- </span>
+ <t t-call="mail.ChatWindowHeaderContent">
+ <t t-set="title" t-value="widget.title"/>
+ <t t-set="unread_counter" t-value="widget.unread_msgs"/>
+ </t>
</div>
<div class="o_chat_content">
</div>
<div class="o_chat_input">
- <input t-att-placeholder="widget.placeholder"/>
+ <input t-att-placeholder="widget.options.placeholder"/>
+ </div>
+ </div>
+ </t>
+
+ <!-- FIXME: move this to its own file in master -->
+ <t t-name="mail.ExtendedChatWindow" t-extend="mail.ChatWindow">
+ <t t-jquery=".o_chat_header" t-operation="after">
+ <div t-if="widget.options.thread_less" class="o_chat_search_input">
+ <span> To: </span>
+ <input type="text"/>
</div>
+ </t>
+ </t>
+
+ <t t-name="mail.ChatWindowsDropdown">
+ <div t-attf-class="o_chat_window o_chat_window_dropdown dropup #{open ? 'open' : ''}">
+ <span class="o_chat_window_dropdown_toggler dropdown-toggle" data-toggle="dropdown">
+ <i class="fa fa-comments-o"/> <t t-esc="sessions.length"/>
+ <span class="o_total_unread_counter">
+ <t t-if="unread_counter"><t t-esc="unread_counter"/></t>
+ </span>
+ </span>
+ <ul class="dropdown-menu">
+ <t t-foreach="sessions" t-as="session">
+ <li class="o_chat_header" t-att-data-session-id="session.id">
+ <t t-call="mail.ChatWindowHeaderContent">
+ <t t-set="title" t-value="session.window.title"/>
+ <t t-set="unread_counter" t-value="session.window.unread_msgs"/>
+ </t>
+ </li>
+ </t>
+ </ul>
</div>
</t>
+ <t t-name="mail.ChatWindowHeaderContent">
+ <span class="o_chat_title">
+ <t t-esc="title"/>
+ <span class="o_unread_counter">
+ <t t-if="unread_counter">(<t t-esc="unread_counter"/>)</t>
+ </span>
+ </span>
+ <span>
+ <a href="#" class="o_chat_window_close fa fa-close"/>
+ </span>
+ </t>
+
</templates>
diff --git a/addons/mail/static/src/xml/client_action.xml b/addons/mail/static/src/xml/client_action.xml
index c8db64a7d56..e76e56a7810 100644
--- a/addons/mail/static/src/xml/client_action.xml
+++ b/addons/mail/static/src/xml/client_action.xml
@@ -75,13 +75,13 @@
<t t-if="!disable_add_channel || !empty">
<div class="o_mail_sidebar_title">
<h4 t-att-class="channel_type == 'public' ? 'o_mail_open_channels' : ''"><t t-esc="channel_title"/></h4>
- <span t-if="!disable_add_channel" class="fa fa-plus o_add" t-att-title="_t('Add')" t-attf-data-type="#{channel_type}"/>
+ <span t-if="!disable_add_channel" class="fa fa-plus o_add" title="Add" t-attf-data-type="#{channel_type}"/>
</div>
</t>
</t>
<t t-name="mail.chat.SidebarItems">
<t t-foreach="channels" t-as="channel">
- <t t-set="counter" t-value="channel.needaction_counter"/>
+ <t t-set="counter" t-value="channel.is_chat ? channel.unread_counter : 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 t-if="display_status">
@@ -94,7 +94,7 @@
<i t-if="channel.mass_mailing" class="fa fa-envelope-o" title="Sends messages by email"/>
<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('Leave this channel')" t-att-data-channel-id="channel.id"/>
+ title="Leave this channel" 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}">
@@ -119,13 +119,14 @@
<!-- Buttons of the Control Panel -->
<t t-name="mail.chat.ControlButtons">
<div>
- <button type="button" class="btn btn-primary btn-sm o_mail_toggle_channels visible-xs-inline" t-att-title='_t("Toggle Channels")'>Channels</button>
- <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_invite" t-att-title='_t("Invite people")'>Invite</button>
- <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_detach hidden-xs" t-att-title='_t("Detach the conversation")'>Detach</button>
- <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_mark_read" t-att-title='_t("Mark all as read")'>Mark all read</button>
- <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_unstar_all" t-att-title='_t("Unstar all messages")'>Unstar all</button>
- <button type="button" class="btn btn-default btn-sm o_mail_chat_button_unsubscribe" t-att-title='_t("Unsubscribe from channel")'>Unsubscribe</button>
- <button type="button" class="btn btn-default btn-sm o_mail_chat_button_settings" t-att-title='_t("Open channel settings")'>Settings</button>
+ <button type="button" class="btn btn-primary btn-sm o_mail_toggle_channels visible-xs-inline" title="Toggle Channels">Channels</button>
+ <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_invite" title="Invite people">Invite</button>
+ <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_detach hidden-xs" title="Detach the conversation">Detach</button>
+ <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_mark_read" title="Mark all as read">Mark all read</button>
+ <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_unstar_all" title="Unstar all messages">Unstar all</button>
+ <button type="button" class="btn btn-default btn-sm o_mail_chat_button_new_message" title="Compose a new message">Send mail</button>
+ <button type="button" class="btn btn-default btn-sm o_mail_chat_button_unsubscribe" title="Unsubscribe from channel">Unsubscribe</button>
+ <button type="button" class="btn btn-default btn-sm o_mail_chat_button_settings" title="Open channel settings">Settings</button>
</div>
</t>
diff --git a/addons/mail/static/src/xml/composer.xml b/addons/mail/static/src/xml/composer.xml
index 9af882c4062..25f9ba6bfda 100644
--- a/addons/mail/static/src/xml/composer.xml
+++ b/addons/mail/static/src/xml/composer.xml
@@ -14,7 +14,9 @@
</div>
</t>
<div t-attf-class="o_composer #{widget.extended ? 'o_extended_composer' : ''}">
- <textarea class="o_composer_input" placeholder="Write something..."/>
+ <div class="o_composer_input">
+ <textarea placeholder="Write something..."/>
+ </div>
<t t-if="!widget.extended">
<div class="btn-group o_composer_buttons">
<button class="btn btn-sm btn-primary o_composer_button_send" type="button"><t t-esc="widget.options.send_text"/></button>
@@ -33,7 +35,7 @@
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
</t>
</span>
- <span class="o_composer_emoji"></span>
+ <span t-att-class="widget.emoji_container_classname"></span>
</div>
<t t-name="mail.ChatComposer.emojis">
diff --git a/addons/mail/static/src/xml/systray.xml b/addons/mail/static/src/xml/systray.xml
index 58a735e80c5..247096b3b6e 100644
--- a/addons/mail/static/src/xml/systray.xml
+++ b/addons/mail/static/src/xml/systray.xml
@@ -1,19 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
- <t t-name="mail.ComposeMessageTopButton">
- <li title='Compose new Message' class="oe_topbar_item oe_topbar_compose_full_email">
- <a href="#">
- <i class="fa fa-envelope-o fa-fw"></i>
+
+ <t t-name="mail.chat.MessagingMenu">
+ <li class="o_mail_navbar_item">
+ <a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="Messages" href="#">
+ <i class="fa fa-comments"/> <span class="o_notification_counter"/>
</a>
+ <ul class="o_mail_navbar_dropdown dropdown-menu" role="menu">
+ <li class="o_mail_navbar_dropdown_top">
+ <div>
+ <button type="button" class="btn btn-sm o_filter_button o_selected"> All </button>
+ <button type="button" class="btn btn-sm o_filter_button" data-filter='chat'> Chat </button>
+ <button type="button" class="btn btn-sm o_filter_button" data-filter='channels'> Channels </button>
+ </div>
+ <button type="button" class="btn btn-sm o_new_message"> New message </button>
+ </li>
+ <li class="o_mail_navbar_dropdown_channels"/>
+ <li class="o_mail_navbar_dropdown_bottom">
+ <button type="button" class="btn o_redirect_to_discuss">
+ @Inbox <span class="o_inbox_counter"/>
+ </button>
+ </li>
+ </ul>
</li>
</t>
- <t t-name="mail.chat.NotificationTopButton">
- <li t-att-title='_t("Notifications")'>
- <a href="#">
- <i class="fa fa-comment"/> <span class="o_notification_counter"/>
- </a>
- </li>
+ <t t-name="mail.chat.ChannelsPreview">
+ <t t-foreach="channels" t-as="channel">
+ <div t-attf-class="o_mail_channel_preview #{channel.unread_counter ? 'o_channel_unread' : ''}"
+ t-att-data-channel_id="channel.id">
+ <img class="o_channel_image" t-att-src="channel.image_src"/>
+ <div class="o_channel_info">
+ <div class="o_channel_title">
+ <span class="o_channel_name">
+ <t t-if="channel.status" t-call="mail.chat.UserStatus">
+ <t t-set="status" t-value="channel.status"/>
+ </t>
+ <t t-esc="channel.name"/>
+ <t t-if="channel.unread_counter">(<t t-esc="channel.unread_counter"/>)</t>
+ </span>
+ <span class="o_last_message_date"> <t t-esc="channel.last_message_date"/> </span>
+ </div>
+ <div class="o_last_message_preview">
+ <t t-raw="channel.last_message_preview"/>
+ </div>
+ </div>
+ </div>
+ </t>
+ </t>
+
+ <t t-name="mail.chat.Spinner">
+ <div class="o_spinner"> <i class="fa fa-spinner fa-spin"/> </div>
</t>
</templates>
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index 0952fcf0e7f..970c8ff6a6f 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -65,7 +65,7 @@
</span>
<i t-if="!message.display_author &amp;&amp; options.display_stars &amp;&amp; message.message_type != 'notification'"
t-att-class="'fa o_thread_message_star ' + (message.is_starred ? 'fa-star' : 'fa-star-o')"
- t-att-data-message-id="message.id" t-att-title="_t('Mark as Todo')"/>
+ t-att-data-message-id="message.id" title="Mark as Todo"/>
</div>
<div t-att-class="'o_thread_message_core' + (message.is_note ? ' o_mail_note' : '')">
<p t-if="message.display_author" class="o_mail_info">
@@ -99,10 +99,10 @@
<span>
<i t-if="options.display_stars &amp;&amp; message.message_type != 'notification'"
t-att-class="'fa fa-lg o_thread_message_star ' + (message.is_starred ? 'fa-star' : 'fa-star-o')"
- t-att-data-message-id="message.id" t-att-title="_t('Mark as Todo')"/>
+ t-att-data-message-id="message.id" title="Mark as Todo"/>
<i t-if="message.is_needaction &amp;&amp; options.display_needactions"
class="fa fa-check o_thread_message_needaction"
- t-att-data-message-id="message.id" t-att-title="_t('Mark as Read')"/>
+ t-att-data-message-id="message.id" title="Mark as Read"/>
</span>
</p>
<t t-if="message.tracking_value_ids &amp;&amp; message.tracking_value_ids.length &gt; 0">
@@ -122,7 +122,7 @@
</div>
</t>
<t t-if="!options.shorten_messages || !message.body_short">
- <t t-raw="message.body"/>
+ <div><t t-raw="message.body"/></div>
</t>
</t>
<t t-foreach="message.attachment_ids" t-as="attachment">