##############################################################################
import mail_alias
- import mail_message
import mail_followers
+ import mail_message
+ import mail_mail
import mail_thread
import mail_group
+import mail_vote
- import ir_needaction
import res_partner
import res_users
import report
('notification', 'System notification'),
], 'Type',
help="Message type: email for email message, notification for system "\
- "message, comment for other messages such as user replies"),
- 'partner_id': fields.many2one('res.partner', 'Related partner',
- help="Deprecated field. Use partner_ids instead."),
- 'partner_ids': fields.many2many('res.partner',
- 'mail_message_res_partner_rel',
- 'message_id', 'partner_id', 'Destination partners',
- help="When sending emails through the social network composition wizard"\
- "you may choose to send a copy of the mail to partners."),
- 'user_id': fields.many2one('res.users', 'Related User', readonly=1),
+ "message, comment for other messages such as user replies"),
+ 'author_id': fields.many2one('res.partner', 'Author', required=True),
+ 'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id', 'Attachments'),
- 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
- 'state': fields.selection([
- ('outgoing', 'Outgoing'),
- ('sent', 'Sent'),
- ('received', 'Received'),
- ('exception', 'Delivery Failed'),
- ('cancel', 'Cancelled'),
- ], 'Status', readonly=True),
- 'auto_delete': fields.boolean('Auto Delete',
- help="Permanently delete this email after sending it, to save space"),
- 'original': fields.binary('Original', readonly=1,
- help="Original version of the message, as it was sent on the network"),
- 'parent_id': fields.many2one('mail.message', 'Parent Message',
- select=True, ondelete='set null',
- help="Parent message, used for displaying as threads with hierarchy"),
+ 'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
- 'vote_ids': fields.one2many('mail.vote', 'msg_id', 'Votes'),
+ 'model': fields.char('Related Document Model', size=128, select=1),
+ 'res_id': fields.integer('Related Document ID', select=1),
+ 'record_name': fields.function(_get_record_name, type='string',
+ string='Message Record Name',
+ help="Name get of the related document."),
+ 'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
+ 'subject': fields.char('Subject'),
+ 'date': fields.datetime('Date'),
+ 'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1),
+ 'body': fields.html('Contents', help='Automatically sanitized HTML contents'),
+ 'unread': fields.function(_get_unread, fnct_search=_search_unread,
+ type='boolean', string='Unread',
+ help='Functional field to search for unread messages linked to uid'),
++ 'vote_ids': fields.one2many('mail.vote', 'msg_id', 'Votes'),
++
}
-
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+ if self._needaction:
+ return [('unread', '=', True)]
+ return []
+
+ def _get_default_author(self, cr, uid, context=None):
+ return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+
_defaults = {
'type': 'email',
- 'state': 'received',
+ 'date': lambda *a: fields.datetime.now(),
+ 'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
+ 'body': '',
}
+
-
+ #---------------------------------------------------
+ #Mail Vote system (Like or Unlike comments
+ #-----------------------------------------------------
+ def vote_toggle(self, cr, uid, ids, context=None):
+ '''
+ Toggles when Comment is liked or unlike.
+ create vote entries if current user like comment..
+ '''
+ vote_pool = self.pool.get('mail.vote')
+ new_vote_id = False
+ for message in self.browse(cr, uid, ids, context):
+ voters_ids = [x.id for x in message.vote_ids if x.user_id.id == uid]
+ if not voters_ids:
+ new_vote_id = vote_pool.create(cr, uid, {'msg_id': message.id, 'user_id': uid}, context=context)
- else:
++ else:
+ vote_pool.unlink(cr, uid, voters_ids, context=context)
- return True
++ return True
++
+
+ #------------------------------------------------------
+ # Message loading for web interface
+ #------------------------------------------------------
+
+ def _message_dict_get(self, cr, uid, msg, context=None):
+ """ Return a dict representation of the message browse record. """
+ attachment_ids = self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)
+ author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
+ author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0]
+ partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context)
+ return {
+ 'id': msg.id,
+ 'type': msg.type,
+ 'attachment_ids': attachment_ids,
+ 'body': msg.body,
+ 'model': msg.model,
+ 'res_id': msg.res_id,
+ 'record_name': msg.record_name,
+ 'subject': msg.subject,
+ 'date': msg.date,
+ 'author_id': author_id,
+ 'author_user_id': author_user_id,
+ 'partner_ids': partner_ids,
+ 'child_ids': [],
+ }
+
+ def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):
+ """ Given a tree with several roots of following structure :
+ [ {'id': 1, 'child_ids': [
+ {'id': 11, 'child_ids': [...] },],
+ {...} ]
+ Flatten it to have a maximum number of levels, 0 being flat and
+ sort messages in a level according to a key of the messages.
+ Perform the flattening at leafs if above the maximum depth, then get
+ back in the tree.
+ :param context: ``sort_key``: key for sorting (id by default)
+ :param context: ``sort_reverse``: reverser order for sorting (True by default)
+ """
+ def _flatten(msg_dict):
+ """ from {'id': x, 'child_ids': [{child1}, {child2}]}
+ get [{'id': x, 'child_ids': []}, {child1}, {child2}]
+ """
+ child_ids = msg_dict.pop('child_ids', [])
+ msg_dict['child_ids'] = []
+ return [msg_dict] + child_ids
+ # return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
+ context = context or {}
+ # Depth-first flattening
+ for message in messages:
+ if message.get('type') == 'expandable':
+ continue
+ message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context)
+ # Flatten if above maximum depth
+ if current_level < level:
+ return_list = messages
+ else:
+ return_list = []
+ for message in messages:
+ for flat_message in _flatten(message):
+ return_list.append(flat_message)
+ return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
+
+ def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
+ """ If IDs are provided, fetch these records. Otherwise use the domain
+ to fetch the matching records.
+ After having fetched the records provided by IDs, it will fetch the
+ parents to have well-formed threads.
+ :return list: list of trees of messages
+ """
+ limit = limit or self._message_read_limit
+ context = context or {}
+ if not ids:
+ ids = self.search(cr, uid, domain, context=context, limit=limit)
+ messages = self.browse(cr, uid, ids, context=context)
+
+ result = []
+ tree = {} # key: ID, value: record
+ for msg in messages:
+ if len(result) < (limit - 1):
+ record = self._message_dict_get(cr, uid, msg, context=context)
+ if thread_level and msg.parent_id:
+ while msg.parent_id:
+ if msg.parent_id.id in tree:
+ record_parent = tree[msg.parent_id.id]
+ else:
+ record_parent = self._message_dict_get(cr, uid, msg.parent_id, context=context)
+ if msg.parent_id.parent_id:
+ tree[msg.parent_id.id] = record_parent
+ if record['id'] not in [x['id'] for x in record_parent['child_ids']]:
+ record_parent['child_ids'].append(record)
+ record = record_parent
+ msg = msg.parent_id
+ if msg.id not in tree:
+ result.append(record)
+ tree[msg.id] = record
+ else:
+ result.append({
+ 'type': 'expandable',
+ 'domain': [('id', '<=', msg.id)] + domain,
+ 'context': context,
+ 'thread_level': thread_level, # should be improve accodting to level of records
+ 'id': -1,
+ })
+ break
+
+ # Flatten the result
+ if thread_level > 0:
+ result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
+ return result
#------------------------------------------------------
# Email api
"be inserted in kanban views."),
}
- _defaults = {
- 'message_state': True,
- }
-
#------------------------------------------------------
- # Automatic subscription when creating/reading
+ # Automatic subscription when creating
#------------------------------------------------------
-
+
def create(self, cr, uid, vals, context=None):
- """ Override of create to subscribe :
- - the writer
- - followers given by the monitored fields
- """
+ """ Override to subscribe the current user. """
thread_id = super(mail_thread, self).create(cr, uid, vals, context=context)
- followers_command = self.message_get_automatic_followers(cr, uid, thread_id, vals, fetch_missing=False, context=context)
- if followers_command:
- self.write(cr, uid, [thread_id], {'message_follower_ids': followers_command}, context=context)
+ self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
return thread_id
- def write(self, cr, uid, ids, vals, context=None):
- """ Override of write to subscribe :
- - the writer
- - followers given by the monitored fields
- """
- if isinstance(ids, (int, long)):
- ids = [ids]
- for id in ids:
- # copy original vals because we are going to modify it
- specific_vals = dict(vals)
- # we modify followers: do not subscribe the uid
- if specific_vals.get('message_follower_ids'):
- followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, add_uid=False, context=context)
- specific_vals['message_follower_ids'] += followers_command
- else:
- followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, context=context)
- specific_vals['message_follower_ids'] = followers_command
- write_res = super(mail_thread, self).write(cr, uid, ids, specific_vals, context=context)
- return True
-
def unlink(self, cr, uid, ids, context=None):
- """Override unlink, to automatically delete messages
- that are linked with res_model and res_id, not through
- a foreign key with a 'cascade' ondelete attribute.
- Notifications will be deleted with messages
- """
+ """ Override unlink to delete messages and followers. This cannot be
+ cascaded, because link is done through (res_model, res_id). """
msg_obj = self.pool.get('mail.message')
+ fol_obj = self.pool.get('mail.followers')
# delete messages and notifications
- msg_to_del_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
- msg_obj.unlink(cr, uid, msg_to_del_ids, context=context)
+ msg_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
+ msg_obj.unlink(cr, uid, msg_ids, context=context)
+ # delete followers
+ fol_ids = fol_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
+ fol_obj.unlink(cr, uid, fol_ids, context=context)
return super(mail_thread, self).unlink(cr, uid, ids, context=context)
- def message_get_automatic_followers(self, cr, uid, id, record_vals, add_uid=True, fetch_missing=False, context=None):
- """ Return the command for the many2many follower_ids field to manage
- subscribers. Behavior :
- - get the monitored fields (ex: ['user_id', 'responsible_id']); those
- fields should be relationships to res.users (#TODO: res.partner)
- - if this field is in the record_vals: it means it has been modified
- thus add its value to the followers
- - if this fields is not in record_vals, but fetch_missing paramter
- is set to True: fetch the value in the record (use: at creation
- for default values, not present in record_vals)
- - if add_uid: add the current user (for example: writer is subscriber)
- - generate the command and return it
- This method has to be used on 1 id, because otherwise it would imply
- to track which user.id is used for which record.id.
-
- :param record_vals: values given to the create method of the new
- record, or values updated in a write.
- :param monitored_fields: a list of fields that are monitored. Those
- fields must be many2one fields to the res.users model.
- :param fetch_missing: is set to True, the method will read the
- record to find values that are not present in record_vals.
-
- #TODO : UPDATE WHEN MERGING TO PARTNERS
- """
- # get monitored fields
- monitored_fields = self.message_get_monitored_follower_fields(cr, uid, [id], context=context)
- modified_fields = [field for field in monitored_fields if field in record_vals.iterkeys()]
- other_fields = [field for field in monitored_fields if field not in record_vals.iterkeys()] if fetch_missing else []
- # for each monitored field: if in record_vals, it has been modified/added
- follower_ids = []
- for field in modified_fields:
- # do not add 'False'
- if record_vals.get(fields):
- follower_ids.append(record_vals.get(field))
- # for other fields: read in record if fetch_missing (otherwise list is void)
- for field in other_fields:
- record = self.browse(cr, uid, id, context=context)
- value = getattr(record, field)
- if value:
- follower_ids.append(value)
- # add uid if asked and not already present
- if add_uid and uid not in follower_ids:
- follower_ids.append(uid)
- return self.message_subscribe_get_command(cr, uid, follower_ids, context=context)
-
#------------------------------------------------------
# mail.message wrappers and tools
#------------------------------------------------------
return display_done && compose_done;
},
+ //Mail vote Functionality...
+ add_vote_event: function(element){
+ self = this;
+ vote_img = element.find('.oe_mail_msg_vote_like');
+ if (vote_img)
+ vote_img.click(function(){
+ self.subscribe_vote($(this).attr('data-id'));
+ });
+ return
+ },
+
+ find_parent_element: function(name, message_id){
+ parent_element = false;
+ _.each($(name), function(element){
+ if ($(element).attr("data-id") == message_id){
+ parent_element = element;
+ }
+ });
+ return parent_element;
+ },
+
+ render_vote: function(message_id){
+ var self = this;
+ var mail_vote = new session.web.DataSetSearch(self, 'mail.vote', self.session.context, [['msg_id','=',parseInt(message_id)]]);
+ mail_vote.read_slice(['user_id']).then(function(result){
+ vote_count = result.length;
+ is_vote_liked = false;
+ _.each(result, function(vote){
+ if (self.session.uid == vote.user_id[0]){
+ is_vote_liked = true;
+ }
+ });
+ parent_element = self.find_parent_element(".oe_mail_msg_vote", message_id);
+ vote_element = session.web.qweb.render('VoteDisplay', {'msg_id': message_id, 'vote_count': vote_count, 'is_vote_liked': is_vote_liked});
+ $(parent_element).html(vote_element);
+ self.add_vote_event($(parent_element));
+ });
+ },
+
+ subscribe_vote: function(message_id){
+ var self = this;
+ this.mail_message = new session.web.DataSet(this, 'mail.message');
+ return this.mail_message.call('vote_toggle', [[parseInt(message_id)]]).then(function(result){
+ self.render_vote(message_id);
+ });
+ },
-
- /**
- * Override-hack of do_action: automatically reload the chatter.
- * Normally it should be called only when clicking on 'Post/Send'
- * in the composition form. */
- do_action: function(action, on_close) {
- this.init_comments();
- if (this.compose_message_widget) {
- this.compose_message_widget.reinit(); }
- return this._super(action, on_close);
- },
-
- instantiate_composition_form: function(mode, email_mode, formatting, msg_id, context) {
- if (this.compose_message_widget) {
- this.compose_message_widget.destroy();
- }
- this.compose_message_widget = new mail.ComposeMessage(this, {
- 'extended_mode': false, 'uid': this.params.uid, 'res_model': this.params.res_model,
- 'res_id': this.params.res_id, 'mode': mode || 'comment', 'msg_id': msg_id,
- 'email_mode': email_mode || false, 'formatting': formatting || false,
- 'context': context || false } );
- var composition_node = this.$el.find('div.oe_mail_thread_action');
- composition_node.empty();
- var compose_done = this.compose_message_widget.appendTo(composition_node);
- return compose_done;
- },
+
+ /** Customize the display
+ * - show_header_compose: show the composition form in the header */
do_customize_display: function() {
- if (this.display.show_post_comment) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); }
+ this.display_user_avatar();
+ if (this.display.show_header_compose) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); }
},
/**
if (! confirm(_t("Do you really want to delete this message?"))) { return false; }
var msg_id = event.srcElement.dataset.id;
if (! msg_id) return false;
- var call_defer = self.ds_msg.unlink([parseInt(msg_id)]);
- $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).hide();
- if (self.params.thread_level > 0) {
- $(event.srcElement).parents('.oe_mail_thread').eq(0).hide();
- }
- event.preventDefault();
- return call_defer;
+ $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove();
+ return self.ds_msg.unlink([parseInt(msg_id)]);
});
// event: click on 'Hide' in msg side menu
- this.$el.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_hide', 'click', function (event) {
- if (! confirm(_t("Do you really want to hide this thread ?"))) { return false; }
+ this.$el.on('click', 'a.oe_mail_msg_hide', function (event) {
+ event.preventDefault();
+ event.stopPropagation();
var msg_id = event.srcElement.dataset.id;
if (! msg_id) return false;
- var call_defer = self.ds.call('message_remove_pushed_notifications', [[self.params.res_id], [parseInt(msg_id)], true]);
- $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).hide();
- if (self.params.thread_level > 0) {
- $(event.srcElement).parents('.oe_mail_thread').eq(0).hide();
- }
- event.preventDefault();
- return call_defer;
+ $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove();
+ return self.ds_notif.call('set_message_read', [parseInt(msg_id)]);
});
- // event: click on "Reply" in msg side menu (email style)
- this.$el.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_reply_by_email', 'click', function (event) {
+ // event: click on "Reply by email" in msg side menu (email style)
+ this.$el.on('click', 'a.oe_mail_msg_reply_by_email', function (event) {
+ event.preventDefault();
+ event.stopPropagation();
var msg_id = event.srcElement.dataset.msg_id;
- var email_mode = (event.srcElement.dataset.type == 'email');
- var formatting = (event.srcElement.dataset.formatting == 'html');
if (! msg_id) return false;
- self.instantiate_composition_form('reply', email_mode, formatting, msg_id);
- event.preventDefault();
+ self.compose_message_widget.refresh({
+ 'default_composition_mode': 'reply',
+ 'default_parent_id': parseInt(msg_id),
+ 'default_content_subtype': 'html'} );
});
},
-
- destroy: function () {
- this._super.apply(this, arguments);
- },
-
- init_comments: function() {
- var self = this;
- this.params.offset = 0;
- this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}};
- this.$el.find('div.oe_mail_thread_display').empty();
- var domain = this.get_fetch_domain(this.comments_structure);
- return this.fetch_comments(this.params.limit, this.params.offset, domain).then();
+
+ /**
+ * Override-hack of do_action: automatically reload the chatter.
+ * Normally it should be called only when clicking on 'Post/Send'
+ * in the composition form. */
+ do_action: function(action, on_close) {
+ this.message_clean();
+ this.message_fetch();
+ if (this.compose_message_widget) {
+ this.compose_message_widget.refresh({
+ 'default_composition_mode': 'comment',
+ 'default_parent_id': this.options.default_parent_id,
+ 'default_content_subtype': 'plain'} );
+ }
+ return this._super(action, on_close);
},
-
- fetch_comments: function (limit, offset, domain) {
- var self = this;
- var defer = this.ds.call('message_read', [[this.params.res_id], (this.params.thread_level > 0), (this.comments_structure['root_ids']),
- (limit+1) || (this.params.limit+1), offset||this.params.offset, domain||undefined ]).then(function (records) {
- if (records.length <= self.params.limit) self.display.show_more = false;
- // else { self.display.show_more = true; records.pop(); }
- // else { self.display.show_more = true; records.splice(0, 1); }
- else { self.display.show_more = true; }
- self.display_comments(records);
- // TODO: move to customize display
- if (self.display.show_more == true) self.$el.find('div.oe_mail_thread_more:last').show();
- else self.$el.find('div.oe_mail_thread_more:last').hide();
+
+ /** Instantiate the composition form, with every parameters in context
+ or in the widget context. */
+ instantiate_composition_form: function(context) {
+ if (this.compose_message_widget) {
+ this.compose_message_widget.destroy();
+ }
+ this.compose_message_widget = new mail.ComposeMessage(this, {
+ 'context': _.extend(context || {}, this.options.context),
});
-
- return defer;
+ var composition_node = this.$el.find('div.oe_mail_thread_action');
+ composition_node.empty();
+ var compose_done = this.compose_message_widget.appendTo(composition_node);
+ return compose_done;
},
- display_comments_from_parameters: function (records) {
- if (records.length > 0 && records.length < (records[0].child_ids.length+1) ) this.display.show_more = true;
- else this.display.show_more = false;
- var defer = this.display_comments(records);
- // TODO: move to customize display
- if (this.display.show_more == true) $('div.oe_mail_thread_more').eq(-2).show();
- else $('div.oe_mail_thread_more').eq(-2).hide();
- return defer;
+ /** Clean the thread */
+ message_clean: function() {
+ this.$el.find('div.oe_mail_thread_display').empty();
},
-
- display_comments: function (records) {
+
+ /** Fetch messages
+ * @param {Bool} initial_mode: initial mode: try to use message_data or
+ * message_ids, if nothing available perform a message_read; otherwise
+ * directly perform a message_read
+ * @param {Array} additional_domain: added to options.domain
+ * @param {Object} additional_context: added to options.context
+ */
+ message_fetch: function (initial_mode, additional_domain, additional_context) {
var self = this;
- // sort the records
- mail.ChatterUtils.records_struct_add_records(this.comments_structure, records, this.params.parent_id);
- //build attachments download urls and compute time-relative from dates
- for (var k in records) {
- records[k].timerelative = $.timeago(records[k].date);
- if (records[k].attachments) {
- for (var l in records[k].attachments) {
- var url = self.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id='+records[k].attachments[l].id;
- records[k].attachments[l].url = url;
- }
- }
+ // domain and context: options + additional
+ fetch_domain = _.flatten([this.options.domain, additional_domain || []], true)
+ fetch_context = _.extend(this.options.context, additional_context || {})
+ // if message_ids is set: try to use it
+ if (initial_mode && this.options.message_data) {
+ return this.message_display(this.options.message_data);
}
+ return this.ds_message.call('message_read',
+ [(initial_mode && this.options.message_ids) || false, fetch_domain, this.options.thread_level, undefined, fetch_context]
+ ).then(this.proxy('message_display'));
+ },
+
+ /* Display a list of records
+ * A specific case is done for 'expandable' messages that are messages
+ displayed under a 'show more' button form
+ */
+ message_display: function (records) {
+ var self = this;
+ var _expendable = false;
_(records).each(function (record) {
+ //Render Votes.
- self.render_vote(record.id);
- var sub_msgs = [];
- if ((record.parent_id == false || record.parent_id[0] == self.params.parent_id) && self.params.thread_level > 0 ) {
- var sub_list = self.comments_structure['tree_struct'][record.id]['direct_childs'];
- _(records).each(function (record) {
- //if (record.parent_id == false || record.parent_id[0] == self.params.parent_id) return;
- if (_.indexOf(sub_list, record.id) != -1) {
- sub_msgs.push(record);
- }
++ self.render_vote(record.id);
+ if (record.type == 'expandable') {
+ _expendable = true;
+ self.update_fetch_more(true);
+ self.fetch_more_domain = record.domain;
+ self.fetch_more_context = record.context;
+ }
+ else {
+ self.display_record(record);
+ // if (self.options.thread_level >= 0) {
+ self.thread = new mail.Thread(self, {
+ 'context': {
+ 'default_model': record.model,
+ 'default_id': record.res_id,
+ 'default_parent_id': record.id },
+ 'message_data': record.child_ids, 'thread_level': self.options.thread_level-1,
+ 'show_header_compose': false, 'show_reply': self.options.thread_level > 1,
+ 'show_hide': self.display.show_hide, 'show_delete': self.display.show_delete,
});
- self.display_comment(record);
- self.thread = new mail.Thread(self, {'res_model': self.params.res_model, 'res_id': self.params.res_id, 'uid': self.params.uid,
- 'records': sub_msgs, 'thread_level': (self.params.thread_level-1), 'parent_id': record.id,
- 'is_wall': self.params.is_wall});
self.$el.find('li.oe_mail_thread_msg:last').append('<div class="oe_mail_thread_subthread"/>');
self.thread.appendTo(self.$el.find('div.oe_mail_thread_subthread:last'));
- }
- else if (self.params.thread_level == 0) {
- self.display_comment(record);
+ // }
}
});
- mail.ChatterUtils.records_struct_update_after_display(this.comments_structure);
- // update offset for "More" buttons
- if (this.params.thread_level == 0) this.params.offset += records.length;
+ if (! _expendable) {
+ this.update_fetch_more(false);
+ }
},
- /** Displays a record, performs text/link formatting */
- display_comment: function (record) {
- record.body = mail.ChatterUtils.do_text_nl2br($.trim(record.body), true);
- // if (record.type == 'email' && record.state == 'received') {
+ /** 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.attachments[].url: url of each attachment
+ * - record.is_author: is the current user the author of the record */
+ display_record: function (record) {
+ // formatting and additional fields
+ record.date = session.web.format_value(record.date, {type:"datetime"});
+ record.timerelative = $.timeago(record.date);
if (record.type == 'email') {
- record.mini_url = ('/mail/static/src/img/email_icon.png');
+ record.avatar = ('/mail/static/src/img/email_icon.png');
} else {
- record.mini_url = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', record.user_id[0]);
+ record.avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.partner', 'image_small', record.author_id[0]);
}
- // body text manipulation
- if (record.subtype == 'plain') {
- record.body = mail.ChatterUtils.do_text_remove_html_tags(record.body);
+ //TDE: FIX
+ if (record.attachments) {
+ for (var l in record.attachments) {
+ var url = self.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id='+records[k].attachments[l].id;
+ record.attachments[l].url = url;
+ }
}
- record.body = mail.ChatterUtils.do_replace_expressions(record.body);
- // format date according to the user timezone
- record.date = session.web.format_value(record.date, {type:"datetime"});
- // is the user the author ?
- record.is_author = mail.ChatterUtils.is_author(this, record.user_id[0]);
- // render
- var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'params': this.params, 'display': this.display});
- // expand feature
+ record.is_author = mail.ChatterUtils.is_author(this, record.author_user_id[0]);
+ // render, add the expand feature
+ var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'params': this.options, 'display': this.display});
$(rendered).appendTo(this.$el.children('div.oe_mail_thread_display:first'));
this.$el.find('div.oe_mail_msg_record_body').expander({
- slicePoint: this.params.msg_more_limit,
+ slicePoint: this.options.msg_more_limit,
expandText: 'read more',
userCollapseText: '[^]',
detailClass: 'oe_mail_msg_tail',
</div>
</ul>
+ <!-- Vote system (Like or Unlike -->
+ <t t-name="VoteDisplay">
+ <t t-if='vote_count'>
+ <span class="oe_left oe_mail_vote_string">Votes :<t t-esc="vote_count"/></span>
+ </t>
+ <li>
+ <t t-if="!is_vote_liked">
+ <button class="oe_mail_msg_vote_like" t-att-data-id="msg_id" title="Click to Vote.">
+ <span>+1</span>
+ </button>
+ </t>
+ <t t-if="is_vote_liked">
+ <button class="oe_mail_msg_vote_like" t-att-data-id="msg_id" title="Click to Unvote." style="background:#DC5F59; padding:1px">
+ <span>-1</span>
+ </button>
+ </t>
+ </li>
+ </t>
+
<!-- default layout -->
<li t-name="mail.thread.message" class="oe_mail oe_mail_thread_msg">
- <div t-attf-class="oe_mail_msg_#{record.type}">
- <img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.mini_url"/>
+ <div t-attf-class="oe_mail_msg_#{record.type} oe_semantic_html_override">
+ <img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.avatar"/>
<div class="oe_mail_msg_content">
<!-- dropdown menu with message options and actions -->
<span class="oe_dropdown_toggle oe_dropdown_arrow">
<div class="oe_clear"/>
<ul class="oe_mail_msg_footer">
<li t-if="record.subject & params.thread_level > 0"><a t-attf-href="#model=#{params.res_model}&id=#{params.res_id}"><t t-raw="record.record_name"/></a></li>
- <li><a t-attf-href="#model=res.users&id=#{record.user_id[0]}"><t t-raw="record.user_id[1]"/></a></li>
+ <li><a t-attf-href="#model=res.partner&id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
<li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li>
+ <li> <span t-att-data-id="record.id" class="oe_mail_msg_vote"></span> </li>
<li t-if="display['show_reply']"><a href="#" class="oe_mail_msg_reply">Reply</a></li>
<!-- uncomment when merging vote
<li><a href="#">Like</a></li>