diff --git a/addons/im_livechat/views/im_livechat_channel_templates.xml b/addons/im_livechat/views/im_livechat_channel_templates.xml
index 911adc39762..ec25a417dca 100644
--- a/addons/im_livechat/views/im_livechat_channel_templates.xml
+++ b/addons/im_livechat/views/im_livechat_channel_templates.xml
@@ -128,6 +128,7 @@
+
@@ -141,6 +142,7 @@
+
diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index f5b2944a05a..105d149fa43 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -44,9 +44,9 @@
'application': True,
'qweb': [
'static/src/xml/activity.xml',
- 'static/src/xml/client_action.xml',
'static/src/xml/composer.xml',
'static/src/xml/chatter.xml',
+ 'static/src/xml/discuss.xml',
'static/src/xml/followers.xml',
'static/src/xml/systray.xml',
'static/src/xml/thread.xml',
diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py
index a3e9a10ced9..4136b353187 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -258,8 +258,8 @@ class MailController(http.Controller):
def needaction(self):
return request.env['res.partner'].get_needaction_count()
- @http.route('/mail/client_action', type='json', auth='user')
- def mail_client_action(self):
+ @http.route('/mail/init_messaging', type='json', auth='user')
+ def mail_init_messaging(self):
values = {
'needaction_inbox_counter': request.env['res.partner'].get_needaction_count(),
'starred_counter': request.env['res.partner'].get_starred_count(),
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 8fa05876f79..0d855dd15de 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -186,17 +186,6 @@ class Message(models.Model):
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):
""" Remove the needaction from messages for the current partner. """
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js
deleted file mode 100644
index 35d16915341..00000000000
--- a/addons/mail/static/src/js/chat_manager.js
+++ /dev/null
@@ -1,1275 +0,0 @@
-odoo.define('mail.chat_manager', function (require) {
-"use strict";
-
-var bus = require('bus.bus').bus;
-var utils = require('mail.utils');
-var config = require('web.config');
-var Bus = require('web.Bus');
-var core = require('web.core');
-var session = require('web.session');
-var time = require('web.time');
-var web_client = require('web.web_client');
-var Class = require('web.Class');
-var Mixins = require('web.mixins');
-var ServicesMixin = require('web.ServicesMixin');
-
-var _t = core._t;
-var _lt = core._lt;
-var LIMIT = 25;
-var preview_msg_max_size = 350; // optimal for native english speakers
-var ODOOBOT_ID = "ODOOBOT";
-
-// Private model
-//----------------------------------------------------------------------------------
-var messages = [];
-var channels = [];
-var channels_preview_def;
-var channel_defs = {};
-var chat_unread_counter = 0;
-var unread_conversation_counter = 0;
-var emojis = [];
-var emoji_substitutions = {};
-var emoji_unicodes = {};
-var needaction_counter = 0;
-var starred_counter = 0;
-var mention_partner_suggestions = [];
-var canned_responses = [];
-var commands = [];
-var discuss_menu_id;
-var global_unread_counter = 0;
-var pinned_dm_partners = []; // partner_ids we have a pinned DM with
-var client_action_open = false;
-
-// Global unread counter and notifications
-//----------------------------------------------------------------------------------
-bus.on("window_focus", null, function() {
- global_unread_counter = 0;
- web_client.set_title_part("_chat");
-});
-
-function notify_incoming_message (msg, options) {
- if (bus.is_odoo_focused() && options.is_displayed) {
- // no need to notify
- return;
- }
- var title = _t('New message');
- if (msg.author_id[1]) {
- title = _.escape(msg.author_id[1]);
- }
- var content = utils.parse_and_transform(msg.body, utils.strip_html).substr(0, preview_msg_max_size);
-
- if (!bus.is_odoo_focused()) {
- global_unread_counter++;
- var tab_title = _.str.sprintf(_t("%d Messages"), global_unread_counter);
- web_client.set_title_part("_chat", tab_title);
- }
-
- utils.send_notification(web_client, title, content);
-}
-
-// Message and channel manipulation helpers
-//----------------------------------------------------------------------------------
-
-// options: channel_id, silent
-function add_message (data, options) {
- options = options || {};
- var msg = _.findWhere(messages, { id: data.id });
-
- if (!msg) {
- msg = chat_manager.make_message(data);
- // 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);
- if (channel) {
- // update the channel's last message (displayed in the channel
- // preview, in mobile)
- if (!channel.last_message || msg.id > channel.last_message.id) {
- channel.last_message = msg;
- }
- add_to_cache(msg, []);
- if (options.domain && options.domain !== []) {
- add_to_cache(msg, options.domain);
- }
- if (channel.hidden) {
- channel.hidden = false;
- chat_manager.bus.trigger('new_channel', channel);
- }
- if (channel.type !== 'static' && !msg.is_author && !msg.is_system_notification) {
- if (options.increment_unread) {
- update_channel_unread_counter(channel, channel.unread_counter+1);
- }
- if (channel.is_chat && options.show_notification) {
- if (!client_action_open && !config.device.isMobile) {
- // automatically open chat window
- chat_manager.bus.trigger('open_chat', channel, { passively: true });
- }
- var query = {is_displayed: false};
- chat_manager.bus.trigger('anyone_listening', channel, query);
- notify_incoming_message(msg, query);
- }
- }
- }
- });
- if (!options.silent) {
- chat_manager.bus.trigger('new_message', msg);
- }
- } else if (options.domain && options.domain !== []) {
- add_to_cache(msg, options.domain);
- }
- return msg;
-}
-
-function make_message (data) {
- var msg = {
- id: data.id,
- author_id: data.author_id,
- body: data.body || "",
- date: moment(time.str_to_datetime(data.date)),
- message_type: data.message_type,
- subtype_description: data.subtype_description,
- is_author: data.author_id && data.author_id[0] === session.partner_id,
- is_note: data.is_note,
- is_discussion: data.is_discussion,
- is_system_notification: (data.message_type === 'notification' && data.model === 'mail.channel')
- || data.info === 'transient_message',
- attachment_ids: data.attachment_ids || [],
- subject: data.subject,
- email_from: data.email_from,
- customer_email_status: data.customer_email_status,
- customer_email_data: data.customer_email_data,
- record_name: data.record_name,
- tracking_value_ids: data.tracking_value_ids,
- channel_ids: data.channel_ids,
- model: data.model,
- res_id: data.res_id,
- url: session.url("/mail/view?message_id=" + data.id),
- module_icon:data.module_icon,
- };
-
- _.each(_.keys(emoji_substitutions), function (key) {
- var escaped_key = String(key).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1');
- var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + escaped_key + ")(?=\\s|$|[a-z]*>)", "g");
- msg.body = msg.body.replace(regexp, '
'+emoji_substitutions[key]+' ');
- });
-
- function property_descr(channel) {
- return {
- enumerable: true,
- get: function () {
- return _.contains(msg.channel_ids, channel);
- },
- set: function (bool) {
- if (bool) {
- add_channel_to_message(msg, channel);
- } else {
- msg.channel_ids = _.without(msg.channel_ids, channel);
- }
- }
- };
- }
-
- Object.defineProperties(msg, {
- is_starred: property_descr("channel_starred"),
- is_needaction: property_descr("channel_inbox"),
- });
-
- if (_.contains(data.needaction_partner_ids, session.partner_id)) {
- msg.is_needaction = true;
- }
- if (_.contains(data.starred_partner_ids, session.partner_id)) {
- msg.is_starred = true;
- }
- if (msg.model === 'mail.channel') {
- var real_channels = _.without(msg.channel_ids, 'channel_inbox', 'channel_starred');
- var origin = real_channels.length === 1 ? real_channels[0] : undefined;
- var channel = origin && chat_manager.get_channel(origin);
- if (channel) {
- msg.origin_id = origin;
- msg.origin_name = channel.name;
- }
- }
-
- // Compute displayed author name or email
- if ((!msg.author_id || !msg.author_id[0]) && msg.email_from) {
- msg.mailto = msg.email_from;
- } else {
- msg.displayed_author = (msg.author_id === ODOOBOT_ID) && "OdooBot" ||
- msg.author_id && msg.author_id[1] ||
- msg.email_from || _t('Anonymous');
- }
-
- // Don't redirect on author clicked of self-posted or OdooBot messages
- msg.author_redirect = !msg.is_author && msg.author_id !== ODOOBOT_ID;
-
- // Compute the avatar_url
- if (msg.author_id === ODOOBOT_ID) {
- msg.avatar_src = "/mail/static/src/img/odoo_o.png";
- } else if (msg.author_id && msg.author_id[0]) {
- msg.avatar_src = "/web/image/res.partner/" + msg.author_id[0] + "/image_small";
- } else if (msg.message_type === 'email') {
- msg.avatar_src = "/mail/static/src/img/email_icon.png";
- } else {
- msg.avatar_src = "/mail/static/src/img/smiley/avatar.jpg";
- }
-
- // add anchor tags to urls
- msg.body = utils.parse_and_transform(msg.body, utils.add_link);
-
- // Compute url of attachments
- _.each(msg.attachment_ids, function(a) {
- a.url = '/web/content/' + a.id + '?download=true';
- });
-
- // format date to the local only once by message
- // can not be done in preprocess, since it alter the original value
- if (msg.tracking_value_ids && msg.tracking_value_ids.length) {
- _.each(msg.tracking_value_ids, function(f) {
- if (f.field_type === 'datetime') {
- var format = 'LLL';
- if (f.old_value) {
- f.old_value = moment.utc(f.old_value).local().format(format);
- }
- if (f.new_value) {
- f.new_value = moment.utc(f.new_value).local().format(format);
- }
- } else if (f.field_type === 'date') {
- var format = 'LL';
- if (f.old_value) {
- f.old_value = moment(f.old_value).local().format(format);
- }
- if (f.new_value) {
- f.new_value = moment(f.new_value).local().format(format);
- }
- }
- });
- }
-
- return msg;
-}
-
-function add_channel_to_message (message, channel_id) {
- message.channel_ids.push(channel_id);
- message.channel_ids = _.uniq(message.channel_ids);
-}
-
-function add_channel (data, options) {
- options = typeof options === "object" ? options : {};
- var channel = chat_manager.get_channel(data.id);
- if (channel) {
- if (channel.is_folded !== (data.state === "folded")) {
- channel.is_folded = (data.state === "folded");
- chat_manager.bus.trigger("channel_toggle_fold", channel);
- }
- } else {
- channel = chat_manager.make_channel(data, options);
- channels.push(channel);
- if (data.last_message) {
- channel.last_message = add_message(data.last_message);
- }
- // In case of a static channel (Inbox, Starred), the name is translated thanks to _lt
- // (lazy translate). In this case, channel.name is an object, not a string.
- channels = _.sortBy(channels, function (channel) { return _.isString(channel.name) ? channel.name.toLowerCase() : '' });
- if (!options.silent) {
- chat_manager.bus.trigger("new_channel", channel);
- }
- if (channel.is_detached) {
- chat_manager.bus.trigger("open_chat", channel);
- }
- }
- return channel;
-}
-
-function make_channel (data, options) {
- var channel = {
- id: data.id,
- name: data.name,
- server_type: data.channel_type,
- type: data.type || data.channel_type,
- all_history_loaded: false,
- uuid: data.uuid,
- is_detached: data.is_minimized,
- is_folded: data.state === "folded",
- autoswitch: 'autoswitch' in options ? options.autoswitch : true,
- hidden: options.hidden,
- display_needactions: options.display_needactions,
- mass_mailing: data.mass_mailing,
- group_based_subscription: data.group_based_subscription,
- needaction_counter: data.message_needaction_counter || 0,
- unread_counter: 0,
- last_seen_message_id: data.seen_message_id,
- cache: {'[]': {
- all_history_loaded: false,
- loaded: false,
- messages: [],
- }},
- };
- if (channel.type === "channel") {
- channel.type = data.public !== "private" ? "public" : "private";
- }
- if (_.size(data.direct_partner) > 0) {
- channel.type = "dm";
- channel.name = data.direct_partner[0].name;
- channel.direct_partner_id = data.direct_partner[0].id;
- 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;
- }
- if (data.last_message_date) {
- channel.last_message_date = moment(time.str_to_datetime(data.last_message_date));
- }
- 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;
-}
-
-function remove_channel (channel) {
- if (!channel) { return; }
- if (channel.type === 'dm') {
- var index = pinned_dm_partners.indexOf(channel.direct_partner_id);
- if (index > -1) {
- pinned_dm_partners.splice(index, 1);
- bus.update_option('bus_presence_partner_ids', pinned_dm_partners);
- }
- }
- channels = _.without(channels, channel);
- delete channel_defs[channel.id];
-}
-
-function get_channel_cache (channel, domain) {
- var stringified_domain = JSON.stringify(domain || []);
- if (!channel.cache[stringified_domain]) {
- channel.cache[stringified_domain] = {
- all_history_loaded: false,
- loaded: false,
- messages: [],
- };
- }
- return channel.cache[stringified_domain];
-}
-
-function invalidate_caches(channel_ids) {
- _.each(channel_ids, function (channel_id) {
- var channel = chat_manager.get_channel(channel_id);
- if (channel) {
- channel.cache = { '[]': channel.cache['[]']};
- }
- });
-}
-
-function add_to_cache(message, domain) {
- _.each(message.channel_ids, function (channel_id) {
- var channel = chat_manager.get_channel(channel_id);
- if (channel) {
- var channel_cache = get_channel_cache(channel, domain);
- var index = _.sortedIndex(channel_cache.messages, message, 'id');
- if (channel_cache.messages[index] !== message) {
- channel_cache.messages.splice(index, 0, message);
- }
- }
- });
-}
-
-function remove_message_from_channel (channel_id, message) {
- message.channel_ids = _.without(message.channel_ids, channel_id);
- var channel = _.findWhere(channels, { id: channel_id });
- _.each(channel.cache, function (cache) {
- cache.messages = _.without(cache.messages, message);
- });
-}
-
-function update_channel_unread_counter (channel, counter) {
- if (channel.unread_counter > 0 && counter === 0) {
- unread_conversation_counter = Math.max(0, unread_conversation_counter-1);
- } else if (channel.unread_counter === 0 && counter > 0) {
- unread_conversation_counter++;
- }
- if (channel.is_chat) {
- chat_unread_counter = Math.max(0, chat_unread_counter - channel.unread_counter + counter);
- }
- channel.unread_counter = counter;
- chat_manager.bus.trigger("update_channel_unread_counter", channel);
-}
-
-// Notification handlers
-// ---------------------------------------------------------------------------------
-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,
- increment_unread: true,
- });
- invalidate_caches(message.channel_ids);
- if (message.channel_ids.length !== 0) {
- needaction_counter++;
- }
- _.each(message.channel_ids, function (channel_id) {
- var channel = chat_manager.get_channel(channel_id);
- if (channel) {
- channel.needaction_counter++;
- }
- });
- chat_manager.bus.trigger('update_needaction', needaction_counter);
-}
-
-function on_channel_notification (message) {
- var def;
- var channel_already_in_cache = true;
- if (message.channel_ids.length === 1) {
- channel_already_in_cache = !!chat_manager.get_channel(message.channel_ids[0]);
- def = chat_manager.join_channel(message.channel_ids[0], {autoswitch: false});
- } else {
- def = $.when();
- }
- def.then(function () {
- // don't increment unread if channel wasn't in cache yet as its unread counter has just been fetched
- add_message(message, { show_notification: true, increment_unread: channel_already_in_cache });
- invalidate_caches(message.channel_ids);
- });
-}
-
-function on_partner_notification (data) {
- if (data.info === "unsubscribe") {
- var channel = chat_manager.get_channel(data.id);
- if (channel) {
- var msg;
- if (_.contains(['public', 'private'], channel.type)) {
- msg = _.str.sprintf(_t('You unsubscribed from
%s.'), channel.name);
- } else {
- msg = _.str.sprintf(_t('You unpinned your conversation with
%s.'), channel.name);
- }
- remove_channel(channel);
- chat_manager.bus.trigger("unsubscribe_from_channel", data.id);
- web_client.do_notify(_("Unsubscribed"), msg);
- }
- } 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 if (data.info === 'channel_seen') {
- on_channel_seen_notification(data);
- } else if (data.info === 'transient_message') {
- on_transient_message_notification(data);
- } else if (data.type === 'activity_updated') {
- onActivityUpdateNodification(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) {
- invalidate_caches(message.channel_ids);
- message.is_starred = data.starred;
- if (!message.is_starred) {
- remove_message_from_channel("channel_starred", message);
- starred_counter--;
- } else {
- add_to_cache(message, []);
- var channel_starred = chat_manager.get_channel('channel_starred');
- channel_starred.cache = _.pick(channel_starred.cache, "[]");
- starred_counter++;
- }
- chat_manager.bus.trigger('update_message', message);
- }
- });
- chat_manager.bus.trigger('update_starred', starred_counter);
-}
-
-function on_mark_as_read_notification (data) {
- _.each(data.message_ids, function (msg_id) {
- var message = _.findWhere(messages, { id: msg_id });
- if (message) {
- invalidate_caches(message.channel_ids);
- remove_message_from_channel("channel_inbox", message);
- chat_manager.bus.trigger('update_message', message, data.type);
- }
- });
- if (data.channel_ids) {
- _.each(data.channel_ids, function (channel_id) {
- var channel = chat_manager.get_channel(channel_id);
- if (channel) {
- 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
- _.each(channels, function (channel) {
- channel.needaction_counter = 0;
- });
- }
- needaction_counter = Math.max(needaction_counter - data.message_ids.length, 0);
- chat_manager.bus.trigger('update_needaction', needaction_counter);
-}
-
-function on_mark_as_unread_notification (data) {
- _.each(data.message_ids, function (message_id) {
- var message = _.findWhere(messages, { id: message_id });
- if (message) {
- invalidate_caches(message.channel_ids);
- add_channel_to_message(message, 'channel_inbox');
- add_to_cache(message, []);
- }
- });
- var channel_inbox = chat_manager.get_channel('channel_inbox');
- channel_inbox.cache = _.pick(channel_inbox.cache, "[]");
-
- _.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_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")) {
- add_channel(chat_session, {autoswitch: false});
- if (!chat_session.is_minimized && chat_session.info !== 'creation') {
- web_client.do_notify(_t("Invitation"), _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")) {
- 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, {keep_open_if_unread: true});
- }
- }
-}
-
-function on_presence_notification (data) {
- 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);
- }
-}
-
-function on_transient_message_notification (data) {
- var last_message = _.last(messages);
- data.id = (last_message ? last_message.id : 0) + 0.01;
- data.author_id = data.author_id || ODOOBOT_ID;
- add_message(data);
-}
-
-function onActivityUpdateNodification (data) {
- chat_manager.bus.trigger('activity_updated', data);
-}
-// Public interface
-//----------------------------------------------------------------------------------
-var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
- // these two functions are exposed for extensibility purposes and shouldn't be called by other modules
- make_message: make_message,
- make_channel: make_channel,
-
- init: function (parent) {
- var self = this;
- Mixins.EventDispatcherMixin.init.call(this);
- this.setParent(parent);
-
- this.bus = new Bus();
- this.bus.on('client_action_open', null, function (open) {
- client_action_open = open;
- });
-
- bus.on('notification', null, on_notification);
-
- this.channel_seen = _.throttle(function (channel) {
- return self._rpc({
- model: 'mail.channel',
- method: 'channel_seen',
- args: [[channel.id]],
- }, {
- shadow: true
- });
- }, 3000);
- },
-
- start: function () {
- this.is_ready = session.is_bound.then(function(){
- var context = _.extend({isMobile: config.device.isMobile}, session.user_context);
- return session.rpc('/mail/client_action', {context: context});
- }).then(this._onMailClientAction.bind(this));
-
- add_channel({
- id: "channel_inbox",
- name: _lt("Inbox"),
- type: "static",
- }, { display_needactions: true });
-
- add_channel({
- id: "channel_starred",
- name: _lt("Starred"),
- type: "static"
- });
- },
-
- _onMailClientAction: function (result) {
- _.each(result.channel_slots, function (channels) {
- _.each(channels, add_channel);
- });
- needaction_counter = result.needaction_inbox_counter;
- starred_counter = result.starred_counter;
- commands = _.map(result.commands, function (command) {
- return _.extend({ id: command.name }, command);
- });
- mention_partner_suggestions = result.mention_partner_suggestions;
- discuss_menu_id = result.menu_id;
-
- // Shortcodes: canned responses and emojis
- _.each(result.shortcodes, function (s) {
- if (s.shortcode_type === 'text') {
- canned_responses.push(_.pick(s, ['id', 'source', 'substitution']));
- } else {
- emojis.push(_.pick(s, ['id', 'source', 'unicode_source', 'substitution', 'description']));
- emoji_substitutions[_.escape(s.source)] = s.substitution;
- if (s.unicode_source) {
- emoji_substitutions[_.escape(s.unicode_source)] = s.substitution;
- emoji_unicodes[_.escape(s.source)] = s.unicode_source;
- }
- }
- });
- bus.start_polling();
- },
-
- // options: domain, load_more
- _fetchFromChannel: function (channel, options) {
- options = options || {};
- var domain =
- (channel.id === "channel_inbox") ? [['needaction', '=', true]] :
- (channel.id === "channel_starred") ? [['starred', '=', true]] :
- [['channel_ids', 'in', channel.id]];
- var cache = get_channel_cache(channel, options.domain);
-
- if (options.domain) {
- domain = domain.concat(options.domain || []);
- }
- if (options.load_more) {
- var min_message_id = cache.messages[0].id;
- domain = [['id', '<', min_message_id]].concat(domain);
- }
-
- return this._rpc({
- model: 'mail.message',
- method: 'message_fetch',
- args: [domain],
- kwargs: {limit: LIMIT, context: session.user_context},
- })
- .then(function (msgs) {
- if (!cache.all_history_loaded) {
- cache.all_history_loaded = msgs.length < LIMIT;
- }
- cache.loaded = true;
-
- _.each(msgs, function (msg) {
- add_message(msg, {channel_id: channel.id, silent: true, domain: options.domain});
- });
- var channel_cache = get_channel_cache(channel, options.domain || []);
- return channel_cache.messages;
- });
- },
- // options: force_fetch
- _fetchDocumentMessages : function (ids, options) {
- var loaded_msgs = _.filter(messages, function (message) {
- return _.contains(ids, message.id);
- });
- var loaded_msg_ids = _.pluck(loaded_msgs, 'id');
-
- options = options || {};
- if (options.force_fetch || _.difference(ids.slice(0, LIMIT), loaded_msg_ids).length) {
- var ids_to_load = _.difference(ids, loaded_msg_ids).slice(0, LIMIT);
-
- return this._rpc({
- model: 'mail.message',
- method: 'message_format',
- args: [ids_to_load],
- context: session.user_context,
- })
- .then(function (msgs) {
- var processed_msgs = [];
- _.each(msgs, function (msg) {
- processed_msgs.push(add_message(msg, {silent: true}));
- });
- return _.sortBy(loaded_msgs.concat(processed_msgs), function (msg) {
- return msg.id;
- });
- });
- } else {
- return $.when(loaded_msgs);
- }
- },
-
- post_message: function (data, options) {
- var self = this;
- options = options || {};
-
- // This message will be received from the mail composer as html content subtype
- // but the urls will not be linkified. If the mail composer takes the responsibility
- // to linkify the urls we end up with double linkification a bit everywhere.
- // Ideally we want to keep the content as text internally and only make html
- // enrichment at display time but the current design makes this quite hard to do.
- var body = utils.parse_and_transform(_.str.trim(data.content), utils.add_link);
-
- var msg = {
- partner_ids: data.partner_ids,
- body: body,
- attachment_ids: data.attachment_ids,
- };
-
- // Replace emojis by their unicode character
- _.each(_.keys(emoji_unicodes), function (key) {
- var escaped_key = String(key).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1');
- var regexp = new RegExp("(\\s|^)(" + escaped_key + ")(?=\\s|$)", "g");
- msg.body = msg.body.replace(regexp, "$1" + emoji_unicodes[key]);
- });
-
- if ('subject' in data) {
- msg.subject = data.subject;
- }
- if ('channel_id' in options) {
- // post a message in a channel or execute a command
- return this._rpc({
- model: 'mail.channel',
- method: data.command ? 'execute_command' : 'message_post',
- args: [options.channel_id],
- kwargs: _.extend(msg, {
- message_type: 'comment',
- content_subtype: 'html',
- subtype: 'mail.mt_comment',
- command: data.command,
- }),
- });
- }
- if ('model' in options && 'res_id' in options) {
- // post a message in a chatter
- _.extend(msg, {
- content_subtype: data.content_subtype,
- context: data.context,
- message_type: data.message_type,
- subtype: data.subtype,
- subtype_id: data.subtype_id,
- });
-
- return this._rpc({
- model: options.model,
- method: 'message_post',
- args: [options.res_id],
- kwargs: msg,
- })
- .then(function (msg_id) {
- return self._rpc({
- model: 'mail.message',
- method: 'message_format',
- args: [msg_id],
- })
- .then(function (msgs) {
- msgs[0].model = options.model;
- msgs[0].res_id = options.res_id;
- add_message(msgs[0]);
- });
- });
- }
- },
-
- get_message: function (id) {
- return _.findWhere(messages, {id: id});
- },
- get_messages: function (options) {
- var channel;
-
- if ('channel_id' in options && options.load_more) {
- // get channel messages, force load_more
- channel = this.get_channel(options.channel_id);
- return this._fetchFromChannel(channel, {domain: options.domain || {}, load_more: true});
- }
- if ('channel_id' in options) {
- // channel message, check in cache first
- channel = this.get_channel(options.channel_id);
- var channel_cache = get_channel_cache(channel, options.domain);
- if (channel_cache.loaded) {
- return $.when(channel_cache.messages);
- } else {
- return this._fetchFromChannel(channel, {domain: options.domain});
- }
- }
- if ('ids' in options) {
- // get messages from their ids (chatter is the main use case)
- return this._fetchDocumentMessages(options.ids, options).then(function(result) {
- chat_manager.mark_as_read(options.ids);
- return result;
- });
- }
- if ('model' in options && 'res_id' in options) {
- // get messages for a chatter, when it doesn't know the ids (use
- // case is when using the full composer)
- var domain = [['model', '=', options.model], ['res_id', '=', options.res_id]];
- this._rpc({
- model: 'mail.message',
- method: 'message_fetch',
- args: [domain],
- kwargs: {limit: 30},
- })
- .then(function (msgs) {
- return _.map(msgs, add_message);
- });
- }
- },
- toggle_star_status: function (message_id) {
- return this._rpc({
- model: 'mail.message',
- method: 'toggle_message_starred',
- args: [[message_id]],
- });
- },
- unstar_all: function () {
- return this._rpc({
- model: 'mail.message',
- method: 'unstar_all',
- args: [[]]
- });
- },
- mark_as_read: function (message_ids) {
- var ids = _.filter(message_ids, function (id) {
- var message = _.findWhere(messages, {id: id});
- // If too many messages, not all are fetched, and some might not be found
- return !message || message.is_needaction;
- });
- if (ids.length) {
- return this._rpc({
- model: 'mail.message',
- method: 'set_message_done',
- args: [ids],
- });
- } else {
- return $.when();
- }
- },
- mark_all_as_read: function (channel, domain) {
- if ((channel.id === "channel_inbox" && needaction_counter) || (channel && channel.needaction_counter)) {
- return this._rpc({
- model: 'mail.message',
- method: 'mark_all_as_read',
- kwargs: {channel_ids: channel.id !== "channel_inbox" ? [channel.id] : [], domain: domain},
- });
- }
- return $.when();
- },
- undo_mark_as_read: function (message_ids, channel) {
- return this._rpc({
- model: 'mail.message',
- method: 'mark_as_unread',
- args: [message_ids, [channel.id]],
- });
- },
- mark_channel_as_seen: function (channel) {
- if (channel.unread_counter > 0 && channel.type !== 'static') {
- update_channel_unread_counter(channel, 0);
- this.channel_seen(channel);
- }
- },
-
- get_channels: function () {
- return _.clone(channels);
- },
-
- get_channel: function (id) {
- 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;
- },
-
- get_mention_partner_suggestions: function (channel) {
- if (!channel) {
- return mention_partner_suggestions;
- }
- if (!channel.members_deferred) {
- channel.members_deferred = this._rpc({
- model: 'mail.channel',
- method: 'channel_fetch_listeners',
- args: [channel.uuid],
- }, {
- shadow: true
- })
- .then(function (members) {
- var suggestions = [];
- _.each(mention_partner_suggestions, function (partners) {
- suggestions.push(_.filter(partners, function (partner) {
- return !_.findWhere(members, { id: partner.id });
- }));
- });
-
- return [members];
- });
- }
- return channel.members_deferred;
- },
-
- get_commands: function (channel) {
- return _.filter(commands, function (command) {
- return !command.channel_types || _.contains(command.channel_types, channel.server_type);
- });
- },
- get_canned_responses: function () {
- return canned_responses;
- },
-
- get_emojis: function() {
- return emojis;
- },
-
- get_needaction_counter: function () {
- return needaction_counter;
- },
- get_starred_counter: function () {
- return starred_counter;
- },
- get_chat_unread_counter: function () {
- return chat_unread_counter;
- },
- get_unread_conversation_counter: function () {
- return unread_conversation_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].is_author || messages[i].is_system_notification)) {
- msg = messages[i];
- i++;
- }
- return msg;
- }
- }
- },
-
- get_discuss_menu_id: function () {
- return discuss_menu_id;
- },
-
- detach_channel: function (channel) {
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_minimize',
- args: [channel.uuid, true],
- }, {
- shadow: true,
- });
- },
- remove_chatter_messages: function (model) {
- messages = _.reject(messages, function (message) {
- return message.channel_ids.length === 0 && message.model === model;
- });
- },
-
- create_channel: function (name, type) {
- var method = type === "dm" ? "channel_get" : "channel_create";
- var args = type === "dm" ? [[name]] : [name, type];
- var context = _.extend({isMobile: config.device.isMobile}, session.user_context);
- return this._rpc({
- model: 'mail.channel',
- method: method,
- args: args,
- kwargs: {context: context},
- })
- .then(add_channel);
- },
- join_channel: function (channel_id, options) {
- if (channel_id in channel_defs) {
- // prevents concurrent calls to channel_join_and_get_info
- return channel_defs[channel_id];
- }
- var channel = this.get_channel(channel_id);
- if (channel) {
- // channel already joined
- channel_defs[channel_id] = $.when(channel);
- } else {
- channel_defs[channel_id] = this._rpc({
- model: 'mail.channel',
- method: 'channel_join_and_get_info',
- args: [[channel_id]],
- })
- .then(function (result) {
- return add_channel(result, options);
- });
- }
- return channel_defs[channel_id];
- },
- open_and_detach_dm: function (partner_id) {
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_get_and_minimize',
- args: [[partner_id]],
- })
- .then(add_channel);
- },
- open_channel: function (channel) {
- chat_manager.bus.trigger(client_action_open ? 'open_channel' : 'detach_channel', channel);
- },
-
- unsubscribe: function (channel) {
- if (_.contains(['public', 'private'], channel.type)) {
- return this._rpc({
- model: 'mail.channel',
- method: 'action_unfollow',
- args: [[channel.id]],
- });
- } else {
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_pin',
- args: [channel.uuid, false],
- });
- }
- },
- close_chat_session: function (channel_id) {
- var channel = this.get_channel(channel_id);
- this._rpc({
- model: 'mail.channel',
- method: 'channel_fold',
- kwargs: {uuid : channel.uuid, state : 'closed'},
- }, {shadow: true});
- },
- fold_channel: function (channel_id, folded) {
- var args = {
- uuid: this.get_channel(channel_id).uuid,
- };
- if (_.isBoolean(folded)) {
- args.state = folded ? 'folded' : 'open';
- }
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_fold',
- kwargs: args,
- }, {shadow: true});
- },
- /**
- * Special redirection handling for given model and id
- *
- * If the model is res.partner, and there is a user associated with this
- * partner which isn't the current user, open the DM with this user.
- * Otherwhise, open the record's form view, if this is not the current user's.
- */
- redirect: function (res_model, res_id, dm_redirection_callback) {
- var self = this;
- var redirect_to_document = function (res_model, res_id, view_id) {
- web_client.do_action({
- type:'ir.actions.act_window',
- view_type: 'form',
- view_mode: 'form',
- res_model: res_model,
- views: [[view_id || false, 'form']],
- res_id: res_id,
- });
- };
- if (res_model === "res.partner") {
- var domain = [["partner_id", "=", res_id]];
- this._rpc({
- model: 'res.users',
- method: 'search',
- args: [domain],
- })
- .then(function (user_ids) {
- if (user_ids.length && user_ids[0] !== session.uid && dm_redirection_callback) {
- self.create_channel(res_id, 'dm').then(dm_redirection_callback);
- } else {
- redirect_to_document(res_model, res_id);
- }
- });
- } else {
- this._rpc({
- model: res_model,
- method: 'get_formview_id',
- args: [[res_id], session.user_context],
- })
- .then(function (view_id) {
- redirect_to_document(res_model, res_id, view_id);
- });
- }
- },
-
- get_channels_preview: function (channels) {
- var channels_preview = _.map(channels, function (channel) {
- var info;
- if (channel.channel_ids && _.contains(channel.channel_ids,"channel_inbox")) {
- // map inbox(mail_message) data with existing channel/chat template
- info = _.pick(channel, 'id', 'body', 'avatar_src', 'res_id', 'model', 'module_icon', 'subject','date', 'record_name', 'status', 'displayed_author', 'email_from', 'unread_counter');
- info.last_message = {
- body: info.body,
- date: info.date,
- displayed_author: info.displayed_author || info.email_from,
- };
- info.name = info.record_name || info.subject || info.displayed_author;
- info.image_src = info.module_icon || info.avatar_src;
- info.message_id = info.id;
- info.id = 'channel_inbox';
- return info;
- }
- info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
- info.last_message = channel.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;
- });
- var missing_channels = _.where(channels_preview, {last_message: undefined});
- if (!channels_preview_def) {
- if (missing_channels.length) {
- var missing_channel_ids = _.pluck(missing_channels, 'id');
- channels_preview_def = this._rpc({
- model: 'mail.channel',
- method: 'channel_fetch_preview',
- args: [missing_channel_ids],
- }, {
- shadow: true,
- });
- } else {
- channels_preview_def = $.when();
- }
- }
- return channels_preview_def.then(function (channels) {
- _.each(missing_channels, function (channel_preview) {
- var channel = _.findWhere(channels, {id: channel_preview.id});
- if (channel) {
- channel_preview.last_message = add_message(channel.last_message);
- }
- });
- // sort channels: 1. unread, 2. chat, 3. 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 - !!c1.last_message ||
- (c2.last_message && 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) {
- if (channel.last_message) {
- channel.last_message_preview = chat_manager.get_message_body_preview(channel.last_message.body);
- channel.last_message_date = channel.last_message.date.fromNow();
- }
- });
- return channels_preview;
- });
- },
- get_message_body_preview: function (message_body) {
- return utils.parse_and_transform(message_body, utils.inline);
- },
-
- search_partner: function (search_val, limit) {
- var def = $.Deferred();
- var values = [];
- // search among prefetched partners
- var search_regexp = new RegExp(_.str.escapeRegExp(utils.unaccent(search_val)), 'i');
- _.each(mention_partner_suggestions, function (partners) {
- if (values.length < limit) {
- values = values.concat(_.filter(partners, function (partner) {
- return session.partner_id !== partner.id && search_regexp.test(partner.name);
- })).splice(0, limit);
- }
- });
- if (!values.length) {
- // extend the research to all users
- def = this._rpc({
- model: 'res.partner',
- method: 'im_search',
- args: [search_val, limit || 20],
- }, {
- shadow: true,
- });
- } else {
- def = $.when(values);
- }
- return def.then(function (values) {
- var autocomplete_data = _.map(values, function (value) {
- return { id: value.id, value: value.name, label: value.name };
- });
- return _.sortBy(autocomplete_data, 'label');
- });
- },
-});
-
-var CallService = Class.extend(Mixins.EventDispatcherMixin, Mixins.ServiceProvider, {
- init: function () {
- Mixins.ServiceProvider.init.call(this);
- Mixins.EventDispatcherMixin.init.call(this);
- },
-});
-var chat_manager = new ChatManager(new CallService());
-chat_manager.start();
-
-return chat_manager;
-
-});
diff --git a/addons/mail/static/src/js/chat_mixin.js b/addons/mail/static/src/js/chat_mixin.js
deleted file mode 100644
index ec1b06a805e..00000000000
--- a/addons/mail/static/src/js/chat_mixin.js
+++ /dev/null
@@ -1,130 +0,0 @@
-odoo.define('mail.chat_mixin', function() {
-"use strict";
-
-
-var ChatMixin = {
- // return a deferred that resolves when chat manager is ready
- _chatReady: function() {
- var result;
- this.trigger_up('chat_manager_ready', {
- callback: function(def) { result = def;}
- });
- return $.when(result);
- },
- _getBus: function() {
- var result;
- this.trigger_up('get_bus', {
- callback: function(bus) { result = bus;}
- });
- return result;
- },
- _getCannedResponses: function() {
- var result;
- this.trigger_up('get_canned_responses', {
- callback: function(responses) { result = responses;}
- });
- return result;
- },
- _getEmojis: function() {
- var result;
- this.trigger_up('get_emojis', {
- callback: function(emojis) { result = emojis;}
- });
- return result;
- },
- _getMentionPartnerSuggestions: function(channel_id) {
- var result;
- this.trigger_up('get_mention_partner_suggestions', {
- channel_id: channel_id,
- callback: function(res) { result = res; },
- });
- return result;
- },
- _getMessages: function(options) {
- var result;
- this.trigger_up('get_messages', {
- options: options,
- callback: function(messages) { result = messages;}
- });
- return result ? result : $.when([]);
- },
- _joinChannel: function(channel_id) {
- var result;
- this.trigger_up('join_channel', {
- channel_id: channel_id,
- callback: function(def) { result = def; },
- });
- return $.when(result);
- },
- _postMessage: function(message, options) {
- var result;
- this.trigger_up('post_message', {
- message: message,
- options: options,
- callback: function(def) { result = def;}
- });
- return $.when(result);
- },
- _removeChatterMessages: function(model) {
- this.trigger_up('remove_chatter_messages', {
- model: model,
- });
- },
- _toggleStarStatus: function(message_id) {
- this.trigger_up('toggle_star_status', {
- message_id: message_id,
- });
- },
-};
-
-return ChatMixin;
-
-});
-
-odoo.define('mail.chat_service', function (require) {
-"use strict";
-
-var chat_manager = require('mail.chat_manager');
-var web_client = require('web.web_client');
-
-web_client.on('get_emojis', web_client, function(event) {
- event.data.callback(chat_manager.get_emojis());
-});
-
-web_client.on('get_canned_responses', web_client, function(event) {
- event.data.callback(chat_manager.get_canned_responses());
-});
-
-web_client.on('chat_manager_ready', web_client, function(event) {
- event.data.callback(chat_manager.is_ready);
-});
-
-web_client.on('get_messages', web_client, function(event) {
- event.data.callback(chat_manager.get_messages(event.data.options));
-});
-
-web_client.on('post_message', web_client, function(event) {
- event.data.callback(chat_manager.post_message(event.data.message, event.data.options));
-});
-
-web_client.on('get_bus', web_client, function(event) {
- event.data.callback(chat_manager.bus);
-});
-
-web_client.on('remove_chatter_messages', web_client, function(event) {
- chat_manager.remove_chatter_messages(event.data.model);
-});
-
-web_client.on('toggle_star_status', web_client, function(event) {
- chat_manager.toggle_star_status(event.data.message_id);
-});
-
-web_client.on('join_channel', web_client, function(event) {
- event.data.callback(chat_manager.join_channel(event.data.channel_id));
-});
-
-web_client.on('get_mention_partner_suggestions', web_client, function(event) {
- event.data.callback(chat_manager.get_mention_partner_suggestions(event.data.channel_id));
-});
-
-});
\ No newline at end of file
diff --git a/addons/mail/static/src/js/chat_window.js b/addons/mail/static/src/js/chat_window.js
index c8cfecd193f..9f09eb390b0 100644
--- a/addons/mail/static/src/js/chat_window.js
+++ b/addons/mail/static/src/js/chat_window.js
@@ -140,7 +140,9 @@ return Widget.extend({
};
this.$input.val('');
if (content) {
- this.trigger('post_message', message, this.channel_id);
+ this.trigger('post_message', message, {
+ channelID: this.channel_id,
+ });
}
}
},
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index 649a7381f7c..dfc041325d9 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -2,7 +2,6 @@ odoo.define('mail.Chatter', function (require) {
"use strict";
var Activity = require('mail.Activity');
-var chat_mixin = require('mail.chat_mixin');
var ChatterComposer = require('mail.ChatterComposer');
var Followers = require('mail.Followers');
var ThreadField = require('mail.ThreadField');
@@ -22,7 +21,7 @@ var QWeb = core.qweb;
// with each other.
// It synchronizes the rendering of those widgets (as they may be asynchronous), to limitate
// the flickering when switching between records
-var Chatter = Widget.extend(chat_mixin, {
+var Chatter = Widget.extend({
template: 'mail.Chatter',
custom_events: {
reload_mail_fields: '_onReloadMailFields',
@@ -52,7 +51,7 @@ var Chatter = Widget.extend(chat_mixin, {
// mention: get the prefetched partners and use them as mention suggestions
// if there is a follower widget, the followers will be added to the
// suggestions as well once fetched
- this.mentionPartnerSuggestions = this._getMentionPartnerSuggestions();
+ this.mentionPartnerSuggestions = this.call('chat_manager', 'getMentionPartnerSuggestions');
this.mentionSuggestions = this.mentionPartnerSuggestions;
this.fields = {};
diff --git a/addons/mail/static/src/js/chatter_composer.js b/addons/mail/static/src/js/chatter_composer.js
index fe23bc528fe..0a8088d87ae 100644
--- a/addons/mail/static/src/js/chatter_composer.js
+++ b/addons/mail/static/src/js/chatter_composer.js
@@ -1,7 +1,6 @@
odoo.define('mail.ChatterComposer', function (require) {
"use strict";
-// var chat_manager = require('mail.chat_manager');
var composer = require('mail.composer');
var utils = require('mail.utils');
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index 2e2aaa66f74..06675ec3902 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -1,7 +1,6 @@
odoo.define('mail.composer', function (require) {
"use strict";
-var chat_mixin = require('mail.chat_mixin');
var DocumentViewer = require('mail.DocumentViewer');
var utils = require('mail.utils');
@@ -385,7 +384,7 @@ var MentionManager = Widget.extend({
});
-var BasicComposer = Widget.extend(chat_mixin, {
+var BasicComposer = Widget.extend({
template: "mail.ChatComposer",
events: {
"keydown .o_composer_input": "_onKeydown",
@@ -482,7 +481,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
self.trigger('input_focused');
});
this.$input.html(this.options.default_body);
- this.$input.css('min-height', this.options.input_min_height)
+ this.$input.css('min-height', this.options.input_min_height);
// Attachments
$(window).on(this.fileupload_id, this.on_attachment_loaded);
@@ -728,7 +727,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
},
// remove mention when user try to edit or remove it.
_removeMention: function (event) {
- if (window.getSelection().anchorNode.parentNode.tagName == 'A') {
+ if (window.getSelection().anchorNode.parentNode.tagName === 'A') {
document.getElementById(window.getSelection().anchorNode.parentNode.id).remove();
}
},
@@ -790,8 +789,8 @@ var BasicComposer = Widget.extend(chat_mixin, {
var self = this;
var def = $.Deferred();
clearTimeout(this.canned_timeout);
- this.canned_timeout = setTimeout(function() {
- var canned_responses = self._getCannedResponses();
+ this.canned_timeout = setTimeout(function () {
+ var canned_responses = self.call('chat_manager', 'getCannedResponses');
var matches = fuzzy.filter(utils.unaccent(search), _.pluck(canned_responses, 'source'));
var indexes = _.pluck(matches.slice(0, self.options.mention_fetch_limit), 'index');
def.resolve(_.map(indexes, function (i) {
@@ -876,7 +875,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
_onEmojiButtonClick: function () {
if (!this.$emojisContainer) { // lazy rendering
this.$emojisContainer = $(QWeb.render('mail.ChatComposer.emojis', {
- emojis: this._getEmojis(),
+ emojis: this.call('chat_manager', 'getEmojis'),
}));
}
if (this.$emojisContainer.parent().length) {
@@ -938,7 +937,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
*/
_onEmojiImageClick: function (ev) {
this.$input.html(this.$input.html() + " " + $(ev.currentTarget).data('emoji') + " ");
- this._placeCaretAtEnd(this.$input[0])
+ this._placeCaretAtEnd(this.$input[0]);
this._hideEmojis();
},
});
diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/discuss.js
similarity index 73%
rename from addons/mail/static/src/js/client_action.js
rename to addons/mail/static/src/js/discuss.js
index d2299259486..4de7e637e8b 100644
--- a/addons/mail/static/src/js/client_action.js
+++ b/addons/mail/static/src/js/discuss.js
@@ -1,10 +1,8 @@
-odoo.define('mail.chat_client_action', function (require) {
+odoo.define('mail.chat_discuss', function (require) {
"use strict";
-var chat_manager = require('mail.chat_manager');
var ChatThread = require('mail.ChatThread');
var composer = require('mail.composer');
-var utils = require('mail.utils');
var config = require('web.config');
var ControlPanelMixin = require('web.ControlPanelMixin');
@@ -33,9 +31,11 @@ var PartnerInviteDialog = Dialog.extend({
/**
* @override
+ * @param {integer|string} channelID id of the channel,
+ * a string for static channels (e.g. 'channel_inbox').
*/
- init: function (parent, title, channel_id) {
- this.channel_id = channel_id;
+ init: function (parent, title, channelID) {
+ this.channelID = channelID;
this._super(parent, {
title: title,
@@ -51,7 +51,8 @@ var PartnerInviteDialog = Dialog.extend({
/**
* @override
*/
- start: function (){
+ start: function () {
+ var self = this;
this.$input = this.$('.o_mail_chat_partner_invite_input');
this.$input.select2({
width: '100%',
@@ -62,7 +63,7 @@ var PartnerInviteDialog = Dialog.extend({
return $('
').text(item.text).prepend(status);
},
query: function (query) {
- chat_manager.search_partner(query.term, 20).then(function (partners) {
+ self.call('chat_manager', 'searchPartner', query.term, 20).then(function (partners) {
query.callback({
results: _.map(partners, function (partner) {
return _.extend(partner, { text: partner.label });
@@ -80,7 +81,7 @@ var PartnerInviteDialog = Dialog.extend({
/**
* @private
- * @returns {Deferred}
+ * @returns {$.Promise}
*/
_addChannel: function () {
var self = this;
@@ -89,22 +90,23 @@ var PartnerInviteDialog = Dialog.extend({
return this._rpc({
model: 'mail.channel',
method: 'channel_invite',
- args: [this.channel_id],
+ args: [this.channelID],
kwargs: {partner_ids: _.pluck(data, 'id')},
}).then(function () {
var names = _.escape(_.pluck(data, 'text').join(', '));
var notification = _.str.sprintf(_t('You added %s to the conversation.'), names);
self.do_notify(_t('New people'), notification);
- // Clear the members_deferred to fetch again the partner
- // when get_mention_partner_suggestions from the chat_manager is triggered
- delete chat_manager.get_channel(self.channel_id).members_deferred;
+ // Clear the membersDeferred to fetch again the partner
+ // when getMentionPartnerSuggestions from the chatManager is triggered
+ var channel = this.call('chat_manager', 'getChannel', self.channelID);
+ delete channel.membersDeferred;
});
}
},
});
-var ChatAction = Widget.extend(ControlPanelMixin, {
- template: 'mail.client_action',
+var Discuss = Widget.extend(ControlPanelMixin, {
+ template: 'mail.discuss',
custom_events: {
search: '_onSearch',
},
@@ -129,7 +131,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
this.domain = [];
this.action = action;
this.options = options || {};
- this.channels_scrolltop = {};
+ this.channelsScrolltop = {};
this.throttledUpdateChannels = _.throttle(this._updateChannels.bind(this), 100, { leading: false });
this.notification_bar = (window.Notification && window.Notification.permission === "default");
this.selected_message = null;
@@ -144,13 +146,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
*/
willStart: function () {
var self = this;
- var view_id = this.action && this.action.search_view_id && this.action.search_view_id[0];
+ var viewID = this.action && this.action.search_view_id && this.action.search_view_id[0];
var def = this
- .loadFieldView(this.dataset, view_id, 'search')
+ .loadFieldView(this.dataset, viewID, 'search')
.then(function (fields_view) {
self.fields_view = fields_view;
});
- return $.when(this._super(), chat_manager.is_ready, def);
+ var chatReady = this.call('chat_manager', 'isReady');
+ return $.when(this._super(), chatReady, def);
},
/**
* @override
@@ -158,30 +161,31 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
start: function () {
var self = this;
- var default_channel = chat_manager.get_channel(this.defaultChannelID) ||
- chat_manager.get_channel('channel_inbox');
- this.basic_composer = new composer.BasicComposer(this, {mention_partners_restricted: true});
- this.extended_composer = new composer.ExtendedComposer(this, {mention_partners_restricted: true});
- this.basic_composer.on('post_message', this, this._onPostMessage);
- this.basic_composer.on('input_focused', this, this._onComposerFocused);
- this.extended_composer.on('post_message', this, this._onPostMessage);
- this.extended_composer.on('input_focused', this, this._onComposerFocused);
+ var defaultChannel = this.call('chat_manager', 'getChannel', this.defaultChannelID) ||
+ this.call('chat_manager', 'getChannel', 'channel_inbox');
+
+ this.basicComposer = new composer.BasicComposer(this, {mention_partners_restricted: true});
+ this.extendedComposer = new composer.ExtendedComposer(this, {mention_partners_restricted: true});
+ this.basicComposer.on('post_message', this, this._onPostMessage);
+ this.basicComposer.on('input_focused', this, this._onComposerFocused);
+ this.extendedComposer.on('post_message', this, this._onPostMessage);
+ this.extendedComposer.on('input_focused', this, this._onComposerFocused);
var defs = [];
defs.push(this._renderButtons());
defs.push(this._renderThread());
- defs.push(this.basic_composer.appendTo(this.$('.o_mail_chat_content')));
- defs.push(this.extended_composer.appendTo(this.$('.o_mail_chat_content')));
+ defs.push(this.basicComposer.appendTo(this.$('.o_mail_chat_content')));
+ defs.push(this.extendedComposer.appendTo(this.$('.o_mail_chat_content')));
defs.push(this._renderSearchView());
return $.when.apply($, defs)
- .then(this._setChannel.bind(this, default_channel))
+ .then(this._setChannel.bind(this, defaultChannel))
.then(this._updateChannels.bind(this))
.then(function () {
self._startListening();
self.thread.$el.on("scroll", null, _.debounce(function () {
if (self.thread.is_at_bottom()) {
- chat_manager.mark_channel_as_seen(self.channel);
+ self.call('chat_manager', 'markChannelAsSeen', self.channel);
}
}, 100));
});
@@ -210,17 +214,19 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
* @override
*/
on_attach_callback: function () {
- chat_manager.bus.trigger('client_action_open', true);
+ var chatBus = this.call('chat_manager', 'getChatBus');
+ chatBus.trigger('discuss_open', true);
if (this.channel) {
- this.thread.scroll_to({offset: this.channels_scrolltop[this.channel.id]});
+ this.thread.scroll_to({offset: this.channelsScrolltop[this.channel.id]});
}
},
/**
* @override
*/
on_detach_callback: function () {
- chat_manager.bus.trigger('client_action_open', false);
- this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop();
+ var chatBus = this.call('chat_manager', 'getChatBus');
+ chatBus.trigger('discuss_open', false);
+ this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop();
},
//--------------------------------------------------------------------------
@@ -229,14 +235,17 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
/**
* @private
- * @returns {Deferred}
+ * @returns {$.Promise}
*/
_fetchAndRenderThread: 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._getThreadRenderingOptions(result));
- self._updateButtonStatus(result.length === 0);
- });
+ return this.call('chat_manager', 'getMessages', {
+ channelID: this.channel.id,
+ domain: this.domain
+ }).then(function (messages) {
+ self.thread.render(messages, self._getThreadRenderingOptions(messages));
+ self._updateButtonStatus(messages.length === 0);
+ });
},
/**
* @private
@@ -250,13 +259,13 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
if (!this.unread_counter) {
this.messages_separator_position = false; // no unread message -> don't display separator
} else {
- var msg = chat_manager.get_last_seen_message(this.channel);
+ var msg = this.call('chat_manager', 'getLastSeenMessage', this.channel);
this.messages_separator_position = msg ? msg.id : 'top';
}
}
return {
channel_id: this.channel.id,
- display_load_more: !chat_manager.all_history_loaded(this.channel, this.domain),
+ display_load_more: !this.call('chat_manager', 'isAllHistoryLoaded', this.channel, this.domain),
display_needactions: this.channel.display_needactions,
messages_separator_position: this.messages_separator_position,
squash_close_messages: this.channel.type !== 'static' && !this.channel.mass_mailing,
@@ -268,22 +277,27 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
};
},
/**
+ * Load more messages for the current thread
+ *
* @private
- * @returns {Deferred}
+ * @returns {$.Promise}
*/
_loadMoreMessages: function () {
var self = this;
- var oldest_msg_id = this.$('.o_thread_message').first().data('messageId');
- var oldest_msg_selector = '.o_thread_message[data-message-id="' + oldest_msg_id + '"]';
- var offset = -dom.getPosition(document.querySelector(oldest_msg_selector)).top;
- return chat_manager
- .get_messages({channel_id: this.channel.id, domain: this.domain, load_more: true})
- .then(function (result) {
+ var oldestMsgID = this.$('.o_thread_message').first().data('messageId');
+ var oldestMsgSelector = '.o_thread_message[data-message-id="' + oldestMsgID + '"]';
+ var offset = -dom.getPosition(document.querySelector(oldestMsgSelector)).top;
+ return this.call('chat_manager', 'getMessages', {
+ channelID: this.channel.id,
+ domain: this.domain,
+ loadMore: true
+ })
+ .then(function (messages) {
if (self.messages_separator_position === 'top') {
self.messages_separator_position = undefined; // reset value to re-compute separator position
}
- self.thread.render(result, self._getThreadRenderingOptions(result));
- offset += dom.getPosition(document.querySelector(oldest_msg_selector)).top;
+ self.thread.render(messages, self._getThreadRenderingOptions(messages));
+ offset += dom.getPosition(document.querySelector(oldestMsgSelector)).top;
self.thread.scroll_to({offset: offset});
});
},
@@ -313,9 +327,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
select: function (event, ui) {
if (self.last_search_val) {
if (ui.item.value === '_create') {
- chat_manager.create_channel(self.last_search_val, "public");
+ self.call('chat_manager', 'createChannel', self.last_search_val, "public");
} else {
- chat_manager.join_channel(ui.item.id);
+ self.call('chat_manager', 'joinChannel', ui.item.id);
}
}
},
@@ -327,23 +341,23 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
} else if (type === 'private') {
$input.on('keyup', this, function (event) {
var name = _.escape($(event.target).val());
- if(event.which === $.ui.keyCode.ENTER && name) {
- chat_manager.create_channel(name, "private");
+ if (event.which === $.ui.keyCode.ENTER && name) {
+ self.call('chat_manager', 'createChannel', name, "private");
}
});
} else if (type === 'dm') {
$input.autocomplete({
source: function (request, response) {
self.last_search_val = _.escape(request.term);
- chat_manager.search_partner(self.last_search_val, 10).done(response);
+ self.call('chat_manager', 'searchPartner', self.last_search_val, 10).done(response);
},
select: function (event, ui) {
var partner_id = ui.item.id;
- var dm = chat_manager.get_dm_from_partner_id(partner_id);
+ var dm = self.call('chat_manager', 'getDmFromPartnerID', partner_id);
if (dm) {
self._setChannel(dm);
} else {
- chat_manager.create_channel(partner_id, "dm");
+ self.call('chat_manager', 'createChannel', partner_id, "dm");
}
// clear the input
$(this).val('');
@@ -418,20 +432,21 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
* @returns {Deferred}
*/
_renderThread: function () {
+ var self = this;
this.thread = new ChatThread(this, {display_help: true});
- this.thread.on('redirect', this, function (res_model, res_id) {
- chat_manager.redirect(res_model, res_id, this._setChannel.bind(this));
+ this.thread.on('redirect', this, function (resModel, resID) {
+ self.call('chat_manager', 'redirect', resModel, resID, self._setChannel.bind(self));
});
- this.thread.on('redirect_to_channel', this, function (channel_id) {
- chat_manager.join_channel(channel_id).then(this._setChannel.bind(this));
+ this.thread.on('redirect_to_channel', this, function (channelID) {
+ self.call('chat_manager', 'joinChannel', channelID).then(this._setChannel.bind(this));
});
this.thread.on('load_more_messages', this, this._loadMoreMessages);
- this.thread.on('mark_as_read', this, function (message_id) {
- chat_manager.mark_as_read([message_id]);
+ this.thread.on('mark_as_read', this, function (messageID) {
+ self.call('chat_manager', 'markAsRead', [messageID]);
});
- this.thread.on('toggle_star_status', this, function (message_id) {
- chat_manager.toggle_star_status(message_id);
+ this.thread.on('toggle_star_status', this, function (messageID) {
+ self.call('chat_manager', 'toggleStarStatus', messageID);
});
this.thread.on('select_message', this, this._selectMessage);
this.thread.on('unselect_message', this, this._unselectMessage);
@@ -444,12 +459,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
* @private
*/
_restoreChannelState: function () {
- var $new_messages_separator = this.$('.o_thread_new_messages_separator');
- if ($new_messages_separator.length) {
- this.thread.$el.scrollTo($new_messages_separator);
+ var $newMessagesSeparator = this.$('.o_thread_new_messages_separator');
+ if ($newMessagesSeparator.length) {
+ this.thread.$el.scrollTo($newMessagesSeparator);
} else {
- var new_channel_scrolltop = this.channels_scrolltop[this.channel.id];
- this.thread.scroll_to({offset: new_channel_scrolltop});
+ var newChannelScrolltop = this.channelsScrolltop[this.channel.id];
+ this.thread.scroll_to({offset: newChannelScrolltop});
}
this._restoreComposerState(this.channel);
},
@@ -461,7 +476,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
if (channel.type === 'static') {
return; // no composer in static channels
}
- var composer = channel.mass_mailing ? this.extended_composer : this.basic_composer;
+ var composer = channel.mass_mailing ? this.extendedComposer : this.basicComposer;
var composerState = this.composerStates[channel.uuid];
if (composerState) {
composer.setState(composerState);
@@ -469,14 +484,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
},
/**
* @private
- * @param {string} search_val
- * @returns {Deferred}
+ * @param {string} searchVal
+ * @returns {$.Promise}
*/
- _searchChannel: function (search_val){
+ _searchChannel: function (searchVal){
return this._rpc({
model: 'mail.channel',
method: 'channel_search_to_join',
- args: [search_val]
+ args: [searchVal]
})
.then(function (result){
var values = [];
@@ -492,27 +507,27 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
},
/**
* @private
- * @param {integer} message_id
+ * @param {integer} messageID
*/
- _selectMessage: function (message_id) {
+ _selectMessage: function (messageID) {
this.$el.addClass('o_mail_selection_mode');
- var message = chat_manager.get_message(message_id);
+ var message = this._getMessage(messageID);
this.selected_message = message;
var subject = "Re: " + message.record_name;
- this.extended_composer.set_subject(subject);
+ this.extendedComposer.set_subject(subject);
if (this.channel.type !== 'static') {
- this.basic_composer.do_hide();
+ this.basicComposer.do_hide();
}
- this.extended_composer.do_show();
+ this.extendedComposer.do_show();
- this.thread.scroll_to({id: message_id, duration: 200, only_if_necessary: true});
- this.extended_composer.focus('body');
+ this.thread.scroll_to({id: messageID, duration: 200, only_if_necessary: true});
+ this.extendedComposer.focus('body');
},
/**
* @private
* @param {Object} channel
- * @returns {Deferred}
+ * @returns {$.Promise}
*/
_setChannel: function (channel) {
var self = this;
@@ -534,9 +549,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
return this._fetchAndRenderThread().then(function () {
// Mark channel's messages as read and clear needactions
if (channel.type !== 'static') {
- chat_manager.mark_channel_as_seen(channel);
+ self.call('chat_manager', 'markChannelAsSeen', channel);
}
-
// Restore scroll position and composer of the new current channel
self._restoreChannelState();
@@ -556,25 +570,26 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
});
},
/**
- * Binds handlers on chat_manager events
+ * Binds handlers on chatManager events
*
* @private
*/
_startListening: function () {
- chat_manager.bus.on('open_channel', this, this._setChannel);
- chat_manager.bus.on('new_message', this, this._onNewMessage);
- chat_manager.bus.on('update_message', this, this._onMessageUpdated);
- chat_manager.bus.on('new_channel', this, this._onNewChannel);
- chat_manager.bus.on('anyone_listening', this, function (channel, query) {
+ var chatBus = this.call('chat_manager', 'getChatBus');
+ chatBus.on('open_channel', this, this._setChannel);
+ chatBus.on('new_message', this, this._onNewMessage);
+ chatBus.on('update_message', this, this._onMessageUpdated);
+ chatBus.on('new_channel', this, this._onNewChannel);
+ chatBus.on('anyone_listening', this, function (channel, query) {
query.is_displayed = query.is_displayed ||
(channel.id === this.channel.id && this.thread.is_at_bottom());
});
- chat_manager.bus.on('unsubscribe_from_channel', this, this._onChannelLeft);
- chat_manager.bus.on('update_needaction', this, this.throttledUpdateChannels);
- chat_manager.bus.on('update_starred', this, this.throttledUpdateChannels);
- chat_manager.bus.on('update_channel_unread_counter', this, this.throttledUpdateChannels);
- chat_manager.bus.on('update_dm_presence', this, this.throttledUpdateChannels);
- chat_manager.bus.on('activity_updated', this, this.throttledUpdateChannels);
+ chatBus.on('unsubscribe_from_channel', this, this._onChannelLeft);
+ chatBus.on('update_needaction', this, this.throttledUpdateChannels);
+ chatBus.on('update_starred', this, this.throttledUpdateChannels);
+ chatBus.on('update_channel_unread_counter', this, this.throttledUpdateChannels);
+ chatBus.on('update_dm_presence', this, this.throttledUpdateChannels);
+ chatBus.on('activity_updated', this, this.throttledUpdateChannels);
},
/**
* Stores the scroll position and composer state of the current channel
@@ -583,7 +598,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
*/
_storeChannelState: function () {
if (this.channel) {
- this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop();
+ this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop();
this._storeComposerState(this.channel);
}
},
@@ -595,7 +610,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
if (channel.type === 'static') {
return; // no composer in static channels
}
- var composer = channel.mass_mailing ? this.extended_composer : this.basic_composer;
+ var composer = channel.mass_mailing ? this.extendedComposer : this.basicComposer;
this.composerStates[channel.uuid] = composer.getState();
composer.clear_composer();
},
@@ -603,11 +618,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
* @private
*/
_unselectMessage: function () {
- this.basic_composer.do_toggle(this.channel.type !== 'static' && !this.channel.mass_mailing);
- this.extended_composer.do_toggle(this.channel.type !== 'static' && this.channel.mass_mailing);
+ this.basicComposer.do_toggle(this.channel.type !== 'static' && !this.channel.mass_mailing);
+ this.extendedComposer.do_toggle(this.channel.type !== 'static' && this.channel.mass_mailing);
if (!config.device.touch) {
- var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer;
+ var composer = this.channel.mass_mailing ? this.extendedComposer : this.basicComposer;
composer.focus();
}
this.$el.removeClass('o_mail_selection_mode');
@@ -623,11 +638,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
var self = this;
var $sidebar = this._renderSidebar({
active_channel_id: this.channel ? this.channel.id: undefined,
- channels: chat_manager.get_channels(),
- needaction_counter: chat_manager.get_needaction_counter(),
- starred_counter: chat_manager.get_starred_counter(),
+ channels: this.call('chat_manager', 'getChannels'),
+ needaction_counter: this.call('chat_manager', 'getNeedactionCounter'),
+ starred_counter: this.call('chat_manager', 'getStarredCounter'),
});
- this.$(".o_mail_chat_sidebar").html($sidebar.contents());
+ self.$(".o_mail_chat_sidebar").html($sidebar.contents());
_.each(['dm', 'public', 'private'], function (type) {
var $input = self.$('.o_mail_add_channel[data-type=' + type + '] input');
self._prepareAddChannelInput($input, type);
@@ -729,27 +744,29 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
*/
_onChannelClicked: function (event) {
event.preventDefault();
- var channel_id = $(event.currentTarget).data('channel-id');
- this._setChannel(chat_manager.get_channel(channel_id));
+ var channelID = $(event.currentTarget).data('channel-id');
+ var channel = this.call('chat_manager', 'getChannel', channelID);
+ this._setChannel(channel);
},
/**
* @private
- * @param {integer || string} channel_id
+ * @param {integer|string} channelID
*/
- _onChannelLeft: function (channel_id) {
- if (this.channel.id === channel_id) {
- this._setChannel(chat_manager.get_channel("channel_inbox"));
+ _onChannelLeft: function (channelID) {
+ if (this.channel.id === channelID) {
+ var channel = this.call('chat_manager', 'getChannel', 'channel_inbox');
+ this._setChannel(channel);
}
this._updateChannels();
- delete this.channels_scrolltop[channel_id];
+ delete this.channelsScrolltop[channelID];
},
/**
* @private
*/
_onComposerFocused: function () {
- var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer;
- var commands = chat_manager.get_commands(this.channel);
- var partners = chat_manager.get_mention_partner_suggestions(this.channel);
+ var composer = this.channel.mass_mailing ? this.extendedComposer : this.basicComposer;
+ var commands = this.call('chat_manager', 'getCommands', this.channel);
+ var partners = this.call('chat_manager', 'getMentionPartnerSuggestions', this.channel);
composer.mention_set_enabled_commands(commands);
composer.mention_set_prefetched_partners(partners);
},
@@ -757,7 +774,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
* @private
*/
_onMarkAllReadClicked: function () {
- chat_manager.mark_all_as_read(this.channel, this.domain);
+ this.call('chat_manager', 'markAllAsRead', this.channel, this.domain);
},
/**
* @private
@@ -766,16 +783,20 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
*/
_onMessageUpdated: function (message, type) {
var self = this;
- var current_channel_id = this.channel.id;
- 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._getThreadRenderingOptions(messages);
- self.thread.remove_message_and_render(message.id, messages, options).then(function () {
- self._updateButtonStatus(messages.length === 0, type);
+ var currentChannelID = this.channel.id;
+ if ((currentChannelID === "channel_starred" && !message.is_starred) ||
+ (currentChannelID === "channel_inbox" && !message.is_needaction)) {
+ this.call('chat_manager', 'getMessages', {
+ channelID: this.channel.id,
+ domain: this.domain
+ }).then(function (messages) {
+ var options = self._getThreadRenderingOptions(messages);
+ self.thread.remove_message_and_render(message.id, messages, options).then(function () {
+ self._updateButtonStatus(messages.length === 0, type);
+
+ });
});
- });
- } else if (_.contains(message.channel_ids, current_channel_id)) {
+ } else if (_.contains(message.channel_ids, currentChannelID)) {
this._fetchAndRenderThread();
}
},
@@ -797,9 +818,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
var self = this;
if (_.contains(message.channel_ids, this.channel.id)) {
if (this.channel.type !== 'static' && this.thread.is_at_bottom()) {
- chat_manager.mark_channel_as_seen(this.channel);
+ this.call('chat_manager', 'markChannelAsSeen', this.channel);
}
-
var should_scroll = this.thread.is_at_bottom();
this._fetchAndRenderThread().then(function () {
if (should_scroll) {
@@ -810,7 +830,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
// Re-render sidebar to indicate that there is a new message in the corresponding channels
this._updateChannels();
// Dump scroll position of channels in which the new message arrived
- this.channels_scrolltop = _.omit(this.channels_scrolltop, message.channel_ids);
+ this.channelsScrolltop = _.omit(this.channelsScrolltop, message.channel_ids);
},
/**
* @private
@@ -818,18 +838,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
*/
_onPostMessage: function (message) {
var self = this;
- var options = this.selected_message ? {} : {channel_id: this.channel.id};
+ var options = this.selected_message ? {} : {channelID: this.channel.id};
if (this.selected_message) {
message.subtype = this.selected_message.is_note ? 'mail.mt_note': 'mail.mt_comment';
message.subtype_id = false;
message.message_type = 'comment';
message.content_subtype = 'html';
-
- options.model = this.selected_message.model;
- options.res_id = this.selected_message.res_id;
}
- chat_manager
- .post_message(message, options)
+ this.call('chat_manager', 'postMessage', message, options)
.then(function () {
if (self.selected_message) {
self._renderSnackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000);
@@ -890,10 +906,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
if (def) {
def.then(function (value) {
if (value === 'denied') {
- utils.send_notification(self, _t('Permission denied'),
+ self._sendNotification(self, _t('Permission denied'),
_t('Odoo will not have the permission to send native notifications on this device.'));
} else {
- utils.send_notification(self, _t('Permission granted'),
+ self._sendNotification(self, _t('Permission granted'),
_t('Odoo has now the permission to send you native notifications on this device.'));
}
});
@@ -936,25 +952,26 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
*/
_onUnpinChannel: function (event) {
event.stopPropagation();
- var channel_id = $(event.target).data("channel-id");
- chat_manager.unsubscribe(chat_manager.get_channel(channel_id));
+ var channelID = $(event.target).data("channel-id");
+ var channel = this.call('chat_manager', 'getChannel', channelID);
+ this.call('chat_manager', 'unsubscribe', channel);
},
/**
* @private
*/
_onUnstarAllClicked: function () {
- chat_manager.unstar_all();
+ this.call('chat_manager', 'unstarAll');
},
/**
* @private
*/
_onUnsubscribeButtonClicked: function () {
- chat_manager.unsubscribe(this.channel);
+ this._unsubscribe(this.channel);
},
});
-core.action_registry.add('mail.chat.instant_messaging', ChatAction);
+core.action_registry.add('mail.chat.instant_messaging', Discuss);
-return ChatAction;
+return Discuss;
});
diff --git a/addons/mail/static/src/js/client_action_mobile.js b/addons/mail/static/src/js/discuss_mobile.js
similarity index 88%
rename from addons/mail/static/src/js/client_action_mobile.js
rename to addons/mail/static/src/js/discuss_mobile.js
index ecabb94e1ff..52622b0ce32 100644
--- a/addons/mail/static/src/js/client_action_mobile.js
+++ b/addons/mail/static/src/js/discuss_mobile.js
@@ -1,12 +1,10 @@
-odoo.define('mail.chat_client_action_mobile', function (require) {
+odoo.define('mail.chat_discuss_mobile', function (require) {
"use strict";
-var ChatAction = require('mail.chat_client_action');
-var chat_manager = require('mail.chat_manager');
+var Discuss = require('mail.chat_discuss');
var config = require('web.config');
var core = require('web.core');
-var session = require('web.session');
var QWeb = core.qweb;
@@ -14,10 +12,10 @@ if (!config.device.isMobile) {
return;
}
-ChatAction.include({
- template: 'mail.client_action_mobile',
+Discuss.include({
+ template: 'mail.discuss_mobile',
need_control_panel: false, // in mobile, we use a custom control panel
- events: _.extend(ChatAction.prototype.events, {
+ events: _.extend(Discuss.prototype.events, {
'click .o_mail_mobile_tab': '_onMobileTabClicked',
'click .o_channel_inbox_item': '_onMobileInboxButtonClicked',
'click .o_mail_channel_preview': '_onMobileChannelClicked',
@@ -103,10 +101,11 @@ ChatAction.include({
/**
* @override
* @private
+ * @param {Object} channel
*/
_setChannel: function (channel) {
if (channel.type !== 'static') {
- chat_manager.detach_channel(channel);
+ this.call('chat_manager', 'detachChannel', channel.id);
} else {
this._super.apply(this, arguments);
}
@@ -170,8 +169,9 @@ ChatAction.include({
if (inInbox) {
def = this._fetchAndRenderThread();
} else {
- var channels = _.where(chat_manager.get_channels(), {type: type});
- def = chat_manager.get_channels_preview(channels);
+ var allChannels = this.call('chat_manager', 'getChannels');
+ var channels = _.where(allChannels, {type: type});
+ def = this.call('chat_manager', 'getChannelsPreview', channels);
}
return $.when(def).then(function (channelsPreview) {
// update content
@@ -179,19 +179,15 @@ ChatAction.include({
if (!previouslyInInbox) {
self.$('.o_mail_chat_tab_pane').remove();
self.$mainContent.append(self.thread.$el);
- self.$mainContent.append(self.extended_composer.$el);
+ self.$mainContent.append(self.extendedComposer.$el);
}
self._restoreChannelState();
} else {
self.thread.$el.detach();
- self.extended_composer.$el.detach();
+ self.extendedComposer.$el.detach();
var $content = $(QWeb.render("mail.chat.MobileTabPane", {
channels: channelsPreview,
- get_message_body_preview: chat_manager.get_message_body_preview,
- moment: moment,
- partner_id: session.partner_id,
type: type,
- widget: self,
}));
self._prepareAddChannelInput($content.find('.o_mail_add_channel input'), type);
self.$mainContent.html($content);
@@ -252,7 +248,8 @@ ChatAction.include({
* @param {MouseEvent}
*/
_onMobileInboxButtonClicked: function (event) {
- this._setChannel(chat_manager.get_channel($(event.currentTarget).data('type')));
+ var channel = this.call('chat_manager', 'getChannel', $(event.currentTarget).data('type'));
+ this._setChannel(channel);
this._updateContent(this.channel.id);
},
/**
@@ -264,7 +261,8 @@ ChatAction.include({
_onMobileTabClicked: function (event) {
var type = $(event.currentTarget).data('type');
if (type === 'channel_inbox') {
- this._setChannel(chat_manager.get_channel('channel_inbox'));
+ var channel = this.call('chat_manager', 'getChannel', 'channel_inbox');
+ this._setChannel(channel);
}
this._updateContent(type);
},
@@ -275,8 +273,8 @@ ChatAction.include({
* @param {MouseEvent}
*/
_onMobileChannelClicked: function (event) {
- var channelId = $(event.currentTarget).data("channel_id");
- chat_manager.detach_channel(chat_manager.get_channel(channelId));
+ var channelID = $(event.currentTarget).data("channel_id");
+ this.call('chat_manager', 'detachChannel', channelID);
},
});
diff --git a/addons/mail/static/src/js/extended_chat_window.js b/addons/mail/static/src/js/extended_chat_window.js
index 2163c28dd92..a8aa5673b81 100644
--- a/addons/mail/static/src/js/extended_chat_window.js
+++ b/addons/mail/static/src/js/extended_chat_window.js
@@ -1,9 +1,6 @@
odoo.define('mail.ExtendedChatWindow', function (require) {
"use strict";
-var core = require('web.core');
-
-var chat_manager = require('mail.chat_manager');
var ChatWindow = require('mail.ChatWindow');
var composer = require('mail.composer');
@@ -19,27 +16,29 @@ return ChatWindow.extend({
this.$el.addClass('o_thread_less');
this.$('.o_chat_search_input input')
.autocomplete({
- source: function(request, response) {
- chat_manager.search_partner(request.term, 10).done(response);
+ source: function (request, response) {
+ self.call('chat_manager', 'searchPartner', request.term, 10).done(response);
},
- select: function(event, ui) {
+ select: function (event, ui) {
self.trigger('open_dm_session', ui.item.id);
},
})
.focus();
} else if (!self.options.input_less) {
- var basic_composer = new composer.BasicComposer(self, {mention_partners_restricted: true, isMini: true});
- basic_composer.on('post_message', self, function (message) {
- this.trigger('post_message', message, this.channel_id);
+ var basicComposer = new composer.BasicComposer(self, {mention_partners_restricted: true, isMini: true});
+ basicComposer.on('post_message', self, function (message) {
+ self.call('chat_manager', 'postMessage', message, {
+ channelID: self.channel_id,
+ });
});
- basic_composer.once('input_focused', self, function () {
- var channel = chat_manager.get_channel(this.channel_id);
- var commands = chat_manager.get_commands(channel);
- var partners = chat_manager.get_mention_partner_suggestions(channel);
- basic_composer.mention_set_enabled_commands(commands);
- basic_composer.mention_set_prefetched_partners(partners);
+ basicComposer.once('input_focused', self, function () {
+ var channel = self.call('chat_manager', 'getChannel', self.channel_id);
+ var commands = self.call('chat_manager', 'getCommands', channel);
+ var partners = self.call('chat_manager', 'getMentionPartnerSuggestions', channel);
+ basicComposer.mention_set_enabled_commands(commands);
+ basicComposer.mention_set_prefetched_partners(partners);
});
- def = basic_composer.replace(self.$('.o_chat_composer'));
+ def = basicComposer.replace(self.$('.o_chat_composer'));
}
return $.when(this._super(), def);
},
@@ -49,7 +48,8 @@ return ChatWindow.extend({
event.stopPropagation();
},
on_reverse_breadcrumb: function () {
- chat_manager.bus.trigger('client_action_open', false);
+ var chatBus = this.call('chat_manager', 'getChatBus');
+ chatBus.trigger('discuss_open', false);
},
on_click_expand: _.debounce(function (event) {
event.preventDefault();
diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js
new file mode 100644
index 00000000000..b7c46f2d4c1
--- /dev/null
+++ b/addons/mail/static/src/js/services/chat_manager.js
@@ -0,0 +1,1751 @@
+odoo.define('mail.ChatManager', function (require) {
+"use strict";
+
+var utils = require('mail.utils');
+
+var AbstractService = require('web.AbstractService');
+var Bus = require('web.Bus');
+var config = require('web.config');
+var core = require('web.core');
+var session = require('web.session');
+var time = require('web.time');
+var web_client = require('web.web_client');
+
+var _t = core._t;
+var _lt = core._lt;
+
+var LIMIT = 30; // max number of fetched messages from the server
+var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers
+var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages
+
+/**
+ * This service handles everything about chat channels and messages.
+ *
+ * There are basically two points of entry:
+ *
+ * 1. Calling a public method by means of 'this.call'
+ * 2. Receiving events on busBus (e.g. 'notification')
+ */
+var ChatManager = AbstractService.extend({
+ name: 'chat_manager',
+ dependencies: ['ajax', 'bus_service'],
+ /**
+ * @override
+ */
+ init: function () {
+ var self = this;
+ this._super.apply(this, arguments);
+
+ this.messages = [];
+ this.channels = [];
+ this.channelsPreviewDef;
+ this.channelDefs = {};
+ this.unreadConversationCounter = 0;
+ this.emojis = [];
+ this.emojiSubstitutions = {};
+ this.emojiUnicodes = {};
+ this.needactionCounter = 0;
+ this.starredCounter = 0;
+ this.mentionPartnerSuggestions = [];
+ this.cannedResponses = [];
+ this.commands = [];
+ this.discussMenuID;
+ this.globalUnreadCounter = 0;
+ this.pinnedDmPartners = []; // partner_ids we have a pinned DM with
+ this.discussOpen = false;
+
+ this.chatBus = new Bus();
+ this.busBus = this.call('bus_service', 'getBus');
+
+ this.chatBus.on('discuss_open', null, function (open) {
+ self.discussOpen = open;
+ });
+
+ this.busBus.on('notification', this, this._onNotification);
+ this.busBus.on('window_focus', this, this._onWindowFocus);
+
+ // TODO create private fonction on prototype
+ this.channelSeen = _.throttle(function (channel) {
+ return self._rpc({
+ model: 'mail.channel',
+ method: 'channel_seen',
+ args: [[channel.id]],
+ }, {
+ shadow: true
+ });
+ }, 3000);
+
+ this._isReady = this._initMessaging();
+
+ this._addChannel({
+ id: "channel_inbox",
+ name: _lt("Inbox"),
+ type: "static",
+ }, { displayNeedactions: true });
+
+ this._addChannel({
+ id: "channel_starred",
+ name: _lt("Starred"),
+ type: "static"
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Closes the chat window of a corresponding channel.
+ * This operation is executed server-side and the chat window will be
+ * folded in all potential tabs from all browsers.
+ *
+ * @param {integer|string} channelID
+ * @return {$.Promise}
+ */
+ closeChatSession: function (channelID) {
+ var channel = this.getChannel(channelID);
+ return this._rpc({
+ model: 'mail.channel',
+ method: 'channel_fold',
+ kwargs: {uuid : channel.uuid, state : 'closed'},
+ }, {shadow: true});
+ },
+ /**
+ * Creates a channel
+ *
+ * @param {integer|string} name id of partner (in case of dm) or name
+ * @param {string} type ['dm', 'public', 'private']
+ * @return {$.Promise}
+ */
+ createChannel: function (name, type) {
+ var method = type === "dm" ? "channel_get" : "channel_create";
+ var args = type === "dm" ? [[name]] : [name, type];
+ var context = _.extend({isMobile: config.device.isMobile}, session.user_context);
+ return this._rpc({
+ model: 'mail.channel',
+ method: method,
+ args: args,
+ kwargs: {context: context},
+ })
+ .then(this._addChannel.bind(this));
+ },
+ /**
+ * Open the chat window for a given channel
+ * (in all potential tabs from all browsers)
+ *
+ * @param {integer} channelID
+ * @return {$.Promise}
+ */
+ detachChannel: function (channelID) {
+ var channel = this.getChannel(channelID);
+ return this._rpc({
+ model: 'mail.channel',
+ method: 'channel_minimize',
+ args: [channel.uuid, true],
+ }, {
+ shadow: true,
+ });
+ },
+ /**
+ * Folds/Minimize the chat window
+ * (in all potential tabs from all browsers)
+ *
+ * @param {integer} channelID
+ * @param {boolean} folded
+ * @return {$.Promise}
+ */
+ foldChannel: function (channelID, folded) {
+ var args = {
+ uuid: this.getChannel(channelID).uuid,
+ };
+ if (_.isBoolean(folded)) {
+ args.state = folded ? 'folded' : 'open';
+ }
+ return this._rpc({
+ model: 'mail.channel',
+ method: 'channel_fold',
+ kwargs: args,
+ }, {shadow: true});
+ },
+ /**
+ * Returns the list of canned responses
+ * A canned response is a pre-formatted text that is triggered with
+ * some keystrokes, such as with ':'.
+ *
+ * @return {Array} array of Objects (mail.shortcode)
+ */
+ getCannedResponses: function () {
+ return this.cannedResponses;
+ },
+ /**
+ * Returns a channel corresponding to the given id.
+ *
+ * @param {string|integer} id e.g. 'channel_inbox', 'channel_starred'
+ * @return {Object|undefined} channel
+ */
+ getChannel: function (id) {
+ return _.findWhere(this.channels, {id: id});
+ },
+ /**
+ * Returns a list of channels
+ *
+ * @return {Object[]}
+ */
+ getChannels: function () {
+ return _.clone(this.channels);
+ },
+ /**
+ * Returns the content that will be shown in the mail navbar dropdown
+ *
+ * @param {Object[]} channels
+ * @return {$.Promise} resolved with array of preview msgs
+ */
+ getChannelsPreview: function (channels) {
+ var self = this;
+ var channelsPreview = _.map(channels, function (channel) {
+ var info;
+ if (channel.channel_ids && _.contains(channel.channel_ids,"channel_inbox")) {
+ // map inbox(mail_message) data with existing channel/chat template
+ info = _.pick(channel,
+ 'id', 'body', 'avatar_src', 'res_id', 'model', 'module_icon',
+ 'subject','date', 'record_name', 'status', 'displayed_author',
+ 'email_from', 'unread_counter');
+ info.last_message = {
+ body: info.body,
+ date: info.date,
+ displayed_author: info.displayed_author || info.email_from,
+ };
+ info.name = info.record_name || info.subject || info.displayed_author;
+ info.image_src = info.module_icon || info.avatar_src;
+ info.message_id = info.id;
+ info.id = 'channel_inbox';
+ return info;
+ }
+ info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
+ info.last_message = channel.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;
+ });
+ var missingChannels = _.where(channelsPreview, {last_message: undefined});
+ if (!this.channelsPreviewDef) {
+ if (missingChannels.length) {
+ var missingChannelIDs = _.pluck(missingChannels, 'id');
+ this.channelsPreviewDef = this._rpc({
+ model: 'mail.channel',
+ method: 'channel_fetch_preview',
+ args: [missingChannelIDs],
+ }, {
+ shadow: true,
+ });
+ } else {
+ this.channelsPreviewDef = $.when();
+ }
+ }
+ return this.channelsPreviewDef.then(function (channels) {
+ _.each(missingChannels, function (channelPreview) {
+ var channel = _.findWhere(channels, {id: channelPreview.id});
+ if (channel) {
+ channelPreview.last_message = self._addMessage(channel.last_message);
+ }
+ });
+ // sort channels: 1. unread, 2. chat, 3. date of last msg
+ channelsPreview.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 - !!c1.last_message ||
+ (c2.last_message && c2.last_message.date.diff(c1.last_message.date));
+ });
+
+ // generate last message preview (inline message body and compute date to display)
+ _.each(channelsPreview, function (channel) {
+ if (channel.last_message) {
+ channel.last_message_preview = utils.parse_and_transform(channel.last_message.body, utils.inline);
+ channel.last_message_date = channel.last_message.date.fromNow();
+ }
+ });
+ return channelsPreview;
+ });
+ },
+ /**
+ * @return {web.Bus} the chat bus
+ */
+ getChatBus: function () {
+ return this.chatBus;
+ },
+ /**
+ * Show the list of available commands next to a message (e.g. star)
+ *
+ * @param {Object} channel
+ * @return {Array}
+ */
+ getCommands: function (channel) {
+ return _.filter(this.commands, function (command) {
+ return !command.channel_types || _.contains(command.channel_types, channel.server_type);
+ });
+ },
+ /**
+ * Returns the record id of ir.ui.menu for Discuss
+ *
+ * @return {integer} record id
+ */
+ getDiscussMenuID: function () {
+ return this.discussMenuID;
+ },
+ /**
+ * Gets direct message channel
+ *
+ * @param {integer} partnerID
+ * @return {Object|undefined} channel
+ */
+ getDmFromPartnerID: function (partnerID) {
+ return _.findWhere(this.channels, {direct_partner_id: partnerID});
+ },
+ /**
+ * Returns list of emojis Objects
+ *
+ * @return {Object[]}
+ * ['id', 'source', 'unicode_source', 'substitution', 'description']
+ */
+ getEmojis: function () {
+ return this.emojis;
+ },
+ /**
+ * Get the last seen message for a given channel
+ *
+ * @param {Object} channel
+ * @return {Object} Message Object
+ */
+ getLastSeenMessage: 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].is_author || messages[i].is_system_notification)) {
+ msg = messages[i];
+ i++;
+ }
+ return msg;
+ }
+ }
+ },
+ /**
+ * Get all listeners of a channel.
+ *
+ * @param {Object} channel
+ * @return {$.Promise>|Array