summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js
diff options
context:
space:
mode:
authorValerie Pirenne <vpi@odoo.com>2015-04-29 17:32:49 +0200
committerThibault Delavallée <tde@openerp.com>2015-05-21 12:10:25 +0200
commitc24332e6da68dbed28ea8ab4702a97c49860d374 (patch)
treed4b4cd321b0a591ebcf11f2f3313d727ce8ec807 /addons/mail/static/src/js
parent946d245c2b73aa0d3ba1784626fd67e31a6c66a9 (diff)
[IMP][ADD] mail, various: timeline view.
This commit introduces a new view type, the timeline view. This view is intended to display messages like the previous chatter. This is now a view like the form or list view. Like other views it will be possible to have custom templates for some specific needs, allowing customization. [IMP] mail: the value tracking is modified. Previously messages were created containing the modified values. Those values are now stored, using a new model mail.tracking.value. The message body is dynamically build based on the values. Tests have been updated. This version is temporary. Indeed two main modifications will come in a short future : - the new design will improve the display - the slack mode will change the way the Inbox and notifications are managed All glory to the Hypnotoad. Special thanks to Valerie Pirenne (vpi), Jerome Maes (jem) and Richat Mathot (rim) that did not code but said a lot of things. Martin Trigaux (mat) did nothing, a bit like for the slides modules, but he is busy sending emails.
Diffstat (limited to 'addons/mail/static/src/js')
-rw-r--r--addons/mail/static/src/js/mail.js2778
-rw-r--r--addons/mail/static/src/js/mail_followers.js247
-rw-r--r--addons/mail/static/src/js/mail_utils.js17
-rw-r--r--addons/mail/static/src/js/suggestions.js6
4 files changed, 1325 insertions, 1723 deletions
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index fecaa24f984..4872200ef15 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -5,1956 +5,1495 @@ var mail_utils = require('mail.utils');
var core = require('web.core');
var data = require('web.data');
var form_common = require('web.form_common');
-var pyeval = require('web.pyeval');
-var SearchView = require('web.SearchView');
-var real_session = require('web.session');
+var session = require('web.session');
var SystrayMenu = require('web.SystrayMenu');
var time = require('web.time');
var utils = require('web.utils');
var web_client = require('web.web_client');
var Widget = require('web.Widget');
+var View = require('web.View');
var _t = core._t;
-var qweb = core.qweb;
+var QWeb = core.qweb;
/**
* ------------------------------------------------------------
- * MessageCommon
+ * TimelineRecordThread (mail_thread Widget)
* ------------------------------------------------------------
- *
- * Common base for expandables, chatter messages and composer. It manages
- * the various variables common to those models.
+ *
+ * This widget instantiates the timeline view on a document. Its
+ * main use is to receive a context and a domain, then display the
+ * timeline view.
*/
+var TimelineRecordThread = form_common.AbstractField.extend ({
+ className: 'o_timeline_record_thread',
-var MessageCommon = Widget.extend({
- /**
- * ------------------------------------------------------------
- * FIXME: this comment was moved as is from the ThreadMessage Init as
- * part of a refactoring. Check that it is still correct
- * ------------------------------------------------------------
- * This widget handles the display of a messages in a thread.
- * Displays a record and performs some formatting on the record :
- * - record.date: formatting according to the user timezone
- * - record.timerelative: relative time givein by timeago lib
- * - record.avatar: image url
- * - record.attachment_ids[].url: url of each attachmentThe
- * thread view :
- * - root thread
- * - - sub message (parent_id = root message)
- * - - - sub thread
- * - - - - sub sub message (parent id = sub thread)
- * - - sub message (parent_id = root message)
- * - - - sub thread
- */
-
- init: function (parent, datasets, options) {
- this._super(parent, options);
-
- // record options
- this.options = datasets.options || options || {};
- // record domain and context
- this.domain = datasets.domain || options.domain || [];
- this.context = _.extend({
- default_model: false,
- default_res_id: 0,
- default_parent_id: false }, options.context || {});
-
- // data of this message
- this.id = datasets.id || false;
- this.last_id = this.id;
- this.model = datasets.model || this.context.default_model || false;
- this.res_id = datasets.res_id || this.context.default_res_id || false;
- this.parent_id = datasets.parent_id || false;
- this.type = datasets.type || false;
- this.subtype = datasets.subtype || false;
- this.is_author = datasets.is_author || false;
- this.author_avatar = datasets.author_avatar || false;
- this.is_private = datasets.is_private || false;
- this.subject = datasets.subject || false;
- this.name = datasets.name || false;
- this.record_name = datasets.record_name || false;
- this.body = datasets.body || '';
- this.body_short = datasets.body_short || '';
- this.vote_nb = datasets.vote_nb || 0;
- this.has_voted = datasets.has_voted || false;
- this.is_favorite = datasets.is_favorite || false;
- this.thread_level = datasets.thread_level || 0;
- this.to_read = datasets.to_read || false;
- this.author_id = datasets.author_id || false;
- this.attachment_ids = datasets.attachment_ids || [];
- this.partner_ids = datasets.partner_ids || [];
- this.date = datasets.date;
- this.user_pid = datasets.user_pid || false;
- this.format_data();
- // update record_name: Partner profile
- if (this.model == 'res.partner') {
- this.record_name = 'Partner Profile of ' + this.record_name;
- }
- else if (this.model == 'hr.employee') {
- this.record_name = 'News from ' + this.record_name;
- }
- // record options and data
- this.show_record_name = this.options.show_record_name && this.record_name && !this.thread_level;
- this.options.show_read = false;
- this.options.show_unread = false;
- if (this.options.show_read_unread_button) {
- if (this.options.read_action == 'read') this.options.show_read = true;
- else if (this.options.read_action == 'unread') this.options.show_unread = true;
- else {
- this.options.show_read = this.to_read;
- this.options.show_unread = !this.to_read;
- }
- this.options.rerender = true;
- this.options.toggle_read = true;
- }
- this.parent_thread = typeof parent.on_message_detroy == 'function' ? parent : this.options.root_thread;
- this.thread = false;
- },
+ init: function (parent, node) {
+ this._super.apply(this, arguments);
- /* Convert date, timerelative and avatar in displayable data. */
- format_data: function () {
+ this.parent = parent;
+ this.dataset = new data.DataSet(this, 'mail.message', this.view.dataset.context);
+ this.domain = (this.node.params && this.node.params.domain) || (this.field && this.field.domain) || [];
- //formating and add some fields for render
- this.date = this.date ? time.str_to_datetime(this.date) : false;
- this.display_date = moment(new Date(this.date)).format('ddd MMM DD YYYY LT');
- if (this.date && new Date().getTime()-this.date.getTime() < 7*24*60*60*1000) {
- this.timerelative = $.timeago(this.date);
- }
- if (this.author_avatar) {
- this.avatar = "data:image/png;base64," + this.author_avatar;
- } else if (this.type == 'email' && (!this.author_id || !this.author_id[0])) {
- this.avatar = ('/mail/static/src/img/email_icon.png');
- } else if (this.author_id && this.template != 'mail.compose_message') {
- this.avatar = mail_utils.get_image(this.session, 'res.partner', 'image_small', this.author_id[0]);
- } else {
- this.avatar = mail_utils.get_image(real_session, 'res.users', 'image_small', real_session.uid);
- }
- if (this.author_id && this.author_id[1]) {
- var parsed_email = mail_utils.parse_email(this.author_id[1]);
- this.author_id.push(parsed_email[0], parsed_email[1]);
- }
- if (this.partner_ids && this.partner_ids.length > 3) {
- this.extra_partners_nbr = this.partner_ids.length - 3;
- this.extra_partners_str = '';
- var extra_partners = this.partner_ids.slice(3);
- for (var key in extra_partners) {
- this.extra_partners_str += extra_partners[key][1];
- }
- }
+ this.opts = _.extend({
+ 'show_reply_button': false,
+ 'show_read_unread_button': true,
+ 'read_action': 'unread',
+ 'show_record_name': false,
+ 'show_compact_message': true,
+ 'compose_as_todo': false,
+ 'view_inbox': false,
+ 'view_mailbox': false,
+ 'emails_from_on_composer': true,
+ 'fetch_limit': 20,
+ 'readonly': this.node.attrs.readonly || false,
+ 'compose_placeholder' : this.node.attrs.placeholder || false,
+ 'display_log_button' : this.options.display_log_button || true,
+ 'show_compose_message': this.view.is_action_enabled('edit') || false,
+ 'show_link': this.parent.is_action_enabled('edit') || true,
+ }, this.node.params);
},
+ start: function () {
+ this.view.on("change:actual_mode", this, this._check_visibility);
+ this._check_visibility();
- /* upload the file on the server, add in the attachments list and reload display
- */
- display_attachments: function () {
- for (var l in this.attachment_ids) {
- var attach = this.attachment_ids[l];
- if (!attach.formating) {
- attach.url = mail_utils.get_attachment_url(real_session, this.id, attach.id);
- attach.name = mail_utils.breakword(attach.name || attach.filename);
- attach.formating = true;
- }
- }
- this.$(".oe_msg_attachment_list").html( qweb.render('mail.thread.message.attachments', {'widget': this}) );
- },
-
- /* return the link to resized image
- */
- attachments_resize_image: function (id, resize) {
- return mail_utils.get_image(real_session, 'ir.attachment', 'datas', id, resize);
- },
+ this.timeline_view = new TimelineView(this, this.dataset, false, this.opts);
+ this._super.apply(this, arguments);
- /* get all child message id linked.
- * @return array of id
- */
- get_child_ids: function () {
- return _.map(this.get_childs(), function (val) { return val.id; });
+ return this.timeline_view.appendTo(this.$el);
},
- /* get all child message linked.
- * @return array of message object
- */
- get_childs: function (nb_thread_level) {
- var res=[];
- if (arguments[1] && this.id) res.push(this);
- if ((isNaN(nb_thread_level) || nb_thread_level>0) && this.thread) {
- _(this.thread.messages).each(function (val, key) {
- res = res.concat( val.get_childs((isNaN(nb_thread_level) ? undefined : nb_thread_level-1), true) );
- });
- }
- return res;
+ _check_visibility: function () {
+ this.$el.toggle(this.view.get("actual_mode") !== "create");
},
- /**
- * search a message in all thread and child thread.
- * This method return an object message.
- * @param {object}{int} option.id
- * @param {object}{string} option.model
- * @param {object}{boolean} option._go_thread_wall
- * private for check the top thread
- * @return thread object
- */
- browse_message: function (options) {
- var i, res;
- // goto the wall thread for launch browse
- if (!options._go_thread_wall) {
- options._go_thread_wall = true;
- for (i in this.options.root_thread.messages) {
- res=this.options.root_thread.messages[i].browse_message(options);
- if (res) return res;
- }
- }
-
- if (this.id === options.id)
- return this;
-
- for (i in this.thread.messages) {
- if (this.thread.messages[i].thread) {
- res=this.thread.messages[i].browse_message(options);
- if (res) return res;
- }
+ render_value: function () {
+ if (! this.view.datarecord.id || data.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
+ return;
}
- return false;
- },
-
- /**
- * call on_message_delete on his parent thread
- */
- destroy: function () {
+ this.timeline_view.set_message_ids(this.get_value());
- this._super();
- this.parent_thread.on_message_detroy(this);
+ var domain = (this.domain || []).concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]);
+ var context = {
+ 'mail_read_set_read': true,
+ 'default_res_id': this.view.datarecord.id || false,
+ 'default_model': this.view.model || false,
+ };
+ return this.timeline_view.do_search(domain, context);
}
-
});
/**
* ------------------------------------------------------------
- * ComposeMessage widget
+ * Timeline View
* ------------------------------------------------------------
- *
- * This widget handles the display of a form to compose a new message.
- * This form is a mail.compose.message form_view.
- * On first time : display a compact textarea that is not the compose form.
- * When the user focuses the textarea, the compose message is instantiated.
+ *
+ * This widget handles the display of messages on a document and in
+ * the inbox. Its main use is to receive a context and a domain, and
+ * to delegate the message fetching and displaying to the MailThread
+ * widget.
*/
+var TimelineView = View.extend ({
+ display_name: _t('Timeline'),
+ view_type: 'timeline',
+ template: 'TimelineWall',
+
+ init: function (parent, dataset, view_id, options) {
+ this._super(parent, dataset, view_id, options);
+
+ this.model = dataset.model;
+ this.domain = dataset.domain || [];
+ this.context = dataset.context || {};
+
+ var opt = _.clone(this.context.options);
+ this.options = options || {};
+ this.options = _.extend(this.options, (opt || {}));
+ this.options = _.extend({
+ 'show_link': true,
+ 'show_reply_button': true,
+ 'show_read_unread_button': true,
+ 'fetch_limit': 15,
+ 'fetch_child_limit': 5,
+ }, this.options);
-var ThreadComposeMessage = MessageCommon.extend({
- template: 'mail.compose_message',
-
- /**
- * @param {Object} parent parent
- * @param {Object} [options]
- * @param {Object} [context] context passed to the
- * mail.compose.message DataSetSearch. Please refer to this model
- * for more details about fields and default values.
- * @param {Object} recipients = [
- {
- 'email_address': [str],
- 'partner_id': False/[int],
- 'name': [str],
- 'full_name': name<email_address>,
- },
- { ... },
- ]
- */
+ this.fields_view = {};
+ this.fields_keys = [];
- init: function (parent, datasets, options) {
- this._super(parent, datasets, options);
- this.show_compact_message = false;
- this.show_delete_attachment = true;
- this.is_log = false;
- this.recipients = [];
- this.recipient_ids = [];
- core.bus.on('clear_uncommitted_changes', this, function(e) {
- if (this.show_composer && !e.isDefaultPrevented()){
- if (!confirm(_t("You are currently composing a message, your message will be discarded.\n\nAre you sure you want to leave this page ?"))) {
- e.preventDefault();
- }
- else{
- this.on_cancel();
- }
- }
- });
+ this.qweb = new QWeb2.Engine();
+ this.has_been_loaded = $.Deferred();
},
start: function () {
- this._super.apply(this, arguments);
-
- this.ds_attachment = new data.DataSetSearch(this, 'ir.attachment');
- this.fileupload_id = _.uniqueId('oe_fileupload_temp');
- $(window).on(this.fileupload_id, this.on_attachment_loaded);
+ var wall_sidebar = new Sidebar(this);
+ wall_sidebar.appendTo(this.$('.o_timeline_inbox_aside'));
+ return this._super.apply(this, arguments);
+ },
- this.display_attachments();
- this.bind_events();
+ view_loading: function (fields_view_get) {
+ return this.load_timeline(fields_view_get);
},
- /* when a user click on the upload button, send file read on_attachment_loaded
- */
- on_attachment_change: function (event) {
- event.stopPropagation();
- var $target = $(event.target);
- if ($target.val() !== '') {
+ load_timeline: function (fields_view_get) {
+ var self = this;
- var filename = $target.val().replace(/.*[\\\/]/,'');
+ this.fields_view = fields_view_get;
+ this.fields_keys = _.keys(this.fields_view.fields);
- // if the files exits for this answer, delete the file before upload
- var attachments=[];
- for (var i in this.attachment_ids) {
- if ((this.attachment_ids[i].filename || this.attachment_ids[i].name) == filename) {
- if (this.attachment_ids[i].upload) {
- return false;
- }
- this.ds_attachment.unlink([this.attachment_ids[i].id]);
- } else {
- attachments.push(this.attachment_ids[i]);
- }
- }
- this.attachment_ids = attachments;
+ this.add_qweb_template();
+ this.has_been_loaded.resolve();
+ },
- // submit file
- this.$('form.oe_form_binary_form').submit();
+ do_show: function () {
+ this.do_push_state({});
+ return this._super();
+ },
- this.$(".oe_attachment_file").hide();
+ do_search: function (dom, cxt, group_by) {
+ var self = this;
- this.attachment_ids.push({
- 'id': 0,
- 'name': filename,
- 'filename': filename,
- 'url': '',
- 'upload': true
- });
- this.display_attachments();
- }
- },
-
- /* when the file is uploaded
- */
- on_attachment_loaded: function (event, result) {
+ var domain = this.domain.concat(dom || []);
+ var context = _.clone(this.context);
+ context = _.extend(context, (cxt || {}));
+ var ds = {
+ "domain" : domain,
+ "context" : context,
+ };
+ var opts = _.clone(this.options);
- if (result.erorr || !result.id ) {
- this.do_warn( qweb.render('mail.error_upload'), result.error);
- this.attachment_ids = _.filter(this.attachment_ids, function (val) { return !val.upload; });
- } else {
- for (var i in this.attachment_ids) {
- if (this.attachment_ids[i].filename == result.filename && this.attachment_ids[i].upload) {
- this.attachment_ids[i]={
- 'id': result.id,
- 'name': result.name,
- 'filename': result.filename,
- 'url': mail_utils.get_attachment_url(this.session, this.id, result.id)
- };
- }
+ return this.has_been_loaded.then(function() {
+ if (self.thread) {
+ $('<span class="o_timeline-placeholder"/>').insertAfter(self.thread.$el);
+ self.thread.destroy();
}
- }
- this.display_attachments();
- var $input = this.$('input.oe_form_binary_file');
- $input.after($input.clone(true)).remove();
- this.$(".oe_attachment_file").show();
+ self.thread = new MailThread(self, ds, opts);
+ self.thread.message_fetch(null, null, self.options.message_ids, self.options.view_inbox ? 1 : 0);
+
+ return self.thread.replace(self.$('.o_timeline-placeholder'));
+ });
},
- /* unlink the file on the server and reload display
- */
- on_attachment_delete: function (event) {
- event.stopPropagation();
- var attachment_id=$(event.target).data("id");
- if (attachment_id) {
- var attachments=[];
- for (var i in this.attachment_ids) {
- if (attachment_id!=this.attachment_ids[i].id) {
- attachments.push(this.attachment_ids[i]);
- }
- else {
- this.ds_attachment.unlink([attachment_id]);
- }
- }
- this.attachment_ids = attachments;
- this.display_attachments();
+ do_reload_menu_emails: function () {
+ var menu = web_client.menu;
+ if (!menu || !menu.current_menu) {
+ return $.when();
}
+ return menu.rpc("/web/menu/load_needaction", {'menu_ids': [menu.current_menu]}).done(function(r) {
+ menu.on_needaction_loaded(r);
+ }).then(function () {
+ menu.trigger("need_action_reloaded");
+ });
},
- bind_events: function () {
+ render_buttons: function($node) {
var self = this;
- this.$('.oe_compact_inbox').on('click', self.on_toggle_quick_composer);
- this.$('.oe_compose_post').on('click', self.on_toggle_quick_composer);
- this.$('.oe_compose_log').on('click', self.on_toggle_quick_composer);
- this.$('input.oe_form_binary_file').on('change', _.bind( this.on_attachment_change, this));
- this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this));
- this.$('.oe_post').on('click', self.on_message_post);
- this.$('.oe_full').on('click', _.bind( this.on_compose_fullmail, this, this.id ? 'reply' : 'comment'));
- /* stack for don't close the compose form if the user click on a button */
- this.$('.oe_msg_left, .oe_msg_center').on('mousedown', _.bind( function () { this.stay_open = true; }, this));
- this.$('.oe_msg_left, .oe_msg_content').on('mouseup', _.bind( function () { this.$('textarea').focus(); }, this));
- var ev_stay = {};
- ev_stay.mouseup = ev_stay.keydown = ev_stay.focus = function () { self.stay_open = false; };
- this.$('textarea').on(ev_stay);
- this.$('textarea').autosize();
- // auto close
- this.$('textarea').on('blur', self.on_toggle_quick_composer);
-
- // event: delete child attachments off the oe_msg_attachment_list box
- this.$(".oe_msg_attachment_list").on('click', '.oe_delete', this.on_attachment_delete);
-
- this.$(".oe_recipients").on('change', 'input', this.on_checked_recipient);
- },
+ this.$buttons = $(QWeb.render("TimelineView.buttons", {'widget': this}));
+ this.$buttons
+ .on('click', 'button.o_timeline_button_new', this.on_compose_message)
+ .on('click', '.o_timeline_share', function(event) {
+ self.write_to_followers(event, self);
+ });
- on_compose_fullmail: function (default_composition_mode) {
- var self = this;
- if(!this.do_check_attachment_upload()) {
- return false;
- }
- var recipient_done = $.Deferred();
- if (this.is_log) {
- recipient_done.resolve([]);
+ $node = $node || this.options.$buttons;
+ if ($node) {
+ this.$buttons.appendTo($node);
}
else {
- recipient_done = this.check_recipient_partners();
+ this.$('.o_timeline_buttons').replaceWith(this.$buttons);
}
- $.when(recipient_done).done(function (partner_ids) {
- var context = {
- 'default_parent_id': self.id,
- 'default_body': mail_utils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
- 'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
- 'default_partner_ids': partner_ids,
- 'default_is_log': self.is_log,
- 'mail_post_autofollow': true,
- 'mail_post_autofollow_partner_ids': partner_ids,
- 'is_private': self.is_private
- };
- if (default_composition_mode != 'reply' && 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;
- }
+ },
- var action = {
- type: 'ir.actions.act_window',
- res_model: 'mail.compose.message',
- view_mode: 'form',
- view_type: 'form',
- views: [[false, 'form']],
- target: 'new',
- context: context,
- };
+ add_qweb_template: function () {
+ for (var i = 0; i < this.fields_view.arch.children.length; i++) {
+ var child = this.fields_view.arch.children[i];
+ if (child.tag === 'templates') {
+ this.qweb.add_template(utils.json_node_to_xml(child));
+ break;
+ }
+ }
+ },
- self.do_action(action);
- self.on_cancel();
+ on_compose_message: function (event) {
+ event.preventDefault();
+ web_client.action_manager.do_action({
+ type: 'ir.actions.act_window',
+ res_model: 'mail.compose.message',
+ view_mode: 'form',
+ view_type: 'form',
+ views: [[false, 'form']],
+ target: 'new',
+ context: {},
});
+ },
+ write_to_followers: function (event, view) {
+ view.thread.instantiate_write_to_followers(event);
},
- reinit: function() {
- var $render = $( qweb.render('mail.compose_message', {'widget': this}) );
+ set_message_ids: function (val) {
+ this.options.message_ids = val;
+ },
+});
- $render.insertAfter(this.$el.last());
- this.$el.remove();
- this.$el = $render;
+/**
+ * ------------------------------------------------------------
+ * Attachment
+ * ------------------------------------------------------------
+ *
+ * This widget handles some features about the attachments. Those
+ * features are common to the MailThread widget and ComposeMessage
+ * widget.
+ */
+var Attachment = Widget.extend ({
+ /**
+ * Upload the file on the server and add in the attachments list
+ */
+ format_attachments: function (message) {
+ for (var l in message.attachment_ids) {
+ var attach = message.attachment_ids[l];
+ if (!attach.formating) {
+ attach.url = mail_utils.get_attachment_url(session, message.id, attach.id);
+ attach.name = mail_utils.breakword(attach.name || attach.filename);
+ attach.formating = true;
+ }
+ }
+ return message.attachment_ids;
+ },
- this.display_attachments();
- this.bind_events();
+ display_attachments: function (message) {
+ return QWeb.render('ThreadMessageAttachments', {'record': message, 'widget': this});
},
- on_cancel: function (event) {
- if (event) event.stopPropagation();
- this.attachment_ids=[];
- this.stay_open = false;
- this.show_composer = false;
- this.reinit();
+ /**
+ * Return the link to resized image
+ */
+ attachments_resize_image: function (id, resize) {
+ return mail_utils.get_image(session, 'ir.attachment', 'datas', id, resize);
},
+});
- /* return true if all file are complete else return false and make an alert */
- do_check_attachment_upload: function () {
- if (_.find(this.attachment_ids, function (file) {return file.upload;})) {
- this.do_warn(qweb.render('mail.error_upload'), qweb.render('mail.error_upload_please_wait'));
- return false;
- } else {
- return true;
- }
+/**
+ * ------------------------------------------------------------
+ * MailThread Widget
+ * ------------------------------------------------------------
+ *
+ * This widget handles the display of a thread of messages. By
+ * default, a thread is folded in the inbox and unfolded on a
+ * document. In the inbox, a thread can be unfolded by clicking
+ * on it.
+ * When unfolded, the display of the threads looks like :
+ * - root thread
+ * - - thread 1
+ * - - - message (parent_id = thread 1)
+ * - - thread 2
+ * - - - message (parent_id = thread 2)
+ */
+var MailThread = Attachment.extend ({
+ template: 'MailThread',
+
+ events: {
+ 'click .o_timeline_msg .oe_read':'on_message_read',
+ 'click .o_timeline_msg .oe_unread':'on_message_unread',
+ 'click .o_timeline_msg .oe_star':'on_star',
+ 'click .o_timeline_parent_message .oe_reply':'on_message_reply',
+ 'click .o_timeline_parent_message .oe_read':'on_thread_read',
+ 'click .o_timeline_vote':'on_vote',
+ 'mouseenter .o_timeline_vote_count':'on_vote_count',
+ 'click .o_timeline_action_author':'on_record_author_clicked',
+ 'click .o_timeline_msg_expandable':'on_message_expandable',
+ 'click .o_timeline_parent_expandable':'on_thread_expandable',
+ 'click .o_timeline_parent_subject':'on_parent_message',
+ 'click .o_timeline_go_to_doc':'on_record_clicked',
+ 'mouseenter .oe_follwrs':'on_display_followers',
},
- check_recipient_partners: function () {
+ /**
+ * @param {Object} parent parent
+ * @param {Object} dataset
+ * @param {Object} root thread with context, domain, etc
+ * @param {Object} messages : {parent_id, list of messages}
+ * or {expandable}
+ * @param {Object} [options]
+ */
+ init: function (parent, dataset, options) {
+ this._super(parent, dataset, options);
var self = this;
- var check_done = $.Deferred();
- var recipients = _.filter(this.recipients, 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.parent_thread.ds_thread._model.call('message_partner_info_from_emails', [[this.context.default_res_id], names_to_find]);
- }
- else {
- find_done.resolve([]);
+ this.domain = dataset.domain || [];
+ this.context = _.clone(dataset.context) || {};
+ this.options = _.clone(options);
+
+ this.is_ready = $.Deferred();
+ this.root = parent instanceof TimelineView ? parent : false;
+ this.view = parent instanceof TimelineView ? parent : parent.view;
+
+ // data of this thread :
+ // - list of messages in the thread,
+ // - parent message = first message of the thread
+ // - thread type : false (if root thread), parent or expandable
+ this.messages = dataset.messages ? dataset.messages : [];
+ this.parent_message = this.messages.length ? this.messages[this.messages.length - 1] : false;
+ this.thread_type = this.messages.length ? 'parent' : false;
+ if (this.messages.length && this.messages[0].message_type == 'expandable') {
+ this.thread_type = 'expandable';
+ }
+
+ // thread represented by parent message data
+ this.id = this.parent_message.id || false;
+ this.parent_id = this.parent_message.id || false;
+ this.author_id = this.parent_message.author_id || false;
+ this.user_pid = this.parent_message.user_pid || false;
+ this.is_private = this.parent_message.is_private || false;
+ this.partner_ids = this.parent_message.partner_ids || [];
+ if (this.parent_message.author_id && !_.contains(_.flatten(this.parent_message.partner_ids),this.parent_message.author_id[0]) && this.parent_message.author_id[0]) {
+ this.partner_ids.push(this.parent_message.author_id);
+ }
+
+ if (!this.root) {
+ this.context = _.extend(this.context, {
+ 'default_model': this.parent_message.model,
+ 'default_res_id': this.parent_message.res_id,
+ });
}
- // 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);
+ this.options.root_thread = this.options.root_thread != undefined ? this.options.root_thread : this;
+ this.options.show_compose_message = this.root ? false : this.options.root_thread.view.options.show_compose_message;
+ this.options.is_folded = this.options.view_inbox ? true : false;
+ this.options.toggle_read = true;
- _.each(recipient_popups, function (partner_info) {
- var deferred = $.Deferred();
- emails_deferred.push(deferred);
+ // add some data to messages for the display
+ _.each(this.messages, function (msg) {
+ if (msg.message_type != "expandable") {
+ msg = self.format_message(msg);
+ }
+ });
- var partner_name = partner_info.full_name;
- var partner_id = partner_info.partner_id;
- var parsed_email = mail_utils.parse_email(partner_name);
+ this.model = this.parent_message.model || false;
+ this.res_id = this.parent_message.res_id || false;
+ this.record_name = this.parent_message.record_name || false;
+ this.subject = this.parent_message.subject || false;
- var pop = new form_common.FormOpenPopup(this);
- pop.show_element(
- 'res.partner',
- partner_id,
- { 'force_email': true,
- 'ref': "compound_context",
- 'default_name': parsed_email[0],
- 'default_email': parsed_email[1],
- }, {
- title: _t("Please complete partner's informations"),
- }
- );
- 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);
- }
- });
+ // add some data to thread for the display (followers, ...)
+ if (this.messages.length) {
+ this.format_thread().then(function () {
+ self.is_ready.resolve();
});
+ }else {
+ self.is_ready.resolve();
+ }
- $.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.parent_thread.ds_thread._model.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;
+ this.compose_message = false;
+
+ this.ds_thread = new data.DataSetSearch(this, this.context.default_model || 'mail.thread');
+ this.ds_message = new data.DataSetSearch(this, 'mail.message');
},
- on_message_post: function () {
+ start: function () {
var self = this;
- if (self.flag_post) {
- return;
- }
- if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
- self.flag_post = true;
- if (this.is_log) {
- this.do_send_message_post([], this.is_log);
+
+ return this.is_ready.then(function () {
+ var $el = $(QWeb.render('MailThread', {'widget': self}));
+ self.replaceElement($el);
+
+ if (self.options.show_compose_message) {
+ self.instantiate_compose_message();
+ self.compose_message.do_show_compact();
}
- else {
- this.check_recipient_partners().done(function (partner_ids) {
- self.do_send_message_post(partner_ids, self.is_log);
- });
+ });
+ },
+
+ /**
+ * instantiate the compose message object and insert this on the DOM
+ * (its place in the DOM depends on those cases : reply to thread in
+ * the inbox, write to followers in the inbox, compose new message on
+ * a document). The compose message is display in compact form.
+ */
+ instantiate_compose_message: function (option) {
+ if (!this.compose_message) {
+ this.context = this.options.compose_as_todo ? _.extend({'default_starred': true}, this.context) : this.context;
+ this.compose_message = new ComposeMessage(this, this, this.options);
+
+ if (this.options.view_inbox && option == 'reply') {
+ this.compose_message.insertAfter(this.$('.o_timeline_thread_content'));
+ } else {
+ if (this.options.view_inbox && option == 'write_to_followers') {
+ this.compose_message.prependTo(this.$el);
+ } else {
+ this.compose_message.insertBefore(this.$el);
+ }
}
}
},
- /* do post a message and fetch the message */
- do_send_message_post: function (partner_ids, log) {
- var self = this;
- var values = {
- 'body': this.$('textarea').val(),
- 'subject': false,
- 'parent_id': this.context.default_parent_id,
- 'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}),
- 'partner_ids': partner_ids,
- 'context': _.extend(this.parent_thread.context, {
- 'mail_post_autofollow': true,
- 'mail_post_autofollow_partner_ids': partner_ids,
- }),
- 'message_type': 'comment',
- 'content_subtype': 'plaintext',
- };
- if (log) {
- values.subtype = false;
- }
- else {
- values.subtype = 'mail.mt_comment';
+ /**
+ * instantiate the compose message and call the on_toggle_quick_composer
+ * method to allow the user to write his message.
+ */
+ on_compose_message: function (event, option) {
+ if (this.compose_message) {
+ this.compose_message.destroy();
}
- this.parent_thread.ds_thread._model.call('message_post', [this.context.default_res_id], values).done(function (message_id) {
- var thread = self.parent_thread;
- var root = thread == self.options.root_thread;
- if (self.options.display_indented_thread < self.thread_level && thread.parent_message) {
- thread = thread.parent_message.parent_thread;
- }
- // create object and attach to the thread object
- thread.message_fetch([["id", "=", message_id]], false, [message_id], function (arg, data) {
- var message = thread.create_message_object( data.slice(-1)[0] );
- // insert the message on dom
- thread.insert_message( message, root ? undefined : self.$el, root );
- });
- self.on_cancel();
- self.flag_post = false;
+ this.compose_message = false;
+
+ this.instantiate_compose_message(option);
+ return this.compose_message.on_toggle_quick_composer(event);
+ },
+
+ instantiate_write_to_followers: function (event) {
+ var self = this;
+ return this.on_compose_message(event, 'write_to_followers').then(function () {
+ self.$('.o_timeline_msg_composer').addClass('o_timeline_write_to_followers');
});
},
- /* Quick composer: toggle minimal / expanded mode
- * - toggle minimal (one-liner) / expanded (textarea, buttons) mode
- * - when going into expanded mode:
- * - call `message_get_suggested_recipients` to have a list of partners to add
- * - compute email_from list (list of unknown email_from to propose to create partners)
+ /**
+ * make a request to read the messages and set them 'read' if needed (calling
+ * RPC to "message_read_wrapper").
+ * @param {Array} replace_domain: domain
+ * @param {Object} replace_context: context
+ * @param {Array} ids read (if the are some ids, the method don't use the domain)
*/
- on_toggle_quick_composer: function (event) {
+ message_fetch: function (replace_domain, replace_context, ids, thread_level, callback) {
var self = this;
- var $input = $(event.target);
- this.compute_emails_from();
- var email_addresses = _.pluck(this.recipients, 'email_address');
- var suggested_partners = $.Deferred();
-
- // if clicked: call for suggested recipients
- if (event.type == 'click') {
- this.is_log = $input.hasClass('oe_compose_log');
- suggested_partners = this.parent_thread.ds_thread.call('message_get_suggested_recipients', [[this.context.default_res_id], this.context]).done(function (additional_recipients) {
- var thread_recipients = additional_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.recipients.push({
- 'checked': true,
- 'partner_id': recipient[0],
- 'full_name': recipient[1],
- 'name': parsed_email[0],
- 'email_address': parsed_email[1],
- 'reason': recipient[2],
- });
+ return this.ds_message.call('message_read_wrapper', [
+ // ids force to read
+ ids === false ? undefined : ids && ids.slice(0, this.options.fetch_limit),
+ // domain + additional
+ (replace_domain ? replace_domain : this.domain),
+ // context + additional
+ (replace_context ? replace_context : this.context),
+ // thread_level
+ thread_level,
+ // parent_id
+ this.context.default_parent_id || undefined,
+ // limits
+ this.options.fetch_limit,
+ this.options.fetch_child_limit
+ ]).done(callback ? _.bind(callback, this, arguments) : this.proxy('treat_threads')
+ ).done(function (records) {
+ // records : {
+ // nb_reab : number of read messages (status from 'unread' to 'read'),
+ // threads : list of threads [[messages_thread1], [messages_thread2]]
+ // }
+ if (records.nb_read) {
+ self.options.root_thread.view.do_reload_menu_emails();
}
- });
- });
- }
- else {
- suggested_partners.resolve({});
- }
+ });
+ },
- // when call for suggested partners finished: re-render the widget
- $.when(suggested_partners).pipe(function () {
- if ((!self.stay_open || (event && event.type == 'click')) && (!self.show_composer || !self.$('textarea:not(.oe_compact)').val().match(/\S+/) && !self.attachment_ids.length)) {
- self.show_composer = !self.show_composer || self.stay_open;
- self.reinit();
- }
- if (!self.stay_open && self.show_composer && (!event || event.type != 'blur')) {
- self.$('textarea:not(.oe_compact):first').focus();
- }
+ /**
+ * manage the list of threads recieved from message_fetch method
+ * and create a MailThread oject for each of them
+ */
+ treat_threads: function (records) {
+ var self = this;
+
+ _.each(records.threads, function (record) {
+ self.create_thread(record);
});
- return suggested_partners;
+ if (!records.threads.length) {
+ this.create_thread();
+ }
+ },
+ create_thread: function (record) {
+ var thread = new MailThread(this, _.extend(_.clone(this), {
+ 'messages': record,
+ }), this.options);
+
+ thread.appendTo(this.$el);
+ return thread;
},
- do_hide_compact: function () {
- this.show_compact_message = false;
- if (!this.show_composer) {
- this.reinit();
+ /**
+ * set a message 'read' when click on Read in message
+ */
+ on_message_read: function (event) {
+ var msg_id = $(event.target).data('id');
+ var msg = _.findWhere(this.messages, {id: msg_id});
+ event.stopPropagation();
+ if (msg_id) {
+ this.on_message_read_unread([msg], true).then(function() {
+ $(event.target).replaceWith(QWeb.render("MessageReadUnread", {record: msg}));
+ });
+
}
},
- do_show_compact: function () {
- this.show_compact_message = true;
- if (!this.show_composer) {
- this.reinit();
+ /**
+ * set a message 'unread' when click on Unread in message
+ */
+ on_message_unread: function (event) {
+ var msg_id = $(event.target).data('id');
+ var msg = _.findWhere(this.messages, {id: msg_id});
+ event.stopPropagation();
+ if (msg_id) {
+ this.on_message_read_unread([msg], false).then(function() {
+ $(event.target).replaceWith(QWeb.render("MessageReadUnread", {record: msg}));
+ });
}
},
- /** Compute the list of unknown email_from the the given thread
- * TDE FIXME: seems odd to delegate to the composer
- * TDE TODO: please de-obfuscate and comment your code */
- compute_emails_from: function () {
+ /**
+ * set all the messages of this thread 'read' when click on
+ * Read in thread
+ */
+ on_thread_read: function (event) {
var self = this;
- var messages = [];
-
- if (this.parent_thread.parent_message) {
- // go to the parented message
- var message = this.parent_thread.parent_message;
- var parent_message = message.parent_id ? message.parent_thread.parent_message : message;
- if(parent_message){
- messages = [parent_message].concat(parent_message.get_childs());
- }
- } else if (this.options.emails_from_on_composer) {
- // get all wall messages if is not a mail.Wall
- _.each(this.options.root_thread.messages, function (msg) {messages.push(msg); messages.concat(msg.get_childs());});
- }
- _.each(messages, function (thread) {
- if (thread.author_id && !thread.author_id[0] &&
- !_.find(self.recipients, function (recipient) {return recipient.email_address == thread.author_id[3];}) &&
- _.some([thread.author_id[1], thread.author_id[2], thread.author_id[3]])) {
- self.recipients.push({ 'full_name': thread.author_id[1],
- 'name': thread.author_id[2],
- 'email_address': thread.author_id[3],
- 'partner_id': false,
- 'checked': true,
- 'reason': 'Incoming email author'
- });
- }
+ event.stopPropagation();
+ this.on_message_read_unread(_.filter(this.messages, function (val) {return val.type != 'expandable'}), true).then(function () {
+ self.check_for_rerender();
});
- return self.recipients;
},
- on_checked_recipient: function (event) {
- var $input = $(event.target);
- var full_name = $input.attr("data");
- _.each(this.recipients, function (recipient) {
- if (recipient.full_name == full_name) {
- recipient.checked = $input.is(":checked");
+ /**
+ * Set the selected message(s) to 'read' or 'unread'
+ * @param {messages} array of messages
+ * @param {boolean} read_value
+ */
+ on_message_read_unread: function (messages, read_value) {
+ var self = this;
+
+ // inside the inbox, when the user mark a message as read/done, don't apply this value
+ // for the starred/favorite message
+ if (this.options.view_inbox && read_value) {
+ messages = _.filter(messages, function (val) {return !val.is_favorite && val.id;});
+ if (!messages.length) {
+ return $.Deferred();
}
+ }
+ var message_ids = _.map(messages, function (val) {return val.id;});
+ return this.ds_message.call('set_message_read', [message_ids, read_value, true, this.context]).then(function (nb_read) {
+ // apply modification
+ _.each(messages, function (msg) {
+ msg.to_read = !read_value;
+ if (self.options.toggle_read) {
+ msg.options.show_read = msg.to_read;
+ msg.options.show_unread = !msg.to_read;
+ }
+ });
});
},
-});
-
-/**
- * ------------------------------------------------------------
- * Thread Message Expandable Widget
- * ------------------------------------------------------------
- *
- * This widget handles the display the expandable message in a thread.
- * - thread
- * - - visible message
- * - - expandable
- * - - visible message
- * - - visible message
- * - - expandable
- */
-var ThreadExpandable = MessageCommon.extend({
- template: 'mail.thread.expandable',
-
- init: function (parent, datasets, options) {
- this._super(parent, datasets, options);
- this.type = 'expandable';
- this.max_limit = datasets.max_limit;
- this.nb_messages = datasets.nb_messages;
- this.flag_used = false;
- },
-
- start: function () {
- this._super.apply(this, arguments);
- this.bind_events();
- },
-
- reinit: function () {
- var $render = $(qweb.render('mail.thread.expandable', {'widget': this}));
- this.$el.replaceWith( $render );
- this.$el = $render;
- this.bind_events();
- },
/**
- * Bind events in the widget. Each event is slightly described
- * in the function. */
- bind_events: function () {
- this.$('.oe_msg_more_message').on('click', this.on_expandable);
- },
+ * add or remove a favorite (or starred) for a message and change class on the DOM
+ */
+ on_star: function (event) {
+ var self = this;
+ var msg_id = $(event.target).data('id');
+ var msg = _.findWhere(this.messages, {id: msg_id});
+ var button = self.$('.oe_star:first');
+ event.stopPropagation();
- animated_destroy: function (fadeTime) {
- var self=this;
- this.$el.fadeOut(fadeTime, function () {
- self.destroy();
- });
- },
+ this.ds_message.call('set_message_starred', [[msg_id], !msg.is_favorite, true]).then(function (star) {
+ msg.is_favorite = star;
+ if (msg.is_favorite) {
+ button.addClass('oe_starred');
+ }else {
+ button.removeClass('oe_starred');
+ }
- /*The selected thread and all childs (messages/thread) became read
- * @param {object} mouse envent
- */
- on_expandable: function (event) {
- var self = this;
- if (event)event.stopPropagation();
- if (this.flag_used) {
- return false;
- }
- this.flag_used = true;
-
- // read messages
- self.parent_thread.message_fetch(this.domain, this.context, false, function (arg, data) {
- self.id = false;
- // insert the message on dom after this message
- var element = self.$el;
- if (self.thread_level === 0){
- element = element.parent();
+ if (self.options.view_inbox && msg.is_favorite) {
+ self.on_message_read_unread([msg], true);
}
- self.parent_thread.switch_new_message(data, element);
- self.animated_destroy(200);
});
+ return false;
+ },
+ /**
+ * instantiate ComposeMessage when click on reply
+ */
+ on_message_reply:function (event) {
+ event.stopPropagation();
+ this.on_compose_message(event, 'reply');
return false;
},
-});
+ /**
+ * Add or remove a vote for a message and display the result
+ */
+ on_vote: function (event) {
+ var self = this;
+ var msg_id = $(event.target).data('id');
+ var msg = _.findWhere(this.messages, {id: msg_id});
+ event.stopPropagation();
-var ThreadMessage = MessageCommon.extend({
- template: 'mail.thread.message',
-
- start: function () {
- this._super.apply(this, arguments);
- this.bind_events();
- if(this.thread_level < this.options.display_indented_thread) {
- this.create_thread();
- }
- this.display_attachments();
+ this.ds_message.call('vote_toggle', [[msg_id]]).then(_.bind(function (vote) {
+ msg.has_voted = vote;
+ msg.vote_nb += msg.has_voted ? 1 : -1;
+ }, self)). then(function () {
+ $(event.target).html(QWeb.render("MessageVote", {record: msg}));
+ });
- this.ds_notification = new data.DataSetSearch(this, 'mail.notification');
- this.ds_message = new data.DataSetSearch(this, 'mail.message');
+ return false;
},
/**
- * Bind events in the widget. Each event is slightly described
- * in the function. */
- bind_events: function () {
- // header icons bindings
- this.$('.oe_read').on('click', this.on_message_read);
- this.$('.oe_unread').on('click', this.on_message_unread);
- this.$('.oe_msg_delete').on('click', this.on_message_delete);
- this.$('.oe_reply').on('click', this.on_message_reply);
- this.$('.oe_star').on('click', this.on_star);
- this.$('.oe_msg_vote').on('click', this.on_vote);
- this.$('.oe_mail_vote_count').on('mouseenter', this.on_hover);
- this.$('.oe_mail_expand').on('click', this.on_expand);
- this.$('.oe_mail_reduce').on('click', this.on_expand);
- this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
- this.$('.oe_mail_action_author').on('click', this.on_record_author_clicked);
- },
- on_hover : function(event){
- var self = this;
+ * display users who voted for the message (tooltip)
+ */
+ on_vote_count : function (event) {
var voter = "";
var limit = 10;
+ var msg_id = $(event.target).data('id');
event.stopPropagation();
+
var $target = $(event.target).hasClass("fa-thumbs-o-up") ? $(event.target).parent() : $(event.target);
- //Note: We can set data-content attr on target element once we fetch data so that next time when one moves mouse on element it saves call
- //But if there is new like comes then we'll not have new likes in popover in that case
+ // Note: We can set data-content attr on target element once we fetch data so that
+ // next time when one moves mouse on element it saves call
+ // But if there is new like comes then we'll not have new likes in popover in that case
if ($target.data('liker-list'))
{
voter = $target.data('liker-list');
- self.bindTooltipTo($target, voter);
+ mail_utils.bindTooltipTo($target, voter, 'top');
$target.tooltip('hide').tooltip('show');
$(".tooltip").on("mouseleave", function () {
$(this).remove();
});
- }else{
- this.ds_message.call('get_likers_list', [this.id, limit])
- .done(function (data) {
+ } else {
+ this.ds_message.call('get_likers_list', [msg_id, limit]).done(function (data) {
_.each(data, function(people, index) {
voter = voter + people.substring(0,1).toUpperCase() + people.substring(1);
- if(index != data.length-1) {
+ if (index != data.length-1) {
voter = voter + "<br/>";
}
});
$target.data('liker-list', voter);
- self.bindTooltipTo($target, voter);
+ mail_utils.bindTooltipTo($target, voter, 'top');
$target.tooltip('hide').tooltip('show');
$(".tooltip").on("mouseleave", function () {
$(this).remove();
});
});
}
+
return true;
},
- bindTooltipTo: function($el, value) {
- $el.tooltip({
- 'title': value,
- 'placement': 'top',
- 'container': this.el,
- 'html': true,
- 'trigger': 'manual',
- 'animation': false
- }).on("mouseleave", function () {
- setTimeout(function () {
- if (!$(".tooltip:hover").length) {
- $el.tooltip("hide");
- }
- },100);
- });
- },
- on_record_clicked: function (event) {
- event.preventDefault();
- var self = this;
- var state = {
- 'model': this.model,
- 'id': this.res_id,
- 'title': this.record_name
- };
- web_client.action_manager.do_push_state(state);
- this.context.params = {
- model: this.model,
- res_id: this.res_id,
- };
- this.thread.ds_thread.call("message_redirect_action", {context: this.context}).then(function(action){
- self.do_action(action);
- });
- },
- on_record_author_clicked: function (event) {
+ /**
+ * go to form view of the message author
+ */
+ on_record_author_clicked: function (event) {
event.preventDefault();
var partner_id = $(event.target).data('partner');
+ var record_name = $(event.target).data('record');
var state = {
'model': 'res.partner',
'id': partner_id,
- 'title': this.record_name
+ 'title': record_name
};
+
web_client.action_manager.do_push_state(state);
- var action = {
+ this.do_action({
type:'ir.actions.act_window',
view_type: 'form',
view_mode: 'form',
res_model: 'res.partner',
views: [[false, 'form']],
res_id: partner_id,
- };
- this.do_action(action);
- },
-
- /* Call the on_compose_message on the thread of this message. */
- on_message_reply:function (event) {
- event.stopPropagation();
- this.create_thread();
- this.thread.on_compose_message(event);
- return false;
- },
-
- on_expand: function (event) {
- event.stopPropagation();
- this.$('.oe_msg_body:first > .oe_msg_body_short:first').toggle();
- this.$('.oe_msg_body:first > .oe_msg_body_long:first').toggle();
- return false;
- },
-
- /**
- * Instantiate the thread object of this message.
- * Each message have only one thread.
- */
- create_thread: function () {
- if (this.thread) {
- return false;
- }
- /*create thread*/
- this.thread = new Thread(this, this, {
- 'domain': this.domain,
- 'context':{
- 'default_model': this.model,
- 'default_res_id': this.res_id,
- 'default_parent_id': this.id
- },
- 'options': this.options
- }
- );
- /*insert thread in parent message*/
- this.thread.insertAfter(this.$el);
- },
-
- /**
- * Fade out the message and his child thread.
- * Then this object is destroyed.
- */
- animated_destroy: function (fadeTime) {
- var self=this;
- this.$el.fadeOut(fadeTime, function () {
- self.parent_thread.message_to_expandable(self);
});
- if (this.thread) {
- this.thread.$el.fadeOut(fadeTime);
- }
},
/**
- * Wait a confirmation for delete the message on the DB.
- * Make an animate destroy
+ * go to the document
*/
- on_message_delete: function (event) {
+ on_record_clicked: function (event) {
+ event.preventDefault();
event.stopPropagation();
- if (! confirm(_t("Do you really want to delete this message?"))) { return false; }
-
- this.animated_destroy(150);
- // delete this message and his childs
- var ids = [this.id].concat( this.get_child_ids() );
- this.ds_message.unlink(ids);
- return false;
- },
- /* Check if the message must be destroy and detroy it or check for re render widget
- * @param {callback} apply function
- */
- check_for_rerender: function () {
var self = this;
+ var state = {
+ 'model': this.context.default_model,
+ 'id': this.context.default_res_id,
+ 'title': this.record_name,
+ };
- var messages = [this].concat(this.get_childs());
- var message_ids = _.map(messages, function (msg) { return msg.id;});
- var domain = mail_utils.expand_domain( this.options.root_thread.domain )
- .concat([["id", "in", message_ids ]]);
-
- return this.parent_thread.ds_message.call('message_read_wrapper', [undefined, domain, [], !!this.parent_thread.options.display_indented_thread, this.context, this.parent_thread.id])
- .then( function (records) {
- // remove message not loaded
- _.map(messages, function (msg) {
- if(!_.find(records, function (record) { return record.id == msg.id; })) {
- msg.animated_destroy(150);
- } else {
- msg.renderElement();
- msg.start();
- }
- self.options.root_thread.MailWidget.do_reload_menu_emails();
- });
+ web_client.action_manager.do_push_state(state);
- });
- },
+ this.context.params = {
+ model: this.context.default_model,
+ res_id: this.context.default_res_id,
+ };
- on_message_read: function (event) {
- event.stopPropagation();
- this.on_message_read_unread(true);
- return false;
+ this.ds_thread.call("message_redirect_action", {context: this.context}).then(function(action){
+ self.do_action(action);
+ });
},
- on_message_unread: function (event) {
+ /**
+ * load next messages when click on 'see more messages' (inside a thread)
+ */
+ on_message_expandable: function (event) {
event.stopPropagation();
- this.on_message_read_unread(false);
- return false;
- },
- /* Set the selected thread and all childs as read or unread, based on
- * read parameter.
- * @param {boolean} read_value
- */
- on_message_read_unread: function (read_value) {
var self = this;
- var messages = [this].concat(this.get_childs());
-
- // inside the inbox, when the user mark a message as read/done, don't apply this value
- // for the stared/favorite message
- if (this.options.view_inbox && read_value) {
- messages = _.filter(messages, function (val) { return !val.is_favorite && val.id; });
- if (!messages.length) {
- this.check_for_rerender();
- return false;
- }
+ if (this.options.view_inbox) {
+ var parent_message = this.messages.pop();
}
- var message_ids = _.map(messages, function (val) { return val.id; });
-
- this.ds_message.call('set_message_read', [message_ids, read_value, true, this.context])
- .then(function () {
- // apply modification
- _.each(messages, function (msg) {
- msg.to_read = !read_value;
- if (msg.options.toggle_read) {
- msg.options.show_read = msg.to_read;
- msg.options.show_unread = !msg.to_read;
- }
- });
- // check if the message must be display, destroy or rerender
- self.check_for_rerender();
+ var msg = this.messages.pop();
+
+ this.message_fetch(msg.domain, this.context, false, 0, function (arg, data) {
+ var messages = data.threads[0];
+ _.each(messages, function (message) {
+ self.add_message(message, true);
});
- return false;
+ if (this.options.view_inbox) {
+ this.messages.push(parent_message);
+ }
+ self.check_for_rerender();
+ });
},
/**
- * add or remove a vote for a message and display the result
- */
- on_vote: function (event) {
+ * load next threads when click on 'see more messages'
+ */
+ on_thread_expandable: function (event) {
event.stopPropagation();
- this.ds_message.call('vote_toggle', [[this.id]])
- .then(
- _.bind(function (vote) {
- this.has_voted = vote;
- this.vote_nb += this.has_voted ? 1 : -1;
- this.display_vote();
- }, this));
- return false;
+ var self = this;
+ var msg = this.messages.pop();
+
+ this.options.root_thread.message_fetch(msg.domain, this.context, false, 1).then(function () {
+ self.$el.remove();
+ self.destroy();
+ });
},
- /**
- * Display the render of this message's vote
+ /**
+ * unfolded the thread by rerender it
*/
- display_vote: function () {
- var vote_element = qweb.render('mail.thread.message.vote', {'widget': this});
- this.$(".oe_msg_footer:first .oe_mail_vote_count").remove();
- this.$(".oe_msg_footer:first .oe_msg_vote").replaceWith(vote_element);
- this.$('.oe_msg_vote').on('click', this.on_vote);
- this.$('.oe_mail_vote_count').on('mouseenter', this.on_hover);
+ on_parent_message: function (event) {
+ event.stopPropagation();
+ this.options.is_folded = !this.options.is_folded;
+ this.check_for_rerender();
},
/**
- * add or remove a favorite (or starred) for a message and change class on the DOM
- */
- on_star: function (event) {
+ * get and display the users following the document (tooltip)
+ */
+ on_display_followers: function (event) {
+ var self = this;
+ var $target = $(event.target);
event.stopPropagation();
- var self=this;
- var button = self.$('.oe_star:first');
-
- this.ds_message.call('set_message_starred', [[self.id], !self.is_favorite, true])
- .then(function (star) {
- self.is_favorite=star;
- if (self.is_favorite) {
- button.addClass('oe_starred');
- } else {
- button.removeClass('oe_starred');
- }
- if (self.options.view_inbox && self.is_favorite) {
- self.on_message_read_unread(true);
- } else {
- self.check_for_rerender();
+ if (this.followers_data) {
+ mail_utils.bindTooltipTo($target, this.followers_data, 'right');
+ $target.tooltip('hide').tooltip('show');
+ $(".tooltip").on("mouseleave", function () {
+ $(this).remove();
+ });
+ }
+ else {
+ var ds_model = new data.DataSetSearch(this, this.model, this.context);
+ ds_model.call('read_followers_data', [this.follower_ids.slice(0, this.options.followers_limit)]).then(function (records) {
+ self.followers_data = [];
+ _.each(records, function(f) {
+ self.followers_data.push(f[1] + '</br>');
+ });
+ if (self.nb_followers - self.followers_limit > 0) {
+ self.followers_data.push('and ' + self.nb_followers - self.followers_limit + 'more ... </br>');
}
+ mail_utils.bindTooltipTo($target, self.followers_data, 'right');
+ $target.tooltip('hide').tooltip('show');
+ $(".tooltip").on("mouseleave", function () {
+ $(this).remove();
+ });
});
- return false;
+ }
},
-});
-
-/**
- * ------------------------------------------------------------
- * Thread Widget
- * ------------------------------------------------------------
- *
- * This widget handles the display of a thread of messages. The
- * thread view:
- * - root thread
- * - - sub message (parent_id = root message)
- * - - - sub thread
- * - - - - sub sub message (parent id = sub thread)
- * - - sub message (parent_id = root message)
- * - - - sub thread
- */
-var Thread = Widget.extend({
- template: 'mail.thread',
-
/**
- * @param {Object} parent parent
- * @param {Array} [domain]
- * @param {Object} [context] context of the thread. It should
- contain at least default_model, default_res_id. Please refer to
- the ComposeMessage widget for more information about it.
- * @param {Object} [options]
- * @param {Object} [message] read about mail.ThreadMessage object
- * @param {Object} [thread]
- * @param {int} [display_indented_thread] number thread level to indented threads.
- * other are on flat mode
- * @param {Array} [parents] liked with the parents thread
- * use with browse, fetch... [O]= top parent
+ * rerender this thread after some change
*/
- init: function (parent, datasets, options) {
- this._super(parent, options);
- this.MailWidget = parent instanceof MailWidget ? parent : false;
- this.domain = options.domain || [];
- this.context = _.extend(options.context || {});
-
- this.options = options.options;
- this.options.root_thread = (options.options.root_thread !== undefined ? options.options.root_thread : this);
- this.options.show_compose_message = this.options.show_compose_message && !this.thread_level;
-
- // record options and data
- this.parent_message= parent.thread !== undefined ? parent : false ;
-
- // data of this thread
- this.id = datasets.id || false;
- this.last_id = datasets.last_id || false;
- this.parent_id = datasets.parent_id || false;
- this.is_private = datasets.is_private || false;
- this.author_id = datasets.author_id || false;
- this.thread_level = (datasets.thread_level+1) || 0;
- datasets.partner_ids = datasets.partner_ids || [];
- if (datasets.author_id && !_.contains(_.flatten(datasets.partner_ids),datasets.author_id[0]) && datasets.author_id[0]) {
- datasets.partner_ids.push(datasets.author_id);
- }
- this.user_pid = datasets.user_pid || false;
- this.partner_ids = datasets.partner_ids;
- this.messages = [];
- this.options.flat_mode = (this.options.display_indented_thread - this.thread_level > 0);
-
- // object compose message
- this.compose_message = false;
-
- this.ds_thread = new data.DataSetSearch(this, this.context.default_model || 'mail.thread');
- this.ds_message = new data.DataSetSearch(this, 'mail.message');
- this.render_mutex = new utils.Mutex();
- },
-
- start: function () {
- this._super.apply(this, arguments);
- this.bind_events();
- return $.when();
- },
+ check_for_rerender: function () {
+ var self = this;
- /* instantiate the compose message object and insert this on the DOM.
- * The compose message is display in compact form.
- */
- instantiate_compose_message: function () {
- // add message composition form view
- if (!this.compose_message) {
- this.compose_message = new ThreadComposeMessage(this, this, {
- 'context': this.options.compose_as_todo && !this.thread_level ? _.extend(this.context, { 'default_starred': true }) : this.context,
- 'options': this.options,
+ if (this.messages.length) {
+ this.is_ready = $.Deferred();
+ this.format_thread().then(function () {
+ self.is_ready.resolve();
});
- if (!this.thread_level || this.thread_level > this.options.display_indented_thread) {
- this.compose_message.insertBefore(this.$el);
- } else {
- this.compose_message.prependTo(this.$el);
- }
}
- },
- /* When the expandable object is visible on screen (with scrolling)
- * then the on_expandable function is launch
- */
- on_scroll: function () {
- _.each( _.filter(this.messages, function (val) {return val.max_limit && !val.parent_id;}), function (val) {
- var pos = val.$el.position();
- if (pos.top) {
- /* bottom of the screen */
- var bottom = $(window).scrollTop()+$(window).height()+200;
- if (bottom > pos.top) {
- val.on_expandable();
- }
- }
+ this.is_ready.then(function () {
+ var $el = $(QWeb.render('MailThread', {'widget': self}));
+ self.replaceElement($el);
+ self.options.root_thread.view.do_reload_menu_emails();
});
},
/**
- * Bind events in the widget. Each event is slightly described
- * in the function. */
- bind_events: function () {
- var self = this;
- self.$('.oe_mail_list_recipients .oe_more').on('click', self.on_show_recipients);
- self.$('.oe_mail_compose_textarea .oe_more_hidden').on('click', self.on_hide_recipients);
+ * add a message to this list of messages of this thread (after reply e.g.)
+ */
+ add_message: function (message, to_end) {
+ message = this.format_message(message);
+ if (to_end) {
+ this.messages.push(message);
+ }else{
+ this.messages.unshift(message);
+ }
},
/**
- *show all the partner list of this parent message
- */
- on_show_recipients: function () {
- var p=$(this).parent();
- p.find('.oe_more_hidden, .oe_hidden').show();
- p.find('.oe_more').hide();
- return false;
+ * insert a message in the DOM
+ */
+ insert_message: function (message) {
+ message.content = this.view.qweb.render("timeline-message", {record: message});
+ this.$('.oe_view_nocontent').remove();
+ if (this.options.view_inbox && !this.root) {
+ $(message.content).insertAfter(this.$('.o_timeline_thread_content'));
+ }else{
+ if (this.options.view_inbox && this.root) {
+ $(message.content).appendTo(this.$('.o_timeline_thread_parent:first'));
+ } else {
+ $(message.content).prependTo(this.$el);
+ }
+ }
},
/**
- *hide a part of the partner list of this parent message
- */
- on_hide_recipients: function () {
- var p=$(this).parent();
- p.find('.oe_more_hidden, .oe_hidden').hide();
- p.find('.oe_more').show();
- return false;
- },
+ * add and convert thread data for display
+ */
+ format_thread: function () {
+ var self = this;
- /* get all child message/thread id linked.
- * @return array of id
- */
- get_child_ids: function () {
- return _.map(this.get_childs(), function (val) { return val.id; });
- },
+ this.followers_loaded = $.Deferred();
- /* get all child message/thread linked.
- * @param {int} nb_thread_level, number of traversed thread level for this search
- * @return array of thread object
- */
- get_childs: function (nb_thread_level) {
- var res=[];
- if (arguments[1]) res.push(this);
- if (isNaN(nb_thread_level) || nb_thread_level>0) {
- _(this.messages).each(function (val) {
- if (val.thread) {
- res = res.concat( val.thread.get_childs((isNaN(nb_thread_level) ? undefined : nb_thread_level-1), true) );
- }
+ if ((! this.nb_followers || !this.follower_ids) && (this.model && this.res_id)) {
+ this.nb_followers = 0;
+ this.follower_ids = [];
+
+ this.ds_res = new data.DataSetSearch(this, this.model, this.context, [['id', '=', this.res_id]]);
+ this.ds_res.read_slice(['id', 'message_follower_ids']).then(function (data) {
+ self.follower_ids = data[0].message_follower_ids;
+ self.nb_followers = data[0].message_follower_ids.length;
+ self.followers_loaded.resolve();
});
+ } else {
+ this.followers_loaded.resolve();
}
- return res;
+
+ return this.followers_loaded.then(function () {
+ var qweb_context = {
+ record: self.messages[0],
+ widget: self,
+ options: self.options,
+ };
+ self.content = self.view.qweb.render("timeline-thread", qweb_context);
+ });
},
/**
- *search a thread in all thread and child thread.
- * This method return an object thread.
- * @param {object}{int} option.id
- * @param {object}{string} option.model
- * @param {object}{boolean} option._go_thread_wall
- * private for check the top thread
- * @param {object}{boolean} option.default_return_top_thread
- * return the top thread (wall) if no thread found
- * @return thread object
+ * add and convert message data for display
*/
- browse_thread: function (options) {
- // goto the wall thread for launch browse
- if (!options._go_thread_wall) {
- options._go_thread_wall = true;
- return this.options.root_thread.browse_thread(options);
+ format_message: function (msg) {
+ msg.tracking_values = msg.tracking_value_ids ? (msg.tracking_value_ids.length > 0) : false;
+ msg.partners = msg.partner_ids ? (msg.partner_ids.length > 0) : false;
+ msg.attachments = msg.attachment_ids ? (msg.attachment_ids.length > 0) : false;
+ msg.parent_id = msg.parent_id || false;
+
+ msg.date = this.format_date(msg.date);
+ msg.author_avatar = this.format_avatar(msg.author_avatar, msg.author_id, msg.message_type);
+ msg.author_id = this.format_author(msg.author_id);
+
+ if (msg.partners) {
+ msg.partners_nb = this.format_partners_nb(msg.partner_ids);
+ msg.partners_str = this.format_partners_str(msg.partner_ids);
}
- if (this.id == options.id) {
- return this;
+ if (msg.attachments) {
+ msg.attachments_content = this.display_attachments(msg);
}
- if (options.id) {
- for (var i in this.messages) {
- if (this.messages[i].thread) {
- var res = this.messages[i].thread.browse_thread({'id':options.id, '_go_thread_wall':true});
- if (res) return res;
- }
+ if (msg.model == 'res.partner') {
+ msg.record_name = _.str.sprintf(_t('Partner Profile of ' + msg.record_name));
+ }else{
+ if (msg.model == 'hr.employee') {
+ msg.record_name = _.str.sprintf(_t('News from ' + msg.record_name));
}
}
- //if option default_return_top_thread, return the top if no found thread
- if (options.default_return_top_thread) {
- return this;
- }
+ msg.parent_thread = this;
- return false;
- },
+ msg.options = _.clone(this.options);
+ msg.options.show_read = false;
+ msg.options.show_unread = false;
- /**
- *search a message in all thread and child thread.
- * This method return an object message.
- * @param {object}{int} option.id
- * @param {object}{string} option.model
- * @param {object}{boolean} option._go_thread_wall
- * private for check the top thread
- * @return message object
- */
- browse_message: function (options) {
- if (this.options.root_thread.messages[0])
- return this.options.root_thread.messages[0].browse_message(options);
+ if (this.options.show_read_unread_button) {
+ if (this.options.read_action == 'read') {
+ msg.options.show_read = true;
+ }else{
+ if (this.options.read_action == 'unread') {
+ msg.options.show_unread = true;
+ }else {
+ msg.options.show_read = msg.to_read;
+ msg.options.show_unread = !msg.to_read;
+ }
+ }
+ }
+ return msg;
},
/**
- *If compose_message doesn't exist, instantiate the compose message.
- * Call the on_toggle_quick_composer method to allow the user to write his message.
- * (Is call when a user click on "Reply" button)
- */
- on_compose_message: function (event) {
- this.instantiate_compose_message();
- this.compose_message.on_toggle_quick_composer(event);
- return false;
+ * Convert date in displayable data.
+ */
+ format_date: function (date) {
+ var d = date ? time.str_to_datetime(date) : false;
+ if (d) {
+ var display_date = moment(new Date(d).toISOString()).format('ddd MMM DD YYYY LT');
+ date = display_date;
+ if (new Date().getTime()-d.getTime() < 7*24*60*60*1000) {
+ var timerelative = $.timeago(d);
+ date = timerelative;
+ }
+ }
+ return date;
},
/**
- *display the message "there are no message" on the thread
- */
- no_message: function () {
- var no_message = $(qweb.render('mail.wall_no_message', {}));
- if (this.options.help) {
- no_message.html(this.options.help);
+ * Convert avatar in displayable data.
+ */
+ format_avatar: function (avatar, author, type) {
+ var avt = false;
+ if (avatar) {
+ avt = "data:image/png;base64," + avatar;
+ }else if (type == 'email' && (!author || !author[0])) {
+ avt = ('/mail/static/src/img/email_icon.png');
}
- if (!this.$el.find(".oe_view_nocontent").length)
- {
- no_message.appendTo(this.$el);
+ else if (author) {
+ avt = mail_utils.get_image(
+ session, 'res.partner', 'image_small', author[0]);
+ }else {
+ avt = mail_utils.get_image(
+ session, 'res.users', 'image_small', session.uid);
}
+ return avt;
},
- /**
- *make a request to read the message (calling RPC to "message_read").
- * The result of this method is send to the switch message for sending ach message to
- * his parented object thread.
- * @param {Array} replace_domain: added to this.domain
- * @param {Object} replace_context: added to this.context
- * @param {Array} ids read (if the are some ids, the method don't use the domain)
- */
- message_fetch: function (replace_domain, replace_context, ids, callback) {
- return this.ds_message.call('message_read_wrapper', [
- // ids force to read
- ids === false ? undefined : ids && ids.slice(0, this.options.fetch_limit),
- // domain + additional
- (replace_domain ? replace_domain : this.domain),
- // ids allready loaded
- (this.id ? [this.id].concat( this.get_child_ids() ) : this.get_child_ids()),
- // option for sending in flat mode by server
- this.options.flat_mode,
- // context + additional
- (replace_context ? replace_context : this.context),
- // parent_id
- this.context.default_parent_id || undefined,
- this.options.fetch_limit,
- ]).done(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message')
- ).done(this.proxy('message_fetch_set_read'));
+ format_author: function (author) {
+ if (author && author[1]) {
+ var parsed_email = mail_utils.parse_email(author[1]);
+ author.push(parsed_email[0], parsed_email[1]);
+ }
+ return author;
},
- message_fetch_set_read: function (message_list) {
- if (! this.context.mail_read_set_read) return;
- var self = this;
- this.render_mutex.exec(function() {
- var msg_ids = _.pluck(message_list, 'id');
- return self.ds_message.call('set_message_read', [msg_ids, true, false, self.context])
- .then(function (nb_read) {
- if (nb_read) {
- self.options.root_thread.MailWidget.do_reload_menu_emails();
- }
- });
- });
+ format_partners_nb: function (partners) {
+ return partners.length - 3;
},
- /**
- *create the message object and attached on this thread.
- * When the message object is create, this method call insert_message for,
- * displaying this message on the DOM.
- * @param : {object} data from calling RPC to "message_read"
- */
- create_message_object: function (data) {
- var self = this;
- var message;
+ format_partners_str: function (partners) {
+ if (partners && partners.length > 3) {
+ var extra_partners_nbr = this.format_partners_nb(partners);
+ var extra_partners_str = ''
+ var extra_partners = partners.slice(3);
- data.thread_level = self.thread_level || 0;
- data.options = _.extend(data.options || {}, self.options);
+ for (var key in extra_partners) {
+ extra_partners_str += extra_partners[key][1];
+ }
- if (data.type=='expandable') {
- message = new ThreadExpandable(self, data, {'context':{
- 'default_model': data.model || self.context.default_model,
- 'default_res_id': data.res_id || self.context.default_res_id,
- 'default_parent_id': self.id,
- }});
- } else {
- data.record_name= (data.record_name !== '' && data.record_name) || (self.parent_message && self.parent_message.record_name);
- message = new ThreadMessage(self, data, {'context':{
- 'default_model': data.model,
- 'default_res_id': data.res_id,
- 'default_parent_id': data.id,
- }});
+ return extra_partners_str;
}
+ },
+});
+
+/**
+ * ------------------------------------------------------------
+ * ComposeMessage widget
+ * ------------------------------------------------------------
+ *
+ * This widget handles the display to compose a new message.
+ */
+var ComposeMessage = Attachment.extend ({
+ template: 'ComposeMessage',
+
+ events: {
+ 'click .o_timeline_compose_post':'on_toggle_quick_composer',
+ 'click .o_timeline_compose_log':'on_toggle_quick_composer',
+ 'click .oe_post':'on_message_post',
+ 'click .oe_full':'on_full',
+ 'click .o_timeline_msg_attachment_list .oe_delete': 'on_attachment_delete',
+ 'change input.oe_form_binary_file': 'on_attachment_change',
+ 'blur textarea':'on_toggle_quick_composer',
+ 'mousedown .o_timeline_msg_center':function (event) {
+ this.stay_open = true;
+ },
+ 'mouseup .o_timeline_msg_content':function (event) {
+ this.$('textarea').focus();
+ },
+ 'change .o_timeline_recipients input':'on_checked_recipient',
+ },
+
+ init: function (parent, dataset, options) {
+ this._super(parent, dataset, options);
+
+ this.domain = dataset.domain || [];
+ this.context = _.clone(dataset.context) || {};
+ this.options = _.clone(options);
+
+ this.show_compact_message = false;
+ this.show_delete_attachment = true;
+ this.is_log = false;
+
+ this.id = dataset.id || false;
+ this.user_pid = dataset.user_pid || false;
+ this.parent_id = dataset.parent_id || false;
+ this.is_private = dataset.is_private || false;
+ this.partner_ids = dataset.partner_ids || [];
+ this.recipients = [];
+ this.recipient_ids = [];
+ this.attachment_ids = [];
+
+ this.parent_thread = parent;
+ this.view = parent.view;
+ this.session = session;
- // check if the message is already create
- for (var i in self.messages) {
- if (message.id && self.messages[i] && self.messages[i].id === message.id) {
- self.messages[i].destroy();
+ core.bus.on('clear_uncommitted_changes', this, function (e) {
+ if (this.show_composer && !e.isDefaultPrevented()) {
+ if (!confirm(_t("You are currently composing a message, your message will be discarded.\n\nAre you sure you want to leave this page ?"))) {
+ e.preventDefault();
+ }
+ else {
+ this.on_cancel();
+ }
}
- }
- self.messages.push( message );
+ });
+ },
- return message;
+ start: function () {
+ this.ds_attachment = new data.DataSetSearch(this, 'ir.attachment');
+ this.fileupload_id = _.uniqueId('oe_fileupload_temp');
+ $(window).on(this.fileupload_id, this.on_attachment_loaded);
+ this.$(".o_timeline_msg_attachment_list").html(this.display_attachments(this));
+ this.bind_events();
+ return this._super.apply(this, arguments);
},
- /**
- *insert the message on the DOM.
- * All message (and expandable message) are sorted. The method get the
- * older and newer message to insert the message (before, after).
- * If there are no older or newer, the message is prepend or append to
- * the thread (parent object or on root thread for flat view).
- * The sort is define by the thread_level (O for newer on top).
- * @param : {object} ThreadMessage object
- */
- insert_message: function (message, dom_insert_after, prepend) {
- var self=this;
- if (this.options.show_compact_message > this.thread_level) {
- this.instantiate_compose_message();
- this.compose_message.do_show_compact();
- }
+ reinit: function () {
+ var $render = $(QWeb.render('ComposeMessage', {'widget': this}));
+ this.replaceElement($render);
+ this.$(".o_timeline_msg_attachment_list").html(this.display_attachments(this));
+ this.bind_events();
+ },
- this.$('.oe_view_nocontent').remove();
- if (dom_insert_after && dom_insert_after.parent()[0] == self.$el[0]) {
- message.insertAfter(dom_insert_after);
- } else if (prepend) {
- message.prependTo(self.$el);
- } else {
- message.appendTo(self.$el);
- }
- message.$el.hide().fadeIn(500);
+ bind_events: function () {
+ var self = this;
+ var ev_stay = {};
+ ev_stay.mouseup = ev_stay.keydown = ev_stay.focus = function () {self.stay_open = false;};
+ this.$('textarea').on(ev_stay);
+ this.$('textarea').autosize();
+ },
- return message;
+ on_cancel: function (event) {
+ if (event) event.stopPropagation();
+ this.attachment_ids = [];
+ this.stay_open = false;
+ this.show_composer = false;
+ this.reinit();
},
-
+
/**
- *get the parent thread of the messages.
- * Each message is send to his parent object (or parent thread flat mode) for creating the object message.
- * @param : {Array} datas from calling RPC to "message_read"
+ * compute the list of unknown email_from the the given thread
*/
- switch_new_message: function (records, dom_insert_after) {
+ compute_emails_from: function () {
var self = this;
- dom_insert_after = typeof dom_insert_after == 'object' ? dom_insert_after : false;
- _(records).each(function (record) {
- var thread = self.browse_thread({
- 'id': record.parent_id,
- 'default_return_top_thread':true
- });
- // create object and attach to the thread object
- var message = thread.create_message_object( record );
- // insert the message on dom
- thread.insert_message( message, dom_insert_after);
+ var messages = this.parent_thread.messages;
+ _.each(messages, function (thread) {
+ if (thread.author_id && !thread.author_id[0] &&
+ !_.find(self.recipients, function (recipient) {return recipient.email_address == thread.author_id[3];})) {
+ self.recipients.push({'full_name': thread.author_id[1],
+ 'name': thread.author_id[2],
+ 'email_address': thread.author_id[3],
+ 'partner_id': false,
+ 'checked': true,
+ 'reason': 'Incoming email author'
+ });
+ }
});
- if (!records.length && this.options.root_thread == this) {
- this.no_message();
- }
+ return self.recipients;
},
/**
- * this method is call when the widget of a message or an expandable message is destroy
- * in this thread. The this.messages array is filter to remove this message
+ * Quick composer: toggle minimal / expanded mode
+ * - toggle minimal (one-liner) / expanded (textarea, buttons) mode
+ * - when going into expanded mode:
+ * - call `message_get_suggested_recipients` to have a list of partners to add
+ * - compute email_from list (list of unknown email_from to propose to create partners)
*/
- on_message_detroy: function () {
+ on_toggle_quick_composer: function (event) {
+ var self = this;
+ var $input = $(event.target);
- this.messages = _.filter(this.messages, function (val) { return !val.isDestroyed(); });
- if (this.options.root_thread == this && !this.messages.length) {
- this.no_message();
- }
- return false;
+ this.compute_emails_from();
+ var email_addresses = _.pluck(this.recipients, 'email_address');
- },
+ var suggested_partners = $.Deferred();
- /**
- * Convert a destroyed message into a expandable message
- */
- message_to_expandable: function (message) {
+ // if clicked: call for suggested recipients
+ if (event.type == 'click') {
+ this.is_log = $input.hasClass('o_timeline_compose_log');
+ suggested_partners = this.parent_thread.ds_thread.call('message_get_suggested_recipients', [[this.context.default_res_id], this.context]).done(function (additional_recipients) {
+ var thread_recipients = additional_recipients[self.context.default_res_id];
- if (!this.thread_level || message.isDestroyed()) {
- message.destroy();
- return false;
+ _.each(thread_recipients, function (recipient) {
+ var parsed_email = mail_utils.parse_email(recipient[1]);
+ if (_.indexOf(email_addresses, parsed_email[1]) == -1) {
+ self.recipients.push({
+ 'checked': true,
+ 'partner_id': recipient[0],
+ 'full_name': recipient[1],
+ 'name': parsed_email[0],
+ 'email_address': parsed_email[1],
+ 'reason': recipient[2],
+ })
+ }
+ });
+ });
+ } else {
+ suggested_partners.resolve({});
}
- var messages = _.sortBy( this.messages, function (val) { return val.id; });
-
- var msg_up = message.$el.prev('.oe_msg');
- var msg_down = message.$el.next('.oe_msg');
- msg_up = msg_up.hasClass('oe_msg_expandable') ? _.find( this.messages, function (val) { return val.$el[0] == msg_up[0]; }) : false;
- msg_down = msg_down.hasClass('oe_msg_expandable') ? _.find( this.messages, function (val) { return val.$el[0] == msg_down[0]; }) : false;
-
- var message_dom = [ ["id", "=", message.id] ];
-
- if ( msg_up && msg_up.type == "expandable" && msg_down && msg_down.type == "expandable") {
- // concat two expandable message and add this message to this dom
- msg_up.domain = mail_utils.expand_domain( msg_up.domain );
- msg_down.domain = mail_utils.expand_domain( msg_down.domain );
-
- msg_down.domain = ['|','|'].concat( msg_up.domain ).concat( message_dom ).concat( msg_down.domain );
-
- if ( !msg_down.max_limit ) {
- msg_down.nb_messages += 1 + msg_up.nb_messages;
+ // when call for suggested partners finished: re-render the widget
+ $.when(suggested_partners).pipe(function (additional_recipients) {
+ if ((!self.stay_open || (event && event.type == 'click'))
+ && (!self.show_composer || !self.$('textarea:not(.o_timeline_compact)').val().match(/\S+/)
+ && !self.attachment_ids.length)) {
+ self.show_composer = !self.show_composer || self.stay_open;
+ self.reinit();
}
-
- msg_up.$el.remove();
- msg_up.destroy();
-
- msg_down.reinit();
-
- } else if ( msg_up && msg_up.type == "expandable") {
- // concat preview expandable message and this message to this dom
- msg_up.domain = mail_utils.expand_domain( msg_up.domain );
- msg_up.domain = ['|'].concat( msg_up.domain ).concat( message_dom );
-
- msg_up.nb_messages++;
-
- msg_up.reinit();
-
- } else if ( msg_down && msg_down.type == "expandable") {
- // concat next expandable message and this message to this dom
- msg_down.domain = mail_utils.expand_domain( msg_down.domain );
- msg_down.domain = ['|'].concat( msg_down.domain ).concat( message_dom );
-
- // it's maybe a message expandable for the max limit read message
- if ( !msg_down.max_limit ) {
- msg_down.nb_messages++;
+ if (!self.stay_open && self.show_composer && (!event || event.type != 'blur')) {
+ self.$('textarea:not(.o_timeline_compact):first').focus();
}
-
- msg_down.reinit();
+ });
- } else {
- // create a expandable message
- var expandable = new ThreadExpandable(this, {
- 'model': message.model,
- 'parent_id': message.parent_id,
- 'nb_messages': 1,
- 'thread_level': message.thread_level,
- 'domain': message_dom,
- 'options': message.options,
- }, {
- 'context':{
- 'default_model': message.model || this.context.default_model,
- 'default_res_id': message.res_id || this.context.default_res_id,
- 'default_parent_id': this.id,
- }
- });
+ return suggested_partners;
+ },
- // add object on array and DOM
- this.messages.push(expandable);
- expandable.insertAfter(message.$el);
+ on_message_post: function (event) {
+ if (this.flag_post){
+ return;
}
- // destroy message
- message.destroy();
-
- return true;
+ var self = this;
+ this.flag_post = true;
+ if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
+ if (this.is_log) {
+ this.do_send_message_post([], this.is_log);
+ } else {
+ this.check_recipient_partners().done(function (partner_ids) {
+ self.do_send_message_post(partner_ids, self.is_log);
+ });
+ }
+ }
},
-});
-/**
- * ------------------------------------------------------------
- * mail : root Widget
- * ------------------------------------------------------------
- *
- * This widget handles the display of messages with thread options. Its main
- * use is to receive a context and a domain, and to delegate the message
- * fetching and displaying to the Thread widget.
- */
-var MailWidget = Widget.extend({
- template: 'mail.Root',
/**
- * @param {Object} parent parent
- * @param {Array} [domain]
- * @param {Object} [context] context of the thread. It should
- * contain at least default_model, default_res_id. Please refer to
- * the compose_message widget for more information about it.
- * @param {Object} [options]
- *... @param {Number} [truncate_limit=250] number of character to
- * display before having a "show more" link; note that the text
- * will not be truncated if it does not have 110% of the parameter
- *... @param {Boolean} [show_record_name] display the name and link for do action
- *... @param {boolean} [show_reply_button] display the reply button
- *... @param {boolean} [show_read_unread_button] display the read/unread button
- *... @param {int} [display_indented_thread] number thread level to indented threads.
- * other are on flat mode
- *... @param {Boolean} [show_compose_message] allow to display the composer
- *... @param {Boolean} [show_compact_message] display the compact message on the thread
- * when the user clic on this compact mode, the composer is open
- *... @param {Array} [message_ids] List of ids to fetch by the root thread.
- * When you use this option, the domain is not used for the fetch root.
- * @param {String} [no_message] Message to display when there are no message
- * @param {Boolean} [show_link] Display partner (authors, followers...) on link or not
- * @param {Boolean} [compose_as_todo] The root composer mark automatically the message as todo
- * @param {Boolean} [readonly] Read only mode, hide all action buttons and composer
+ * post a message and fetch the message
*/
- init: function (parent, action) {
- this._super(parent, action);
- this.action = _.clone(action);
- this.domain = this.action.domain || this.action.params.domain || [];
- this.context = this.action.context || this.action.params.context || {};
-
- this.action.params = _.extend({
- 'display_indented_thread' : -1,
- 'show_reply_button' : false,
- 'show_read_unread_button' : false,
- 'truncate_limit' : 250,
- 'show_record_name' : false,
- 'show_compose_message' : false,
- 'show_compact_message' : false,
- 'compose_placeholder': false,
- 'show_link': true,
- 'view_inbox': false,
- 'message_ids': undefined,
- 'compose_as_todo' : false,
- 'readonly' : false,
- 'emails_from_on_composer': true,
- 'fetch_limit': 30 // limit of chatter messages
- }, this.action.params);
+ do_send_message_post: function (partner_ids, log) {
+ var self = this;
- this.action.params.help = this.action.help || false;
- },
+ var values = {
+ 'body': this.$('textarea').val(),
+ 'subject': false,
+ 'parent_id': this.parent_id || this.id,
+ 'partner_ids': partner_ids,
+ 'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}),
+ 'context': _.extend(this.parent_thread.context, {
+ 'mail_post_autofollow': true,
+ 'mail_post_autofollow_partner_ids': partner_ids,
+ }),
+ 'message_type': 'comment',
+ 'content_subtype': 'plaintext',
+ };
- start: function () {
- this._super.apply(this, arguments);
- this.message_render();
- },
-
- /**
- * create an object "related_menu"
- * contains the menu widget and the sub menu related of this wall
- */
- do_reload_menu_emails: function () {
- var menu = web_client.menu;
- if (!menu || !menu.current_menu) {
- return $.when();
+ if(log){
+ values.subtype = false;
+ }else{
+ values.subtype = 'mail.mt_comment';
}
- return menu.rpc("/web/menu/load_needaction", {'menu_ids': [menu.current_menu]}).done(function(r) {
- menu.on_needaction_loaded(r);
- }).then(function () {
- menu.trigger("need_action_reloaded");
- });
- },
- /**
- *Create the root thread and display this object in the DOM.
- * Call the no_message method then c all the message_fetch method
- * of this root thread to display the messages.
- */
- message_render: function () {
+ this.parent_thread.ds_thread._model.call('message_post', [this.context.default_res_id], values).done(function (message_id) {
+ var thread = self.parent_thread;
- this.thread = new Thread(this, {}, {
- 'domain' : this.domain,
- 'context' : this.context,
- 'options': this.action.params,
- });
+ // attach message to the thread object
+ thread.message_fetch([["id", "=", message_id]], false, [message_id], 0, function (arg, data) {
+ var message = data.threads[0][0];
+ thread.add_message(message, false);
+ thread.insert_message(message);
+ });
- this.thread.appendTo( this.$el );
+ self.on_cancel();
+ self.flag_post = false;
+ });
+ },
- if (this.action.params.show_compose_message) {
- this.thread.instantiate_compose_message();
- this.thread.compose_message.do_show_compact();
- }
+ check_recipient_partners: function () {
+ var self = this;
+ var check_done = $.Deferred();
- this.thread.message_fetch(null, null, this.action.params.message_ids);
+ var recipients = _.filter(this.recipients, 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.parent_thread.ds_thread._model.call('message_partner_info_from_emails', [[this.context.default_res_id], names_to_find]);
+ }
+ else {
+ find_done.resolve([]);
+ }
-});
-core.action_registry.add('mail.Widget', MailWidget);
+ // 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);
-/**
- * ------------------------------------------------------------
- * mail_thread Widget
- * ------------------------------------------------------------
- *
- * This widget handles the display of messages on a document. Its main
- * use is to receive a context and a domain, and to delegate the message
- * fetching and displaying to the Thread widget.
- * Use Help on the field to display a custom "no message loaded"
- */
-var RecordThread = form_common.AbstractField.extend({
- template: 'mail.record_thread',
+ var partner_name = partner_info.full_name;
+ var partner_id = partner_info.partner_id;
+ var parsed_email = mail_utils.parse_email(partner_name);
- init: function (parent, node) {
- this._super.apply(this, arguments);
- this.ParentViewManager = parent;
- this.node = _.clone(node);
- this.node.params = _.extend({
- 'display_indented_thread': -1,
- 'show_reply_button': false,
- 'show_read_unread_button': true,
- 'read_action': 'unread',
- 'show_record_name': false,
- 'show_compact_message': 1,
- 'display_log_button' : true,
- }, this.node.params);
- if (this.node.attrs.placeholder) {
- this.node.params.compose_placeholder = this.node.attrs.placeholder;
- }
- if (this.node.attrs.readonly) {
- this.node.params.readonly = this.node.attrs.readonly;
- }
- if ('display_log_button' in this.options) {
- this.node.params.display_log_button = this.options.display_log_button;
- }
- this.domain = (this.node.params && this.node.params.domain) || (this.field && this.field.domain) || [];
+ var pop = new form_common.FormOpenPopup(this);
+ pop.show_element(
+ 'res.partner',
+ partner_id,
+ { 'force_email': true,
+ 'ref': "compound_context",
+ 'default_name': parsed_email[0],
+ 'default_email': parsed_email[1],
+ }, {
+ title: _t("Please complete partner's informations"),
+ }
+ );
+ 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);
+ }
+ });
+ });
- if (!this.ParentViewManager.is_action_enabled('edit')) {
- this.node.params.show_link = false;
- }
+ $.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.parent_thread.ds_thread._model.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;
},
- start: function () {
- this._super.apply(this, arguments);
- // NB: check the actual_mode property on view to know if the view is in create mode anymore
- this.view.on("change:actual_mode", this, this._check_visibility);
- this._check_visibility();
+ on_full: function () {
+ return this.on_compose_fullmail(this.id ? 'reply' : 'comment');
},
- _check_visibility: function () {
- this.$el.toggle(this.view.get("actual_mode") !== "create");
- },
+ on_compose_fullmail: function (default_composition_mode) {
+ if (!this.do_check_attachment_upload())
+ return false;
- render_value: function () {
- if (! this.view.datarecord.id || data.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
- this.$('oe_mail_thread').hide();
- return;
+ var self = this;
+ var recipient_done = $.Deferred();
+
+ if (this.is_log){
+ recipient_done.resolve([]);
+ }else{
+ recipient_done = this.check_recipient_partners();
}
- this.node.params = _.extend(this.node.params, {
- 'message_ids': this.get_value(),
- 'show_compose_message': this.view.is_action_enabled('edit'),
- });
- this.node.context = {
- 'mail_read_set_read': true, // set messages as read in Chatter
- 'default_res_id': this.view.datarecord.id || false,
- 'default_model': this.view.model || false,
- };
+ $.when(recipient_done).done(function (partner_ids) {
+ var context = {
+ 'default_parent_id': self.id,
+ 'default_body': mail_utils.get_text2html(
+ self.$el ? (self.$el.find('textarea:not(.o_timeline_compact)').val() || '') : ''),
+ 'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
+ 'default_partner_ids': partner_ids,
+ 'default_is_log': self.is_log,
+ 'mail_post_autofollow': true,
+ 'mail_post_autofollow_partner_ids': partner_ids,
+ 'is_private': self.is_private,
+ };
- if (this.root) {
- $('<span class="oe_mail-placeholder"/>').insertAfter(this.root.$el);
- this.root.destroy();
- }
- // create and render Thread widget
- this.root = new MailWidget(this, _.extend(this.node, {
- 'domain' : (this.domain || []).concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]),
- }));
+ if (default_composition_mode != 'reply' && 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;
+ }
- return this.root.replace(this.$('.oe_mail-placeholder'));
+ var action = {
+ type: 'ir.actions.act_window',
+ res_model: 'mail.compose.message',
+ view_mode: 'form',
+ view_type: 'form',
+ views: [[false, 'form']],
+ target: 'new',
+ context: context,
+ };
+ self.do_action(action);
+ self.on_cancel();
+ });
},
-});
-core.form_widget_registry.add('mail_thread', RecordThread);
-/**
- * ------------------------------------------------------------
- * Aside Widget
- * ------------------------------------------------------------
- *
- * This widget handles the display of a sidebar on the Wall. Its main use
- * is to display group and employees suggestion (if hr is installed).
- */
-var WallSidebar = Widget.extend({
- template: 'mail.wall.sidebar',
-});
+ on_checked_recipient: function (event) {
+ var $input = $(event.target);
+ var full_name = $input.attr("data");
+ _.each(this.recipients, function (recipient) {
+ if (recipient.full_name == full_name) {
+ recipient.checked = $input.is(":checked");
+ }
+ });
+ },
+ /**
+ * when a user click on the upload button, send file read on_attachment_loaded
+ */
+ on_attachment_change: function (event) {
+ event.stopPropagation();
+ var $target = $(event.target);
+ if ($target.val() !== '') {
+ var filename = $target.val().replace(/.*[\\\/]/,'');
-/**
- * ------------------------------------------------------------
- * Wall Widget
- * ------------------------------------------------------------
- *
- * This widget handles the display of messages on a Wall. Its main
- * use is to receive a context and a domain, and to delegate the message
- * fetching and displaying to the Thread widget.
- */
+ // if the files exits for this answer, delete the file before upload
+ var attachments = [];
+ for (var i in this.attachment_ids) {
+ if ((this.attachment_ids[i].filename || this.attachment_ids[i].name) == filename) {
+ if (this.attachment_ids[i].upload) {
+ return false;
+ }
+ this.ds_attachment.unlink([this.attachment_ids[i].id]);
+ } else {
+ attachments.push(this.attachment_ids[i]);
+ }
+ }
+ this.attachment_ids = attachments;
-var MailWall = Widget.extend({
- template: 'mail.wall',
+ // submit file
+ this.$('form.oe_form_binary_form').submit();
+ this.$(".oe_attachment_file").hide();
- /**
- * @param {Object} parent parent
- * @param {Object} [options]
- * @param {Array} [options.domain] domain on the Wall
- * @param {Object} [options.context] context, is an object. It should
- * contain default_model, default_res_id, to give it to the threads.
- */
- init: function (parent, action) {
- var key;
- this._super(parent, action);
- this.ActionManager = parent;
-
- this.action = _.clone(action);
- this.domain = this.action.params.domain || this.action.domain || [];
- this.context = _.extend(this.action.params.context || {}, this.action.context || {});
-
- // filter some parameters that we will propagate as search_default
- this.defaults = {};
- for (key in this.action.context.params) {
- if (_.indexOf(['model', 'res_id'], key) == -1) {
- continue;
- }
- this.context['search_default_' + key] = this.action.context.params[key];
- }
- for (key in this.context) {
- if (key.match(/^search_default_/)) {
- this.defaults[key.replace(/^search_default_/, '')] = this.context[key];
- }
- }
- this.action.params = _.extend({
- 'display_indented_thread': 1,
- 'show_reply_button': true,
- 'show_read_unread_button': true,
- 'show_compose_message': true,
- 'show_record_name': true,
- 'show_compact_message': this.action.params.view_mailbox ? false : 1,
- 'view_inbox': false,
- 'emails_from_on_composer': false,
- 'fetch_limit': 30 // allow inbox to load all children messages
- }, this.action.params);
- },
+ this.attachment_ids.push({
+ 'id': 0,
+ 'name': filename,
+ 'filename': filename,
+ 'url': '',
+ 'upload': true
+ });
- start: function () {
- this._super.apply(this);
- this.bind_events();
- this.load_searchview(this.defaults);
- if (! this.searchview.has_defaults) {
- this.message_render();
+ this.$(".o_timeline_msg_attachment_list").html(this.display_attachments(this));
}
- // render sidebar
- var wall_sidebar = new WallSidebar(this);
- wall_sidebar.appendTo(this.$el.find('.oe_mail_wall_aside'));
},
/**
- * Load the mail.message search view
- * @param {Object} defaults ??
+ * when the file is uploaded
*/
- load_searchview: function (defaults) {
- var self = this,
- ds_msg = new data.DataSetSearch(this, 'mail.message'),
- options = {
- $buttons: this.$('.oe-search-options'),
- action: this.action,
- };
- this.searchview = new SearchView(this, ds_msg, false, defaults || {}, options);
- this.searchview.on('search_data', this, this.do_searchview_search);
- this.searchview.appendTo(this.$('.oe-cp-search-view')).then(function () {
- self.searchview.toggle_visibility(true);
- });
- if (this.searchview.has_defaults) {
- this.searchview.ready.then(this.searchview.do_search);
+ on_attachment_loaded: function (event, result) {
+ if (result.erorr || !result.id ) {
+ this.do_warn(QWeb.render('mail.error_upload'), result.error);
+ this.attachment_ids = _.filter(this.attachment_ids, function (val) { return !val.upload; });
+ }
+ else {
+ for (var i in this.attachment_ids) {
+ if (this.attachment_ids[i].filename == result.filename && this.attachment_ids[i].upload) {
+ this.attachment_ids[i] = {
+ 'id': result.id,
+ 'name': result.name,
+ 'filename': result.filename,
+ 'url': mail_utils.get_attachment_url(session, this.id, result.id)
+ };
+ }
+ }
}
- return this.searchview;
+
+ this.$(".o_timeline_msg_attachment_list").html(this.display_attachments(this));
+
+ var $input = this.$('input.oe_form_binary_file');
+ $input.after($input.clone(true)).remove();
+ this.$(".oe_attachment_file").show();
},
/**
- * Get the domains, contexts and groupbys in parameter from search
- * view, then render the filtered threads.
- * @param {Array} domains
- * @param {Array} contexts
- * @param {Array} groupbys
+ * Unlink the file on the server and reload display
*/
- do_searchview_search: function (domains, contexts, groupbys) {
- var self = this;
- pyeval.eval_domains_and_contexts({
- domains: domains || [],
- contexts: contexts || [],
- group_by_seq: groupbys || []
- }).then(function (results) {
- if(self.root) {
- $('<span class="oe_mail-placeholder"/>').insertAfter(self.root.$el);
- self.root.destroy();
+ on_attachment_delete: function (event) {
+ event.stopPropagation();
+ var attachment_id = $(event.target).data("id");
+
+ if (attachment_id) {
+ var attachments = [];
+ for (var i in this.attachment_ids) {
+ if (attachment_id != this.attachment_ids[i].id) {
+ attachments.push(this.attachment_ids[i]);
+ }
+ else {
+ this.ds_attachment.unlink([attachment_id]);
+ }
}
- return self.message_render(results);
- });
+ this.attachment_ids = attachments;
+ this.$(".o_timeline_msg_attachment_list").html(this.display_attachments(this));
+ }
},
/**
- * Create the root thread widget and display this object in the DOM
+ * return true if all file are complete else return false and make an alert
*/
- message_render: function (search) {
- var domain = this.domain.concat(search && search.domain ? search.domain : []);
- var context = _.extend(this.context, search && search.context ? search.context : {});
-
- this.root = new MailWidget(this, _.extend(this.action, {
- 'domain' : domain,
- 'context' : context,
- }));
- return this.root.replace(this.$('.oe_mail-placeholder'));
+ do_check_attachment_upload: function () {
+ if (_.find(this.attachment_ids, function (file) {return file.upload;})) {
+ this.do_warn(_t("Uploading error"), _t("Please, wait while the file is uploading."));
+ return false;
+ }
+ return true;
},
- bind_events: function () {
- var self=this;
- this.$(".oe_write_full").click(function (event) {
- event.stopPropagation();
- var action = {
- name: _t('Compose Email'),
- type: 'ir.actions.act_window',
- res_model: 'mail.compose.message',
- view_mode: 'form',
- view_type: 'form',
- action_from: 'mail.ThreadComposeMessage',
- views: [[false, 'form']],
- target: 'new',
- context: {
- },
- };
- web_client.action_manager.do_action(action);
- });
- this.$(".oe_write_onwall").click(function (event) { self.root.thread.on_compose_message(event); });
- }
+ do_show_compact: function () {
+ this.show_compact_message = true;
+ if (!this.show_composer) {
+ this.reinit();
+ }
+ },
});
-core.action_registry.add('mail.wall', MailWall);
/**
* ------------------------------------------------------------
- * Sub-widgets loading
+ * Aside Widget
* ------------------------------------------------------------
- *
- * Load here widgets that could depend on widgets defined in mail.js
+ *
+ * This widget handles the display of a sidebar in the inbox. Its main
+ * use is to display group and employees suggestion (if hr is installed).
*/
-
-// openerp.mail.suggestions(session, mail); // import suggestion.js (suggestion widget)
+var Sidebar = Widget.extend({
+ template: 'TimelineSidebar',
+});
/**
* ------------------------------------------------------------
@@ -1964,20 +1503,23 @@ core.action_registry.add('mail.wall', MailWall);
* Add a link on the top user bar for write a full mail
*/
var ComposeMessageTopButton = Widget.extend({
- template:'mail.compose_message_top_button',
+ template:'ComposeMessageTopButton',
+
events: {
"click": "on_compose_message",
},
+
on_compose_message: function (ev) {
ev.preventDefault();
var ctx = {}
- if ($('a.oe_compose_post') && $('a.oe_compose_post').is(":visible") == true && (this.getParent()).getParent().action_manager.inner_widget.active_view.type == 'form'){
+ if ($('a.oe_compose_post') && $('a.oe_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,
- }
+ };
}
- var action = {
+ web_client.action_manager.do_action({
type: 'ir.actions.act_window',
res_model: 'mail.compose.message',
view_mode: 'form',
@@ -1985,19 +1527,21 @@ var ComposeMessageTopButton = Widget.extend({
views: [[false, 'form']],
target: 'new',
context: ctx,
- };
- web_client.action_manager.do_action(action);
+ });
},
});
// Put the ComposeMessageTopButton widget in the systray menu
SystrayMenu.Items.push(ComposeMessageTopButton);
+core.view_registry.add('timeline', TimelineView);
+core.form_widget_registry.add('mail_thread', TimelineRecordThread);
+
return {
- Thread: Thread,
- RecordThread: RecordThread,
- WallSidebar: WallSidebar,
- Wall: MailWall,
+ TimelineView: TimelineView,
+ MailThread: MailThread,
+ TimelineRecordThread: TimelineRecordThread,
+ Sidebar: Sidebar,
};
});
diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js
index 1f5c385fc11..b58ba3f45a7 100644
--- a/addons/mail/static/src/js/mail_followers.js
+++ b/addons/mail/static/src/js/mail_followers.js
@@ -14,7 +14,7 @@ var qweb = core.qweb;
/**
* ------------------------------------------------------------
- * mail_followers Widget
+ * timeline_followers Widget
* ------------------------------------------------------------
*
* This widget handles the display of a list of records as a vertical
@@ -26,19 +26,19 @@ var qweb = core.qweb;
var Followers = form_common.AbstractField.extend({
- template: 'mail.followers',
+ template: 'mail_followers',
init: function() {
this._super.apply(this, arguments);
+
this.image = this.node.attrs.image || 'image_small';
this.comment = this.node.attrs.help || false;
- this.displayed_limit = this.node.attrs.displayed_nb || 10;
- this.displayed_nb = this.displayed_limit;
this.ds_model = new data.DataSetSearch(this, this.view.model);
this.ds_users = new data.DataSetSearch(this, 'res.users');
this.value = [];
this.followers = [];
+ this.data_subtype = {};
this.view_is_editable = this.__parentedParent.is_action_enabled('edit');
},
@@ -49,7 +49,7 @@ var Followers = form_common.AbstractField.extend({
this.on_check_visibility_mode();
this.reinit();
this.bind_events();
- this._super();
+ return this._super();
},
on_check_visibility_mode: function () {
@@ -62,37 +62,43 @@ var Followers = form_common.AbstractField.extend({
},
reinit: function() {
- this.message_is_follower = false;
+ this.data_subtype = {};
+ this.message_is_follower == undefined;
this.display_buttons();
},
bind_events: function() {
var self = this;
+
// event: click on '(Un)Follow' button, that toggles the follow for uid
- this.$('.oe_follower').on('click', function (event) {
- if($(this).hasClass('oe_notfollow'))
+ this.$('.o_timeline_follower').on('click', function (event) {
+ if($(this).hasClass('o_timeline_notfollow')) {
self.do_follow();
- else
+ }
+ else {
self.do_unfollow();
+ }
});
+
// event: click on a subtype, that (un)subscribe for this subtype
- this.$el.on('click', '.oe_subtype_list input', function(event) {
+ this.$el.on('click', '.o_timeline_subtype_list input', function(event) {
self.do_update_subscription(event);
- var $list = self.$('.oe_subtype_list');
+ var $list = self.$('.o_timeline_subtype_list');
if(!$list.hasClass('open')) {
$list.addClass('open');
}
- if(self.$('.oe_subtype_list ul')[0].children.length < 1) {
+ if(self.$('.o_timeline_subtype_list ul')[0].children.length < 1) {
$list.removeClass('open');
}
event.stopPropagation();
});
+
// event: click on 'invite' button, that opens the invite wizard
- this.$('.oe_invite').on('click', self.on_invite_follower);
+ this.$el.on('click', '.oe_invite', self.on_invite_follower);
+
// event: click on 'edit_subtype(pencil)' button to edit subscription
this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
- this.$el.on('click', '.oe_show_more', self.on_show_more_followers);
this.$el.on('click', 'a[data-partner]', self.on_follower_clicked);
},
@@ -137,19 +143,16 @@ var Followers = form_common.AbstractField.extend({
});
},
- on_show_more_followers: function (event) {
- this.displayed_nb += this.displayed_limit;
- this.display_followers(false);
- },
-
on_remove_follower: function (event) {
var partner_id = $(event.target).data('id');
- var name = $(event.target).parent().find("a").html();
- if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
- var context = new data.CompoundContext(this.build_context(), {});
- return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
- .then(this.proxy('read_value'));
- }
+ var name = $(event.target).parent().find("a").html();
+ if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
+ var context = new data.CompoundContext(this.build_context(), {});
+ return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id],
+ [partner_id],
+ context])
+ .then(this.proxy('read_value'));
+ }
},
on_follower_clicked: function (event) {
@@ -174,11 +177,11 @@ var Followers = form_common.AbstractField.extend({
read_value: function () {
var self = this;
- this.displayed_nb = this.displayed_limit;
- return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).then(function (results) {
- self.value = results[0].message_follower_ids;
- self.render_value();
- });
+ return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids'])
+ .then(function (results) {
+ self.value = results[0].message_follower_ids;
+ self.render_value();
+ });
},
render_value: function () {
@@ -200,10 +203,13 @@ var Followers = form_common.AbstractField.extend({
fetch_generic: function (error, event) {
var self = this;
event.preventDefault();
- return this.ds_users.call('read', [[session.uid], ['partner_id']]).then(function (results) {
- var pid = results[0]['partner_id'][0];
- self.message_is_follower = (_.indexOf(self.value, pid) != -1);
- }).then(self.proxy('display_generic'));
+
+ return this.ds_users.call('read', [[session.uid], ['partner_id']])
+ .then(function (results) {
+ var pid = results[0]['partner_id'][0];
+ self.message_is_follower = (_.indexOf(self.value, pid) != -1);
+ })
+ .then(self.proxy('display_generic'));
},
_format_followers: function(count){
@@ -220,8 +226,8 @@ var Followers = form_common.AbstractField.extend({
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
- this.$('.oe_follower_list').empty();
- this.$('.oe_follower_title').html(this._format_followers(this.value.length));
+ var node_user_list = this.$('.o_timeline_follower_list').empty();
+ this.$('.o_timeline_follower_title').html(this._format_followers(this.value.length));
},
/** Display the followers */
@@ -229,140 +235,169 @@ var Followers = form_common.AbstractField.extend({
var self = this;
this.message_is_follower = false;
this.followers = records || this.followers;
+
// clean and display title
- var node_user_list = this.$('.oe_follower_list').empty();
- this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
- self.message_is_follower = _.indexOf(this.followers.map(function (rec) { return rec[2]['is_uid'];}), true) != -1;
+ var node_user_list = this.$('.o_timeline_follower_list').empty();
+ this.$('.o_timeline_follower_title').html(this._format_followers(this.followers.length));
+
+ $(qweb.render('mail_followers_add_more', {'widget': self})).appendTo(node_user_list);
+
+ self.message_is_follower = _.indexOf(this.followers.map(function(rec) {return rec[2]['is_uid']}), true) != -1;
// truncate number of displayed followers
- var truncated = this.followers.slice(0, this.displayed_nb);
- _(truncated).each(function (record) {
+ _(this.followers).each(function (record) {
var partner = {
'id': record[0],
'name': record[1],
'is_uid': record[2]['is_uid'],
'is_editable': record[2]['is_editable'],
'avatar_url': mail_utils.get_image(session, 'res.partner', 'image_small', record[0]),
- };
- $(qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
+ }
+
+ $(qweb.render('mail_followers_partner', {'record': partner, 'widget': self}))
+ .appendTo(node_user_list);
+
// On mouse-enter it will show the edit_subtype pencil.
if (partner.is_editable) {
- self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
- self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
- self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
+ self.$('.o_timeline_follower_list').on('mouseenter mouseleave', function(e) {
+ self.$('.o_timeline_edit_subtype')
+ .toggleClass('oe_hidden', e.type == 'mouseleave');
+ self.$('.o_timeline_follower_list').find('.oe_partner')
+ .toggleClass('oe_partner_name', e.type == 'mouseenter');
});
}
});
- // FVA note: be sure it is correctly translated
- if (truncated.length < this.followers.length) {
- $(qweb.render('mail.followers.show_more', {'number': (this.followers.length - truncated.length)} )).appendTo(node_user_list);
- }
},
display_buttons: function () {
if (this.message_is_follower) {
- this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
+ this.$('button.o_timeline_follower').removeClass('o_timeline_notfollow').addClass('o_timeline_following');
}
else {
- this.$('button.oe_follower').removeClass('oe_following').addClass('oe_notfollow');
+ this.$('button.o_timeline_follower').removeClass('o_timeline_following').addClass('o_timeline_notfollow');
}
- if (this.view.is_action_enabled('edit'))
- this.$('span.oe_mail_invite_wrapper').hide();
- else
- this.$('span.oe_mail_invite_wrapper').show();
+ if (this.view.is_action_enabled('edit')) {
+ this.$('span.oe_timeline_invite_wrapper').hide();
+ }
+ else {
+ this.$('span.oe_timeline_invite_wrapper').show();
+ }
},
/** Fetch subtypes, only if current user is follower */
fetch_subtypes: function (user_pid) {
var self = this;
var dialog = false;
+
if (user_pid) {
dialog = true;
- } else {
- this.$('.oe_subtype_list ul').empty();
+ }
+ else {
+ var subtype_list_ul = this.$('.o_timeline_subtype_list ul').empty();
if (! this.message_is_follower) {
- this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', true);
+ this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', true);
return;
}
else {
- this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', false);
+ this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', false);
}
}
var id = this.view.datarecord.id;
- this.ds_model.call('message_get_subscription_data', [[id], user_pid, new data.CompoundContext(this.build_context(), {})])
+ this.ds_model.call('message_get_subscription_data',
+ [[id], user_pid, new data.CompoundContext(this.build_context(), {})])
.then(function (data) {self.display_subtypes(data, id, dialog);});
},
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data, id, dialog) {
var self = this;
- var $list;
- if (dialog) {
- $list = self.$dialog.$el;
- }
- else {
- $list = this.$('.oe_subtype_list ul');
- }
- var records = data[id].message_subtype_data;
- this.records_length = $.map(records, function(value, index) { return index; }).length;
- if (this.records_length > 1) { self.display_followers(); }
- var old_model = '';
- _(records).each(function (record, record_name) {
- if (old_model != record.parent_model){
- if (old_model !== ''){
- var index = $($list).find('.oe_subtype').length;
- $($($list).find('.oe_subtype')[index-1]).addClass('subtype-border');
- }
- old_model = record.parent_model;
+ var $list = this.$('.o_timeline_subtype_list ul');
+
+ if (_.isEmpty(this.data_subtype)) {
+ this.data_subtype = data;
+
+ if (dialog) {
+ $list = this.$dialog.$el;
}
- record.name = record_name;
- record.followed = record.followed || undefined;
- $(qweb.render('mail.followers.subtype', {'record': record, 'dialog': dialog})).appendTo($list);
- });
+
+ var old_model = '';
+ var records = data[id].message_subtype_data;
+ this.records_length = $.map(records, function(value, index) { return index; }).length;
+
+ if (this.records_length > 1) {
+ self.display_followers();
+ }
+
+ _.each(records, function (record, record_name) {
+ if (old_model != record.parent_model) {
+ if (old_model != '') {
+ var index = $($list).find('.oe_subtype').length;
+ $list.find('.oe_subtype')[index-1].addClass('subtype-border');
+ }
+ old_model = record.parent_model;
+ }
+
+ record.name = record_name;
+ record.followed = record.followed || undefined;
+ $(qweb.render('mail_followers_subtype', {'record': record,
+ 'dialog': dialog}))
+ .appendTo($list);
+ });
+ }
},
do_follow: function () {
var context = new data.CompoundContext(this.build_context(), {});
- this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', false);
- this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [session.uid], undefined, context])
+ this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', false);
+ this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id],
+ [session.uid],
+ undefined,
+ context])
.then(this.proxy('read_value'));
- _.each(this.$('.oe_subtype_list input'), function (record) {
+ _.each(this.$('.o_timeline_subtype_list input'), function (record) {
$(record).attr('checked', 'checked');
});
},
do_unfollow: function (user_pid) {
if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
- _(this.$('.oe_msg_subtype_check')).each(function (record) {
+ _(this.$('.o_timeline_msg_subtype_check')).each(function (record) {
$(record).attr('checked',false);
});
- var action_unsubscribe = 'message_unsubscribe_users';
- this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', true);
- var follower_ids = [session.uid];
- if (user_pid) {
- action_unsubscribe = 'message_unsubscribe';
- follower_ids = [user_pid];
- }
+
+ var action_unsubscribe = 'message_unsubscribe_users';
+ this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', true);
+ var follower_ids = [session.uid];
+
+ if (user_pid) {
+ action_unsubscribe = 'message_unsubscribe';
+ follower_ids = [user_pid];
+ }
+
var context = new data.CompoundContext(this.build_context(), {});
- return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
- .then(this.proxy('read_value'));
+ return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id],
+ follower_ids, context])
+ .then(this.proxy('read_value'));
}
return false;
},
do_update_subscription: function (event, user_pid) {
var self = this;
+ this.data_subtype = {};
var action_subscribe = 'message_subscribe_users';
var follower_ids = [session.uid];
var oe_action = this.$('.oe_actions input[type="checkbox"]');
+
if (user_pid) {
action_subscribe = 'message_subscribe';
follower_ids = [user_pid];
oe_action = $('.oe_edit_actions input[type="checkbox"]');
}
- var checklist = [];
+
+ var checklist = new Array();
_(oe_action).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
@@ -372,17 +407,23 @@ var Followers = form_common.AbstractField.extend({
if (!checklist.length) {
if (!this.do_unfollow(user_pid)) {
$(event.target).attr("checked", "checked");
- } else {
- self.$('.oe_subtype_list ul').empty();
}
- } else {
+ else {
+ self.$('.o_timeline_subtype_list ul').empty();
+ }
+ }
+ else {
var context = new data.CompoundContext(this.build_context(), {});
- return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
- .then(this.proxy('read_value'));
+ return this.ds_model.call(action_subscribe, [[this.view.datarecord.id],
+ follower_ids,
+ checklist,
+ context])
+ .then(this.proxy('read_value'));
}
},
});
+
/* Add the widget to registry */
core.form_widget_registry.add('mail_followers', Followers);
-});
+}); \ No newline at end of file
diff --git a/addons/mail/static/src/js/mail_utils.js b/addons/mail/static/src/js/mail_utils.js
index 0634e240340..b30968304aa 100644
--- a/addons/mail/static/src/js/mail_utils.js
+++ b/addons/mail/static/src/js/mail_utils.js
@@ -107,6 +107,22 @@ function breakword(str){
return out;
}
+function bindTooltipTo($el, value, position) {
+ $el.tooltip({
+ 'title': value,
+ 'placement': position,
+ 'html': true,
+ 'trigger': 'manual',
+ 'animation': false,
+ }).on("mouseleave", function () {
+ setTimeout(function () {
+ if (!$(".tooltip:hover").length) {
+ $el.tooltip("hide");
+ }
+ }, 100);
+ });
+}
+
return {
parse_email: parse_email,
get_image: get_image,
@@ -115,6 +131,7 @@ return {
get_text2html: get_text2html,
expand_domain: expand_domain,
breakword: breakword,
+ bindTooltipTo: bindTooltipTo,
};
});
diff --git a/addons/mail/static/src/js/suggestions.js b/addons/mail/static/src/js/suggestions.js
index 5a80c0685de..120fe6a22d8 100644
--- a/addons/mail/static/src/js/suggestions.js
+++ b/addons/mail/static/src/js/suggestions.js
@@ -48,7 +48,7 @@ var Groups = Widget.extend({
if (this.groups.length === 0) {
return this.$el.empty();
}
- return this.$el.empty().html(QWeb.render('mail.suggestions.groups', {'widget': this}));
+ return this.$el.empty().html(QWeb.render('TimelineSuggestionGroups', {'widget': this}));
},
join_group: function (event) {
@@ -72,7 +72,7 @@ var Groups = Widget.extend({
}
});
-mail.WallSidebar.include({
+mail.Sidebar.include({
start: function () {
this._super.apply(this, arguments);
var sug_groups = new Groups(this);
@@ -80,4 +80,4 @@ mail.WallSidebar.include({
},
});
-});
+}); \ No newline at end of file