odoo.define('mail_base.base', function (require) { "use strict"; var bus = require('bus.bus').bus; var utils = require('mail.utils'); var config = require('web.config'); var core = require('web.core'); var data = require('web.data'); var Model = require('web.Model'); var session = require('web.session'); var time = require('web.time'); var web_client = require('web.web_client'); var composer = require('mail.composer'); var config = require('web.config'); var Chatter = require('mail.Chatter'); var form_common = require('web.form_common'); var _t = core._t; var _lt = core._lt; var LIMIT = 100; var preview_msg_max_size = 350; // optimal for native english speakers var ODOOBOT_ID = "ODOOBOT"; var MessageModel = new Model('mail.message', session.context); var ChannelModel = new Model('mail.channel', session.context); var UserModel = new Model('res.users', session.context); var PartnerModel = new Model('res.partner', session.context); var chat_manager = require('mail.chat_manager'); // 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 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; bus.on("window_focus", null, function() { global_unread_counter = 0; web_client.set_title_part("_chat"); }); var channel_seen = _.throttle(function (channel) { return ChannelModel.call('channel_seen', [[channel.id]], {}, {shadow: true}); }, 3000); var ChatAction = core.action_registry.get('mail.chat.instant_messaging'); ChatAction.include({ init: function(parent, action, options) { this._super.apply(this, arguments); this.channels_show_send_button = ['channel_inbox']; this.channels_display_subject = []; }, start: function() { var result = this._super.apply(this, arguments); var search_defaults = {}; var context = this.action ? this.action.context : []; _.each(context, function (value, key) { var match = /^search_default_(.*)$/.exec(key); if (match) { search_defaults[match[1]] = value; } }); this.searchview.defaults = search_defaults; var self = this; return $.when(result).done(function() { $('.oe_leftbar').toggle(false); self.searchview.do_search(); }); }, destroy: function() { var result = this._super.apply(this, arguments); $('.oe_leftbar .oe_secondary_menu').each(function(){ if ($(this).css('display') == 'block'){ if ($(this).children().length > 0) { $('.oe_leftbar').toggle(true); } return false; } }); return result; }, set_channel: function(channel){ var result = this._super.apply(this, arguments); var self = this; return $.when(result).done(function() { self.$buttons .find('.o_mail_chat_button_new_message') .toggle(self.channels_show_send_button.indexOf(channel.id) != -1); }); }, get_thread_rendering_options: function (messages) { var options = this._super.apply(this, arguments); options.display_subject = options.display_subject || this.channels_display_subject.indexOf(this.channel.id) != -1; return options; }, update_message_on_current_channel: function(current_channel_id, message){ var starred = current_channel_id === "channel_starred" && !message.is_starred; var inbox = current_channel_id === "channel_inbox" && !message.is_needaction; return starred || inbox; }, on_update_message: function (message) { var self = this; var current_channel_id = this.channel.id; if (this.update_message_on_current_channel(current_channel_id, message)) { chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function (messages) { var options = self.get_thread_rendering_options(messages); self.thread.remove_message_and_render(message.id, messages, options).then(function () { self.update_button_status(messages.length === 0); }); }); } else if (_.contains(message.channel_ids, current_channel_id)) { this.fetch_and_render_thread(); } } }); var MailComposer = composer.BasicComposer.extend({ template: 'mail.chatter.ChatComposer', init: function (parent, dataset, options) { this._super(parent, options); this.thread_dataset = dataset; this.suggested_partners = []; this.options = _.defaults(this.options, { display_mode: 'textarea', record_name: false, is_log: false, }); if (this.options.is_log) { this.options.send_text = _t('Log'); } this.events = _.extend(this.events, { 'click .o_composer_button_full_composer': 'on_open_full_composer', }); }, willStart: function () { if (this.options.is_log) { return this._super.apply(this, arguments); } return $.when(this._super.apply(this, arguments), this.message_get_suggested_recipients()); }, should_send: function () { return false; }, preprocess_message: function () { var self = this; var def = $.Deferred(); this._super().then(function (message) { message = _.extend(message, { subtype: 'mail.mt_comment', message_type: 'comment', content_subtype: 'html', context: self.context, }); // Subtype if (self.options.is_log) { message.subtype = 'mail.mt_note'; } // Partner_ids if (!self.options.is_log) { var checked_suggested_partners = self.get_checked_suggested_partners(); self.check_suggested_partners(checked_suggested_partners).done(function (partner_ids) { message.partner_ids = (message.partner_ids || []).concat(partner_ids); // update context message.context = _.defaults({}, message.context, { mail_post_autofollow: true, }); def.resolve(message); }); } else { def.resolve(message); } }); return def; }, /** * Send the message on SHIFT+ENTER, but go to new line on ENTER */ prevent_send: function (event) { return !event.shiftKey; }, message_get_suggested_recipients: function () { var self = this; var email_addresses = _.pluck(this.suggested_partners, 'email_address'); return this.thread_dataset .call('message_get_suggested_recipients', [[this.context.default_res_id], this.context]) .done(function (suggested_recipients) { var thread_recipients = suggested_recipients[self.context.default_res_id]; _.each(thread_recipients, function (recipient) { var parsed_email = utils.parse_email(recipient[1]); if (_.indexOf(email_addresses, parsed_email[1]) === -1) { self.suggested_partners.push({ checked: true, partner_id: recipient[0], full_name: recipient[1], name: parsed_email[0], email_address: parsed_email[1], reason: recipient[2], }); } }); }); }, /** * Get the list of selected suggested partners * @returns Array() : list of 'recipient' selected partners (may not be created in db) **/ get_checked_suggested_partners: function () { var self = this; var checked_partners = []; this.$('.o_composer_suggested_partners input:checked').each(function() { var full_name = $(this).data('fullname'); checked_partners = checked_partners.concat(_.filter(self.suggested_partners, function(item) { return full_name === item.full_name; })); }); return checked_partners; }, /** * Check the additional partners (not necessary registered partners), and open a popup form view * for the ones who informations is missing. * @param Array : list of 'recipient' partners to complete informations or validate * @returns Deferred resolved with the list of checked suggested partners (real partner) **/ check_suggested_partners: function (checked_suggested_partners) { var self = this; var check_done = $.Deferred(); var recipients = _.filter(checked_suggested_partners, function (recipient) { return recipient.checked; }); var recipients_to_find = _.filter(recipients, function (recipient) { return (! recipient.partner_id); }); var names_to_find = _.pluck(recipients_to_find, 'full_name'); var recipients_to_check = _.filter(recipients, function (recipient) { return (recipient.partner_id && ! recipient.email_address); }); var recipient_ids = _.pluck(_.filter(recipients, function (recipient) { return recipient.partner_id && recipient.email_address; }), 'partner_id'); var names_to_remove = []; var recipient_ids_to_remove = []; // have unknown names -> call message_get_partner_info_from_emails to try to find partner_id var find_done = $.Deferred(); if (names_to_find.length > 0) { find_done = self.thread_dataset.call('message_partner_info_from_emails', [[this.context.default_res_id], names_to_find]); } else { find_done.resolve([]); } // for unknown names + incomplete partners -> open popup - cancel = remove from recipients $.when(find_done).pipe(function (result) { var emails_deferred = []; var recipient_popups = result.concat(recipients_to_check); _.each(recipient_popups, function (partner_info) { var deferred = $.Deferred(); emails_deferred.push(deferred); var partner_name = partner_info.full_name; var partner_id = partner_info.partner_id; var parsed_email = utils.parse_email(partner_name); var dialog = new form_common.FormViewDialog(self, { res_model: 'res.partner', res_id: partner_id, context: { force_email: true, ref: "compound_context", default_name: parsed_email[0], default_email: parsed_email[1], }, title: _t("Please complete partner's informations"), disable_multiple_selection: true, }).open(); dialog.on('closed', self, function () { deferred.resolve(); }); dialog.opened().then(function () { dialog.view_form.on('on_button_cancel', self, function () { names_to_remove.push(partner_name); if (partner_id) { recipient_ids_to_remove.push(partner_id); } }); }); }); $.when.apply($, emails_deferred).then(function () { var new_names_to_find = _.difference(names_to_find, names_to_remove); find_done = $.Deferred(); if (new_names_to_find.length > 0) { find_done = self.thread_dataset.call('message_partner_info_from_emails', [[self.context.default_res_id], new_names_to_find, true]); } else { find_done.resolve([]); } $.when(find_done).pipe(function (result) { var recipient_popups = result.concat(recipients_to_check); _.each(recipient_popups, function (partner_info) { if (partner_info.partner_id && _.indexOf(partner_info.partner_id, recipient_ids_to_remove) === -1) { recipient_ids.push(partner_info.partner_id); } }); }).pipe(function () { check_done.resolve(recipient_ids); }); }); }); return check_done; }, on_open_full_composer: function() { if (!this.do_check_attachment_upload()){ return false; } var self = this; var recipient_done = $.Deferred(); if (this.options.is_log) { recipient_done.resolve([]); } else { var checked_suggested_partners = this.get_checked_suggested_partners(); recipient_done = this.check_suggested_partners(checked_suggested_partners); } recipient_done.then(function (partner_ids) { var context = { default_parent_id: self.id, default_body: utils.get_text2html(self.$input.val()), default_attachment_ids: _.pluck(self.get('attachment_ids'), 'id'), default_partner_ids: partner_ids, default_is_log: self.options.is_log, mail_post_autofollow: true, }; if (self.context.default_model && self.context.default_res_id) { context.default_model = self.context.default_model; context.default_res_id = self.context.default_res_id; } self.do_action({ type: 'ir.actions.act_window', res_model: 'mail.compose.message', view_mode: 'form', view_type: 'form', views: [[false, 'form']], target: 'new', context: context, }, { on_close: function() { self.trigger('need_refresh'); var parent = self.getParent(); chat_manager.get_messages({model: parent.model, res_id: parent.res_id}); }, }).then(self.trigger.bind(self, 'close_composer')); }); } }); Chatter.include({ open_composer: function (options) { var self = this; var old_composer = this.composer; // create the new composer this.composer = new MailComposer(this, this.thread_dataset, { commands_enabled: false, context: this.context, input_min_height: 50, input_max_height: Number.MAX_VALUE, // no max_height limit for the chatter input_baseline: 14, is_log: options && options.is_log, record_name: this.record_name, default_body: old_composer && old_composer.$input && old_composer.$input.val(), default_mention_selections: old_composer && old_composer.mention_get_listener_selections(), }); this.composer.on('input_focused', this, function () { this.composer.mention_set_prefetched_partners(this.mention_suggestions || []); }); this.composer.insertBefore(this.$('.o_mail_thread')).then(function () { // destroy existing composer if (old_composer) { old_composer.destroy(); } if (!config.device.touch) { self.composer.focus(); } self.composer.on('post_message', self, self.on_post_message); self.composer.on('need_refresh', self, self.refresh_followers); self.composer.on('close_composer', null, self.close_composer.bind(self, true)); }); this.mute_new_message_button(true); }, }); var MailTools = core.Class.extend({ notify_incoming_message: function (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(title, content); }, // Message and channel manipulation helpers //---------------------------------------------------------------------------------- // options: channel_id, silent add_message: function (data, options) { options = options || {}; var msg = _.findWhere(messages, { id: data.id }); if (!msg) { msg = chat_manager.mail_tools.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) { chat_manager.mail_tools.add_to_cache(msg, []); if (options.domain && options.domain !== []) { chat_manager.mail_tools.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) { chat_manager.mail_tools.update_channel_unread_counter(channel, channel.unread_counter+1); } if (channel.is_chat && options.show_notification) { if (!client_action_open && config.device.size_class !== config.device.SIZES.XS) { // 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); chat_manager.mail_tools.notify_incoming_message(msg, query); } } } }); if (!options.silent) { chat_manager.bus.trigger('new_message', msg); } } else if (options.domain && options.domain !== []) { chat_manager.mail_tools.add_to_cache(msg, options.domain); } return msg; }, property_descr: function (channel, msg, self) { return { enumerable: true, get: function () { return _.contains(msg.channel_ids, channel); }, set: function (bool) { if (bool) { self.add_channel_to_message(msg, channel); } else { msg.channel_ids = _.without(msg.channel_ids, channel); } } }; }, get_properties: function(msg){ return { is_starred: chat_manager.mail_tools.property_descr("channel_starred", msg, chat_manager.mail_tools), is_needaction: chat_manager.mail_tools.property_descr("channel_inbox", msg, chat_manager.mail_tools) }; }, set_channel_flags: function(data, msg){ 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; } return msg; }, get_channel_array: function(msg){ return [ msg.channel_ids, 'channel_inbox', 'channel_starred' ]; }, make_message: function (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_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) }; _.each(_.keys(emoji_substitutions), function (key) { var escaped_key = String(key).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + escaped_key + ")(?=\\s|$|)", "g"); msg.body = msg.body.replace(regexp, ' '+emoji_substitutions[key]+' '); }); Object.defineProperties(msg, chat_manager.mail_tools.get_properties(msg)); msg = chat_manager.mail_tools.set_channel_flags(data, msg); if (msg.model === 'mail.channel') { var real_channels = _.without(chat_manager.mail_tools.get_channel_array(msg)); 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'; }); return msg; }, add_channel_to_message: function (message, channel_id) { message.channel_ids.push(channel_id); message.channel_ids = _.uniq(message.channel_ids); }, add_channel: function (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.mail_tools.make_channel(data, options); channels.push(channel); 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; }, make_channel: function (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, 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" && data.public !== "private") { channel.type = "public"; } else if (data.public === "private") { channel.type = "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) { chat_manager.mail_tools.update_channel_unread_counter(channel, data.message_unread_counter); } return channel; }, remove_channel: function (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]; }, get_channel_cache: function (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]; }, invalidate_caches: function (channel_ids) { _.each(channel_ids, function (channel_id) { var channel = chat_manager.get_channel(channel_id); if (channel) { channel.cache = { '[]': channel.cache['[]']}; } }); }, clear_cache_all_channels: function(){ _.each(channels, function(channel){ channel.cache = { '[]': { all_history_loaded: false, loaded: false, messages: [] } } }); }, add_to_cache: function (message, domain) { _.each(message.channel_ids, function (channel_id) { var channel = chat_manager.get_channel(channel_id); if (channel) { var channel_cache = chat_manager.mail_tools.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); } } }); }, remove_from_cache: function(message, domain){ var self = this; _.each(message.channel_ids, function (channel_id) { var channel = chat_manager.get_channel(channel_id); if (channel) { var channel_cache = self.get_channel_cache(channel, domain); var index = _.sortedIndex(channel_cache.messages, message, 'id'); if (channel_cache.messages[index] === message) { channel_cache.messages.splice(index, 1); } } }); }, remove_message_from_channel: function (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); }); }, get_domain: function(channel){ return (channel.id === "channel_inbox") ? [['needaction', '=', true]] : (channel.id === "channel_starred") ? [['starred', '=', true]] : false; }, // options: domain, load_more fetch_from_channel: function (channel, options) { options = options || {}; var domain = chat_manager.mail_tools.get_domain(channel) || [['channel_ids', 'in', channel.id]]; var cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain); if (options.domain) { domain = new data.CompoundDomain(domain, options.domain || []); } if (options.load_more) { var min_message_id = cache.messages[0].id; domain = new data.CompoundDomain([['id', '<', min_message_id]], domain); } return MessageModel.call('message_fetch', [domain], {limit: LIMIT}).then(function (msgs) { if (!cache.all_history_loaded) { cache.all_history_loaded = msgs.length < LIMIT; } cache.loaded = true; _.each(msgs, function (msg) { chat_manager.mail_tools.add_message(msg, {channel_id: channel.id, silent: true, domain: options.domain}); }); var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain || []); return channel_cache.messages; }); }, // options: force_fetch fetch_document_messages: 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 MessageModel.call('message_format', [ids_to_load]).then(function (msgs) { var processed_msgs = []; _.each(msgs, function (msg) { processed_msgs.push(chat_manager.mail_tools.add_message(msg, {silent: true})); }); return _.sortBy(loaded_msgs.concat(processed_msgs), function (msg) { return msg.date; }); }); } else { return $.when(loaded_msgs); } }, update_channel_unread_counter: function (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 // --------------------------------------------------------------------------------- on_notification: function (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 chat_manager.mail_tools.on_needaction_notification(notification[1]); } else if (model === 'mail.channel') { // new message in a channel chat_manager.mail_tools.on_channel_notification(notification[1]); } else if (model === 'res.partner') { // channel joined/left, message marked as read/(un)starred, chat open/closed chat_manager.mail_tools.on_partner_notification(notification[1]); } else if (model === 'bus.presence') { // update presence of users chat_manager.mail_tools.on_presence_notification(notification[1]); } else if (model === 'mail_base.mail_sent') { // Delete cache in order to fetch new message // TODO find a solution without deleting cache. Currently // problem is that on inheriting send_mail in // mail.compose.message it's not possible to get id of new // message chat_manager.mail_tools.clear_cache_all_channels(); } }); }, on_needaction_notification: function (message) { message = chat_manager.mail_tools.add_message(message, { channel_id: 'channel_inbox', show_notification: true, increment_unread: true }); chat_manager.mail_tools.invalidate_caches(message.channel_ids); 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); }, on_channel_notification: function (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 chat_manager.mail_tools.add_message(message, { show_notification: true, increment_unread: channel_already_in_cache }); chat_manager.mail_tools.invalidate_caches(message.channel_ids); }); }, on_partner_notification: function (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); } this.remove_channel(channel); chat_manager.bus.trigger("unsubscribe_from_channel", data.id); web_client.do_notify(_("Unsubscribed"), msg); } } else if (data.type === 'toggle_star') { chat_manager.mail_tools.on_toggle_star_notification(data); } else if (data.type === 'mark_as_read') { chat_manager.mail_tools.on_mark_as_read_notification(data); } else if (data.type === 'mark_as_unread') { chat_manager.mail_tools.on_mark_as_unread_notification(data); } else if (data.info === 'channel_seen') { chat_manager.mail_tools.on_channel_seen_notification(data); } else if (data.info === 'transient_message') { chat_manager.mail_tools.on_transient_message_notification(data); } else { chat_manager.mail_tools.on_chat_session_notification(data); } }, on_toggle_star_notification: function (data) { _.each(data.message_ids, function (msg_id) { var message = _.findWhere(messages, { id: msg_id }); if (message) { chat_manager.mail_tools.invalidate_caches(message.channel_ids); message.is_starred = data.starred; if (!message.is_starred) { chat_manager.mail_tools.remove_message_from_channel("channel_starred", message); starred_counter--; } else { chat_manager.mail_tools.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); }, on_mark_as_read_notification: function (data) { _.each(data.message_ids, function (msg_id) { var message = _.findWhere(messages, { id: msg_id }); if (message) { chat_manager.mail_tools.invalidate_caches(message.channel_ids); chat_manager.mail_tools.remove_message_from_channel("channel_inbox", message); chat_manager.bus.trigger('update_message', message); } }); 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); }, on_mark_as_unread_notification: function (data) { _.each(data.message_ids, function (message_id) { var message = _.findWhere(messages, { id: message_id }); if (message) { chat_manager.mail_tools.invalidate_caches(message.channel_ids); chat_manager.mail_tools.add_channel_to_message(message, 'channel_inbox'); chat_manager.mail_tools.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); }, on_channel_seen_notification: function (data) { var channel = chat_manager.get_channel(data.id); if (channel) { channel.last_seen_message_id = data.last_message_id; if (channel.unread_counter) { chat_manager.mail_tools.update_channel_unread_counter(channel, 0); } } }, on_chat_session_notification: function (chat_session) { var channel; if ((chat_session.channel_type === "channel") && (chat_session.state === "open")) { chat_manager.mail_tools.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}); } } }, on_presence_notification: function (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); } }, on_transient_message_notification: function (data) { var last_message = _.last(messages); data.id = (last_message ? last_message.id : 0) + 0.01; data.author_id = data.author_id || ODOOBOT_ID; chat_manager.mail_tools.add_message(data); } }); var cls = new MailTools(); // Public interface //---------------------------------------------------------------------------------- chat_manager.mail_tools = cls; // we add this function this way in order to make them extendable via MailTools.include({...}) chat_manager.make_message = function(){ return chat_manager.mail_tools.make_message.apply(chat_manager.mail_tools, arguments); }; chat_manager.make_channel = function(){ return chat_manager.mail_tools.make_channel.apply(chat_manager.mail_tools, arguments); }; chat_manager.post_message = function (data, options) { options = options || {}; var msg = { partner_ids: data.partner_ids, body: _.str.trim(data.content), attachment_ids: data.attachment_ids }; if ('subject' in data) { msg.subject = data.subject; } if ('channel_id' in options) { // post a message in a channel or execute a command return ChannelModel.call(data.command ? 'execute_command' : 'message_post', [options.channel_id], _.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 }); if (options.model && options.res_id){ var model = new Model(options.model); return model.call('message_post', [options.res_id], msg).then(function (msg_id) { return MessageModel.call('message_format', [msg_id]).then(function (msgs) { msgs[0].model = options.model; msgs[0].res_id = options.res_id; chat_manager.mail_tools.add_message(msgs[0]); }); }); } else { options.model = 'mail.compose.message'; var compose_model = new Model(options.model); return compose_model.call('create', [msg, {default_parent_id: options.parent_id}]).then(function(id){ return compose_model.call('send_mail_action', [id, {}]); }); } } }; chat_manager.get_message = function (id) { return _.findWhere(messages, {id: id}); }; chat_manager.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 chat_manager.mail_tools.fetch_from_channel(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 = chat_manager.mail_tools.get_channel_cache(channel, options.domain); if (channel_cache.loaded) { return $.when(channel_cache.messages); } else { return chat_manager.mail_tools.fetch_from_channel(channel, {domain: options.domain}); } } if ('ids' in options) { // get messages from their ids (chatter is the main use case) return chat_manager.mail_tools.fetch_document_messages(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]]; MessageModel.call('message_fetch', [domain], {limit: 30}).then(function (msgs) { return _.map(msgs, chat_manager.mail_tools.add_message); }); } }; chat_manager.toggle_star_status = function (message_id) { return MessageModel.call('toggle_message_starred', [[message_id]]); }; chat_manager.unstar_all = function () { return MessageModel.call('unstar_all', [[]], {}); }; chat_manager.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 MessageModel.call('set_message_done', [ids]); } else { return $.when(); } }; chat_manager.mark_all_as_read = function (channel, domain) { if ((channel.id === "channel_inbox" && needaction_counter) || (channel && channel.needaction_counter)) { return MessageModel.call('mark_all_as_read', [], {channel_ids: channel.id !== "channel_inbox" ? [channel.id] : [], domain: domain}); } return $.when(); }; chat_manager.undo_mark_as_read = function (message_ids, channel) { return MessageModel.call('mark_as_unread', [message_ids, [channel.id]]); }; chat_manager.mark_channel_as_seen = function (channel) { if (channel.unread_counter > 0 && channel.type !== 'static') { chat_manager.mail_tools.update_channel_unread_counter(channel, 0); channel_seen(channel); } }; chat_manager.get_channels = function () { return _.clone(channels); }; chat_manager.get_channel = function (id) { return _.findWhere(channels, {id: id}); }; chat_manager.get_dm_from_partner_id = function (partner_id) { return _.findWhere(channels, {direct_partner_id: partner_id}); }; chat_manager.all_history_loaded = function (channel, domain) { return chat_manager.mail_tools.get_channel_cache(channel, domain).all_history_loaded; }; chat_manager.get_mention_partner_suggestions = function (channel) { if (!channel) { return mention_partner_suggestions; } if (!channel.members_deferred) { channel.members_deferred = ChannelModel .call("channel_fetch_listeners", [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; }; chat_manager.get_commands = function (channel) { return _.filter(commands, function (command) { return !command.channel_types || _.contains(command.channel_types, channel.server_type); }); }; chat_manager.get_canned_responses = function () { return canned_responses; }; chat_manager.get_needaction_counter = function () { return needaction_counter; }; chat_manager.get_starred_counter = function () { return starred_counter; }; chat_manager.get_chat_unread_counter = function () { return chat_unread_counter; }; chat_manager.get_unread_conversation_counter = function () { return unread_conversation_counter; }; chat_manager.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; } } }; chat_manager.get_discuss_menu_id = function () { return discuss_menu_id; }; chat_manager.detach_channel = function (channel) { return ChannelModel.call("channel_minimize", [channel.uuid, true], {}, {shadow: true}); }; chat_manager.remove_chatter_messages = function (model) { messages = _.reject(messages, function (message) { return message.channel_ids.length === 0 && message.model === model; }); }; chat_manager.create_channel = function (name, type) { var method = type === "dm" ? "channel_get" : "channel_create"; var args = type === "dm" ? [[name]] : [name, type]; return ChannelModel .call(method, args) .then(chat_manager.mail_tools.add_channel); }; chat_manager.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] = ChannelModel .call('channel_join_and_get_info', [[channel_id]]) .then(function (result) { return chat_manager.mail_tools.add_channel(result, options); }); } return channel_defs[channel_id]; }; chat_manager.open_and_detach_dm = function (partner_id) { return ChannelModel.call('channel_get_and_minimize', [[partner_id]]).then(chat_manager.mail_tools.add_channel); }; chat_manager.open_channel = function (channel) { chat_manager.bus.trigger(client_action_open ? 'open_channel' : 'detach_channel', channel); }; chat_manager.unsubscribe = function (channel) { var def; if (_.contains(['public', 'private'], channel.type)) { return ChannelModel.call('action_unfollow', [[channel.id]]); } else { return ChannelModel.call('channel_pin', [channel.uuid, false]); } }; chat_manager.close_chat_session = function (channel_id) { var channel = this.get_channel(channel_id); ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"}, {shadow: true}); }; chat_manager.fold_channel = function (channel_id, folded) { var args = { uuid: this.get_channel(channel_id).uuid }; if (_.isBoolean(folded)) { args.state = folded ? 'folded' : 'open'; } return ChannelModel.call("channel_fold", [], args, {shadow: true}); }; /** * Special redirection handling for given model and id * * 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. */ chat_manager.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]]; UserModel.call("search", [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 { new Model(res_model).call('get_formview_id', [[res_id], session.context]).then(function (view_id) { redirect_to_document(res_model, res_id, view_id); }); } }; chat_manager.get_channels_preview = function (channels) { var channels_preview = _.map(channels, function (channel) { var info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter'); info.last_message = _.last(channel.cache['[]'].messages); if (!info.is_chat) { info.image_src = '/web/image/mail.channel/'+channel.id+'/image_small'; } else if (channel.direct_partner_id) { info.image_src = '/web/image/res.partner/'+channel.direct_partner_id+'/image_small'; } else { info.image_src = '/mail/static/src/img/smiley/avatar.jpg'; } return info; }); 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 = ChannelModel.call('channel_fetch_preview', [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 = chat_manager.mail_tools.add_message(channel.last_message); } }); return _.filter(channels_preview, function (channel) { return channel.last_message; // remove empty channels }); }); }; chat_manager.get_message_body_preview = function (message_body) { return utils.parse_and_transform(message_body, utils.inline); }; chat_manager.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 = PartnerModel.call('im_search', [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'); }); }; chat_manager.bus.on('client_action_open', null, function (open) { client_action_open = open; }); // In order to extend init use chat_manager.is_ready Derrered object. See example in mail_arhive module function init () { chat_manager.mail_tools.add_channel({ id: "channel_inbox", name: _lt("Inbox"), type: "static", }, { display_needactions: true }); chat_manager.mail_tools.add_channel({ id: "channel_starred", name: _lt("Starred"), type: "static" }); // unsubscribe and then subscribe to the event, to avoid duplication of new messages bus.off('notification'); bus.on('notification', null, function(){ chat_manager.mail_tools.on_notification.apply(chat_manager.mail_tools, arguments); }); return session.rpc('/mail/client_action').then(function (result) { _.each(result.channel_slots, function (channels) { _.each(channels, chat_manager.mail_tools.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', 'substitution', 'description'])); emoji_substitutions[_.escape(s.source)] = s.substitution; } }); bus.start_polling(); }); } chat_manager.is_ready = init(); return { ODOOBOT_ID: ODOOBOT_ID, chat_manager: chat_manager, MailTools: MailTools, MailComposer: MailComposer }; });