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');
var utils = require('mail.utils');
var concurrency = require('web.concurrency');
var config = require('web.config');
var core = require('web.core');
var Widget = require('web.Widget');
var QWeb = core.qweb;
// The purpose of this widget is to display the chatter area below the form view
//
// It instanciates the optional mail_thread, mail_activity and mail_followers widgets.
// It Ensures that those widgets are appended at the right place, and allows them to communicate
// 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, {
template: 'mail.Chatter',
custom_events: {
reload_mail_fields: '_onReloadMailFields',
},
events: {
'click .o_chatter_button_new_message': '_onOpenComposerMessage',
'click .o_chatter_button_log_note': '_onOpenComposerNote',
'click .o_chatter_button_schedule_activity': '_onScheduleActivity',
},
supportedFieldTypes: ['one2many'],
// inherited
init: function (parent, record, mailFields, options) {
this._super.apply(this, arguments);
this._setState(record);
this.dp = new concurrency.DropPrevious();
// 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.mentionSuggestions = this.mentionPartnerSuggestions;
this.fields = {};
if (mailFields.mail_activity) {
this.fields.activity = new Activity(this, mailFields.mail_activity, record, options);
}
if (mailFields.mail_followers) {
this.fields.followers = new Followers(this, mailFields.mail_followers, record, options);
}
if (mailFields.mail_thread) {
this.fields.thread = new ThreadField(this, mailFields.mail_thread, record, options);
var nodeOptions = this.record.fieldsInfo.form[mailFields.mail_thread].options;
this.hasLogButton = nodeOptions.display_log_button;
this.postRefresh = nodeOptions.post_refresh || 'never';
}
},
start: function () {
this.$topbar = this.$('.o_chatter_topbar');
// render and append the buttons
this.$topbar.append(QWeb.render('mail.Chatter.Buttons', {
new_message_btn: !!this.fields.thread,
log_note_btn: this.hasLogButton,
schedule_activity_btn: !!this.fields.activity,
isMobile: config.device.isMobile,
}));
// start and append the widgets
var fieldDefs = _.invoke(this.fields, 'appendTo', $('
'));
var def = this.dp.add($.when.apply($, fieldDefs));
this._render(def).then(this._updateMentionSuggestions.bind(this));
return this._super.apply(this, arguments);
},
// public
update: function (record, fieldNames) {
var self = this;
// close the composer if we switch to another record as it is record dependent
if (this.record.res_id !== record.res_id) {
this._closeComposer(true);
}
// update the state
this._setState(record);
// detach the thread and activity widgets (temporarily force the height to prevent flickering)
// keep the followers in the DOM as it has a synchronous pre-rendering
this.$el.height(this.$el.height());
if (this.fields.activity) {
this.fields.activity.$el.detach();
}
if (this.fields.thread) {
this.fields.thread.$el.detach();
}
// reset and re-append the widgets (and reset 'height: auto' rule)
// if fieldNames is given, only reset those fields, otherwise reset all fields
var fieldsToReset;
if (fieldNames) {
fieldsToReset = _.filter(this.fields, function (field) {
return _.contains(fieldNames, field.name);
});
} else {
fieldsToReset = this.fields;
}
var fieldDefs = _.invoke(fieldsToReset, 'reset', record);
var def = this.dp.add($.when.apply($, fieldDefs));
this._render(def).then(function () {
self.$el.height('auto');
self._updateMentionSuggestions();
});
},
// private
_closeComposer: function (force) {
if (this.composer && (this.composer.is_empty() || force)) {
this.$el.removeClass('o_chatter_composer_active');
this.$('.o_chatter_button_new_message, .o_chatter_button_log_note').removeClass('o_active');
this.composer.do_hide();
this.composer.clear_composer();
}
},
_openComposer: function (options) {
var self = this;
var old_composer = this.composer;
// create the new composer
this.composer = new ChatterComposer(this, this.record.model, options.suggested_partners || [], {
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.mentionSuggestions || []);
});
this.composer.insertAfter(this.$('.o_chatter_topbar')).then(function () {
// destroy existing composer
if (old_composer) {
old_composer.destroy();
}
if (!config.device.touch) {
self.composer.focus();
}
self.composer.on('post_message', self, function (message) {
self.fields.thread.postMessage(message).then(function () {
self._closeComposer(true);
if (self.postRefresh === 'always' || (self.postRefresh === 'recipients' && message.partner_ids.length)) {
self.trigger_up('reload');
}
});
});
self.composer.on('need_refresh', self, self.trigger_up.bind(self, 'reload'));
self.composer.on('close_composer', null, self._closeComposer.bind(self, true));
self.$el.addClass('o_chatter_composer_active');
self.$('.o_chatter_button_new_message, .o_chatter_button_log_note').removeClass('o_active');
self.$('.o_chatter_button_new_message').toggleClass('o_active', !self.composer.options.is_log);
self.$('.o_chatter_button_log_note').toggleClass('o_active', self.composer.options.is_log);
});
},
_render: function (def) {
// the rendering of the chatter is aynchronous: relational data of its fields needs to be
// fetched (in some case, it might be synchronous as they hold an internal cache).
// this function takes a deferred as argument, which is resolved once all fields have
// fetched their data
// this function appends the fields where they should be once the given deferred is resolved
// and if it takes more than 500ms, displays a spinner to indicate that it is loading
var self = this;
var $spinner = $(QWeb.render('Spinner'));
concurrency.rejectAfter(concurrency.delay(500), def).then(function () {
$spinner.appendTo(self.$el);
});
return def.then(function () {
if (self.fields.activity) {
self.fields.activity.$el.appendTo(self.$el);
}
if (self.fields.followers) {
self.fields.followers.$el.appendTo(self.$topbar);
}
if (self.fields.thread) {
self.fields.thread.$el.appendTo(self.$el);
}
}).always($spinner.remove.bind($spinner));
},
_setState: function (record) {
if (!this.record || this.record.res_id !== record.res_id) {
this.context = {
default_res_id: record.res_id || false,
default_model: record.model || false,
};
// reset the suggested_partners_def to ensure a reload of the
// suggested partners when opening the composer on another record
this.suggested_partners_def = undefined;
}
this.record = record;
this.record_name = record.data.display_name;
},
_updateMentionSuggestions: function () {
if (!this.fields.followers) {
return;
}
var self = this;
this.mentionSuggestions = [];
// add the followers to the mention suggestions
var follower_suggestions = [];
var followers = this.fields.followers.getFollowers();
_.each(followers, function (follower) {
if (follower.res_model === 'res.partner') {
follower_suggestions.push({
id: follower.res_id,
name: follower.name,
email: follower.email,
});
}
});
if (follower_suggestions.length) {
this.mentionSuggestions.push(follower_suggestions);
}
// add the partners (followers filtered out) to the mention suggestions
_.each(this.mentionPartnerSuggestions, function (partners) {
self.mentionSuggestions.push(_.filter(partners, function (partner) {
return !_.findWhere(follower_suggestions, { id: partner.id });
}));
});
},
// handlers
_onOpenComposerMessage: function () {
var self = this;
if (!this.suggested_partners_def) {
this.suggested_partners_def = $.Deferred();
var method = 'message_get_suggested_recipients';
var args = [[this.context.default_res_id], this.context];
this._rpc({model: this.record.model, method: method, args: args})
.then(function (result) {
if (!self.suggested_partners_def) {
return; // widget has been reset (e.g. we just switched to another record)
}
var suggested_partners = [];
var thread_recipients = result[self.context.default_res_id];
_.each(thread_recipients, function (recipient) {
var parsed_email = recipient[1] && utils.parse_email(recipient[1]);
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],
});
});
self.suggested_partners_def.resolve(suggested_partners);
});
}
this.suggested_partners_def.then(function (suggested_partners) {
self._openComposer({ is_log: false, suggested_partners: suggested_partners });
});
},
_onOpenComposerNote: function () {
this._openComposer({is_log: true});
},
_onReloadMailFields: function (event) {
var fieldNames = [];
if (this.fields.activity && event.data.activity) {
fieldNames.push(this.fields.activity.name);
}
if (this.fields.followers && event.data.followers) {
fieldNames.push(this.fields.followers.name);
}
if (this.fields.thread && event.data.thread) {
fieldNames.push(this.fields.thread.name);
}
this.trigger_up('reload', {
fieldNames: fieldNames,
keepChanges: true,
});
},
_onScheduleActivity: function () {
this.fields.activity.scheduleActivity(false);
},
});
return Chatter;
});