diff options
Diffstat (limited to 'addons/mail/static/src/js/mail_chatter.js')
| -rw-r--r-- | addons/mail/static/src/js/mail_chatter.js | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/addons/mail/static/src/js/mail_chatter.js b/addons/mail/static/src/js/mail_chatter.js new file mode 100644 index 00000000000..7032f864eb4 --- /dev/null +++ b/addons/mail/static/src/js/mail_chatter.js @@ -0,0 +1,471 @@ +odoo.define('mail.chatter', function (require) { +"use strict"; + +var core = require('web.core'); +var data = require('web.data'); +var session = require('web.session'); +var web_client = require('web.web_client'); +var Model = require('web.Model'); +var Widget = require('web.Widget'); +var form_common = require('web.form_common'); +var SystrayMenu = require('web.SystrayMenu'); +var mail_utils = require('mail.utils'); +var mail_thread = require('mail.thread'); + +var QWeb = core.qweb; +var _t = core._t; +var internal_bus = core.bus; + + +/** + * Mail Compose Message for Chatter + * + * Extends the basic MailComposeMessage Widget to add 'suggested partner' layer (open popup + * when suggested partner is selected without email, or other informations), and the button + * to open the 'mail.compose.message' wizard. + **/ +var ChatterMailComposeMessage = mail_thread.MailComposeMessage.extend({ + template: 'mail.chatter.ComposeMessage', + init: function(parent, dataset, options){ + this._super.apply(this, arguments); + this.suggested_partners = []; + this.context = options.context || {}; + // options + this.options = _.defaults(options, { + 'record_name': false, + 'is_log': false, + 'internal_subtypes': [], + }); + }, + 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()); + }, + start: function(){ + this.$input = this.$('.o_mail_compose_message_input'); + this.$input.focus(); + // add mail compose message full button + this.$('.o_mail_compose_message_button_group').append('<button class="btn btn-default o_mail_compose_message_button_full_message" type="button" data-toggle="popover"><i class="fa fa-pencil-square-o"/></button>') + this.$('.o_mail_compose_message_button_full_message').on('click', this, this.on_compose_message); + return this._super.apply(this, arguments); + }, + 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 = mail_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 partner + * @returns Array() : list of 'recipient' selected partner (may not be created in db) + **/ + get_checked_suggested_partners: function(){ + var self = this; + var checked_partners = []; + this.$('.o_mail_chatter_compose_message_suggested_partners input:checked').each(function(index){ + var $input = $(this); + var full_name = $input.data('fullname'); + checked_partners = checked_partners.concat(_.filter(self.suggested_partners, function(item){ + return full_name === item.full_name; + })); + }); + return checked_partners; + }, + /** + * Check the additionnal 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 = mail_utils.parse_email(partner_name); + + var pop = new form_common.FormViewDialog(this, { + 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"), + }).open(); + pop.on('closed', self, function () { + deferred.resolve(); + }); + pop.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; + }, + message_post: function(body, attachment_ids, values){ + var self = this; + var _super = self._super; + // default values + values = _.defaults(values || {}, { + 'message_type': 'comment', + 'content_subtype': 'html', + 'partner_ids': [], + 'subtype_id': false, + 'subtype': false, + }); + // update subtype + if(this.options.is_log) { + var subtype_id = parseInt(this.$('.o_mail_chatter_compose_message_subtype_select').val()); + if(_.indexOf(_.pluck(this.internal_subtypes, 'id'), subtype_id) == -1) { + values['subtype'] = 'mail.mt_note' + }else{ + values['subtype_id'] = subtype_id; + } + }else{ + values['subtype'] = 'mail.mt_comment'; + } + // update partner_ids + var def = $.when(); + if(this.options.is_log){ + // directly call the super() + def = _.bind(_super, self)(body, attachment_ids, values); + }else{ + // call the super() when all partner popup are treated + var checked_suggested_partners = this.get_checked_suggested_partners(); + this.check_suggested_partners(checked_suggested_partners).done(function(partner_ids){ + // update context + values['partner_ids'] = partner_ids; + values['context'] = _.defaults(self.context, { + 'mail_post_autofollow': true, + 'mail_post_autofollow_partner_ids': partner_ids, + }); + def = _.bind(_super, self)(body, attachment_ids, values); + }); + } + return def + }, + is_empty: function(){ + return this.$input.val() === ''; + }, + on_compose_message: function(event){ + var self = this; + if (!this.do_check_attachment_upload()){ + return false; + } + + 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': mail_utils.get_text2html(self.$('.o_mail_compose_message_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, + 'mail_post_autofollow_partner_ids': partner_ids, + }; + + 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'); + }, + }); + }); + } +}); + + +/** + * Document Chatter ('mail_thread' widget) + * + * Displayed on every document. Since it is displayed on a form view, it extends 'AbstractField' widget, and + * required the behaviour of 'MailThreadMixin' to manage mail.message (using common methods). + * This widget is responsible of the toggle the message composer and the custom message rendering. + **/ +var ChatterMailThread = form_common.AbstractField.extend(mail_thread.MailThreadMixin, { + template: 'mail.chatter.ChatterMailThread', + events: { + "click .o_mail_redirect": "on_click_redirect", + "click .o_mail_thread_message_star": "on_message_star", + "click .o_mail_thread_message_needaction": "on_message_needaction", + "click .o_mail_chatter_show_more_message": "on_message_show_more", + // toggle message composer (!! declaration order is important !!) + "click .o_mail_chatter_button_new_message": "on_open_composer", + "click .o_mail_chatter_button_log_note": "on_open_composer", + "blur .o_mail_compose_message_input": "on_close_composer", + "mousedown .o_mail_chatter_compose_message": function(event) { + this.stay_open = true; + }, + "focus .o_mail_compose_message_input": function(event) { + this.stay_open = false; + }, + "keydown .o_mail_compose_message_input": function(event) { + this.stay_open = false; + }, + }, + init: function (parent, node){ + this._super.apply(this, arguments); + mail_thread.MailThreadMixin.init.call(this); + this.message_composer = undefined; + this.record_name = ''; + this.context = this.options.context || {}; + // options from 'message_ids' node in field_view_get + this.options = _.defaults(this.options, { + 'display_log_button': true, + 'display_document_link': false, + 'internal_subtypes': [], + 'emoji_list': [], + }); + this.emoji_set_substitution(this.options.emoji_list); + }, + start: function(){ + mail_thread.MailThreadMixin.start.call(this); + return this._super.apply(this, arguments); + }, + on_message_show_more: function(event){ + var self = this; + this.message_load_history().then(function(messages){ + if(messages.length < mail_thread.LIMIT_MESSAGE){ + self.$('.o_mail_chatter_show_more_message').hide(); + } + }); + }, + /** + * When a message is correctly posted, fetch its data to render it + * @param {Number} message_id : the identifier of the new posted message + * @returns {Deferred} + */ + on_message_post: function(message_id){ + var self = this; + return this.message_format([message_id]).then(function(messages){ + self.stay_open = false; + self.message_insert(messages); + self.on_close_composer(); + }); + }, + /** + * The value of the field has change (modification or switch to next document). Form view is + * not re-rendered, simply updated. A re-fetch is needed. + * @override + */ + render_value: function(){ + var self = this; + // re-rendering for mail composer message + this.options.display_show_more_message = this.get_value().length > mail_thread.LIMIT_MESSAGE; + this.renderElement(); + // reset dataset + this.ThreadDataset = this.view.dataset; + // update context (require for mail.compose.message wizard) + this.context = _.extend({ + 'mail_read_set_read': true, // set messages as read in Chatter TODO JEM : not used anymore + 'default_res_id': this.view.datarecord.id || false, + 'default_model': this.view.model || false, + }, this.options.context || {}); + // fetch messages of current document + this.message_format(this.get_value().slice(0, mail_thread.LIMIT_MESSAGE)).then(function(raw_messages){ + self._message_replace(self._message_preprocess(raw_messages)); + self.record_name = _.last(raw_messages) ? _.last(raw_messages).record_name : ''; + // set the thread as read + self.ThreadDataset.call('message_set_read', [[self.view.datarecord.id]]).then(function(message_ids){ + // decrement the needaction top counter + internal_bus.trigger('mail_needaction_done', message_ids.length); + }); + }); + }, + // composer toggle + on_open_composer: function(event){ + var self = this; + this.$('.o_mail_chatter_compose_buttons').hide(); + this.$('.o_mail_chatter_compose_message_wrap').show(); + // destroy existing composer + if(this.message_composer){ + this.message_composer.destroy(); + } + // create the new composer + this.message_composer = new ChatterMailComposeMessage(this, this.ThreadDataset, { + 'internal_subtypes': this.options.internal_subtypes, + 'is_log': this.$(event.currentTarget).hasClass('o_mail_chatter_button_log_note'), + 'emoji_list': this.options.emoji_list, + 'context': this.context, + 'record_name': this.record_name, + }); + this.message_composer.appendTo(this.$('.o_mail_chatter_compose_message_wrap')); + this.message_composer.on('message_sent', this, this.on_message_post); + this.message_composer.on('need_refresh', this, function(e){ + self.message_refresh(); + self.on_close_composer(); + }); + }, + on_close_composer: function(event){ + if(this.message_composer && this.message_composer.is_empty() && !this.stay_open){ + this.$('.o_mail_chatter_compose_buttons').show(); + this.$('.o_mail_chatter_compose_message_wrap').hide(); + } + }, + /** + * Refresh the current message, and set them as current message. Use 'message_fetch', since its + * domain don't use 'ids', so if there is new messages, they'll be fetched. + * @returns {Deferred} + */ + message_refresh: function(){ + return this.message_load_new(); + }, + /** + * Render the messages + * @override + */ + message_render: function(){ + this.$('.o_mail_chatter_messages').html(QWeb.render('mail.chatter.ChatterMailThread.messages', {'widget': this})); + }, + /** + * Order the messages + * @param {Object[]} messages : messages to order + * @override + */ + _message_order: function(messages){ + var messages = mail_thread.MailThreadMixin._message_order.call(this, messages); + return messages.reverse(); + }, + /** + * Return the message domain for the current document mail thread + * @override + */ + get_message_domain: function(){ + var res_id = this.ThreadDataset.ids[this.ThreadDataset.index]; + return [['model', '=', this.ThreadDataset.model], ['res_id', '=', res_id]]; + } +}); + +core.form_widget_registry.add('mail_thread', ChatterMailThread); + + + +/** + * Global ComposeMessage Top Button + * + * Add a link on the top user bar to write a full mail. It opens the form view + * of the mail.compose.message (in a modal). + */ +var ComposeMessageTopButton = Widget.extend({ + template:'mail.ComposeMessageTopButton', + events: { + "click": "on_compose_message", + }, + on_compose_message: function (ev) { + ev.preventDefault(); + var ctx = {}; + if ($('button.o_timeline_compose_post') && $('button.o_timeline_compose_post').is(":visible") == true && + (this.getParent()).getParent().action_manager.inner_widget.active_view.type == 'form'){ + ctx = { + 'default_res_id': (this.getParent()).getParent().action_manager.inner_widget.active_view.controller.datarecord.id, + 'default_model': (this.getParent()).getParent().action_manager.inner_widget.active_view.controller.model, + }; + } + web_client.action_manager.do_action({ + type: 'ir.actions.act_window', + res_model: 'mail.compose.message', + view_mode: 'form', + view_type: 'form', + views: [[false, 'form']], + target: 'new', + context: ctx, + }); + }, +}); + +// Put the ComposeMessageTopButton widget in the systray menu +SystrayMenu.Items.push(ComposeMessageTopButton); + + +return { + ChatterMailThread: ChatterMailThread, +}; + +}); |
