From 4a9e3512c58627b42fc82f4f2283e2ba0064637f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 19 Apr 2012 17:26:44 +0200 Subject: [PATCH 001/397] [ADD] mail: added mail.subscription.hide model. This will holds the hiden notification subtypes. bzr revid: tde@openerp.com-20120419152644-u3t5y18s0w7r5ton --- addons/mail/mail_subscription.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/addons/mail/mail_subscription.py b/addons/mail/mail_subscription.py index fc82af879a9..2b6a8dd5342 100644 --- a/addons/mail/mail_subscription.py +++ b/addons/mail/mail_subscription.py @@ -44,6 +44,25 @@ class mail_subscription(osv.osv): ondelete='cascade', required=True, select=1), } +class mail_subscription_hide(osv.osv): + """ + mail_subscription_hide holds the data to store the hiden notifications + on the Wall. Hide choices are related to a subscription, not a model + or a user. + :param: subscription_id: the related subscription + :param: subtype: the message subtype hiden + """ + _name = 'mail.subscription.hide' + _rec_name = 'id' + _order = 'id desc' + _description = 'Hidden subscription' + _columns = { + 'subscription_id': fields.many2one('mail.subscription', string='Subscription', + ondelete='cascade', required=True, select=1), + 'subtype': fields.char('Message subtype', size=64, + required=True, help='Message subtype is a string that depends on the related model.'), + } + class mail_notification(osv.osv): """ mail_notification is a relational table modeling messages pushed to users. From 491e8ad9f6192278ee386d6a721f8c810009d581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 19 Apr 2012 17:38:25 +0200 Subject: [PATCH 002/397] [ADD] mail.subscription.hide: added views and menu entry. bzr revid: tde@openerp.com-20120419153825-xodlfiotdgdmco30 --- addons/mail/mail_subscription_view.xml | 36 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/addons/mail/mail_subscription_view.xml b/addons/mail/mail_subscription_view.xml index 52b72127b78..827c23b25d2 100644 --- a/addons/mail/mail_subscription_view.xml +++ b/addons/mail/mail_subscription_view.xml @@ -20,6 +20,23 @@ + + + + mail.subscription.hide.tree + mail.subscription.hide + tree + 10 + + + + + + + + @@ -45,6 +62,13 @@ tree,form + + Hidden subscriptions + mail.subscription.hide + form + tree,form + + Pushed notif mail.notification @@ -52,11 +76,15 @@ tree,form - - + + + - - + + From ae0dbd21455e8a6ef2d358b8999fba49674fd497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 19 Apr 2012 17:53:39 +0200 Subject: [PATCH 003/397] [ADD] mail.group: added a 'models' field, to link groups to models. Purpose: group will be able to aggregate messages, and users following the group will receive notifications. Example: being able to follow all leads. bzr revid: tde@openerp.com-20120419155339-etlzmi5jo5d8iwgf --- addons/mail/mail_group.py | 2 ++ addons/mail/mail_group_view.xml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index 0a8677d7862..83d0705c4e0 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -119,6 +119,8 @@ class mail_group(osv.osv): ondelete='set null', required=True, select=1, help="Responsible of the group that has all rights on the record."), 'public': fields.boolean('Public', help='This group is visible by non members. Invisible groups can add members through the invite button.'), + 'models': fields.many2many('ir.model', rel='mail_group_models_rel', id1='mail_group_id', id2='model_id', + string='Linked models', help='Linked models'), 'photo_big': fields.binary('Full-size photo', help='Field holding the full-sized PIL-supported and base64 encoded version of the group image. The photo field is used as an interface for this field.'), 'photo': fields.function(_get_photo, fnct_inv=_set_photo, string='Photo', type="binary", store = { diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index ea0dede377d..d911b4a7deb 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -58,6 +58,11 @@ + + + + + From caba5db34bb9d6a4f91127c063293aae3be13780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 19 Apr 2012 18:20:50 +0200 Subject: [PATCH 004/397] [ADD] mail.message: added a message_subtype field, that will contain more precise data about the type of content. Example: create, cancel, ... Purpose: being able to filter (hide) a given content type. bzr revid: tde@openerp.com-20120419162050-6uq495s94i4etnbp --- addons/mail/mail_message.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 5d5d599be0e..9228861b139 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -206,6 +206,12 @@ class mail_message(osv.osv): ('comment', 'Comment'), ('notification', 'System notification'), ], 'Type', help="Message type: e-mail for e-mail message, notification for system message, comment for other messages such as user replies"), + 'message_subtype': fields.selection([ + ('email', 'e-mail'), + ('comment', 'Comment'), + ('create', 'Create'), + ('cancel', 'Cancel'), + ], 'Type', help="Message subtype, such as create or cancel. May be overriden by addons."), 'partner_id': fields.many2one('res.partner', 'Related partner'), 'user_id': fields.many2one('res.users', 'Related user', readonly=1), 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'), @@ -224,6 +230,7 @@ class mail_message(osv.osv): _defaults = { 'type': 'email', + 'message_subtype': 'email', 'state': 'received', } From 7a4ca1338c43b63c4d7976519ffe7a374d922fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 20 Apr 2012 10:42:41 +0200 Subject: [PATCH 005/397] [ADD] Chatter widget: added links to hide a thread or a subtype. Only links, not functional currently, because logic is not implemented. bzr revid: tde@openerp.com-20120420084241-02l4tfifzxjd88go --- addons/mail/static/src/css/mail.css | 31 ++++++++++++++++++++++++++++ addons/mail/static/src/js/mail.js | 32 +++++++++++++++++++++++++++++ addons/mail/static/src/xml/mail.xml | 6 ++++++ 3 files changed, 69 insertions(+) diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 7f0cc6f222c..5bae0d5e73b 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -167,6 +167,7 @@ } .openerp .oe_mail_msg_notification, .openerp .oe_mail_msg_comment, .openerp .oe_mail_msg_email { + position: relative; padding: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; @@ -229,6 +230,36 @@ color: #4E43E7; } +.openerp img.oe_mail_msg_menu_icon { + float: right; + cursor: pointer; +} + +.openerp ul.oe_mail_msg_menu { +/* + display: none; +*/ + display: block; + position: absolute; + top: 1em; + right: 1em; + overflow-x: hidden; + z-index: 900; + background: white; + padding: 4px; + border: 1px solid #AFAFB6; + width: 150px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -o-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); +} + /* ------------------------------ */ /* Styling (should be openerp) */ /* ------------------------------ */ diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index d99674f98a7..160a1dc33fd 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -174,6 +174,34 @@ openerp.mail = function(session) { } return false; }); + // event: click on 'hide notification' in wheel_menu + this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_hide_thread', 'click', function (event) { + console.log('hiding notification'); + if (! confirm(_t("Do you really want to hide this thread ?"))) { return false; } + var msg_id = event.srcElement.dataset.id; + if (! msg_id) return false; + console.log(msg_id); + //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('ul.oe_mail_thread').eq(0).hide(); + } + return false; + }); + // event: click on 'hide this type' in wheel_menu + this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_hide_type', 'click', function (event) { + console.log('hiding type'); + if (! confirm(_t("Do you really want to hide this thread ?"))) { return false; } + var msg_id = event.srcElement.dataset.id; + if (! msg_id) return false; + console.log(msg_id); + //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('ul.oe_mail_thread').eq(0).hide(); + } + return false; + }); // event: click on an internal link this.$element.find('div.oe_mail_thread_display').delegate('a.intlink', 'click', function (event) { // lazy implementation: fetch data and try to redirect @@ -193,6 +221,10 @@ openerp.mail = function(session) { } else self.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(res_id), views: [[false, 'form']]}); }); + // event: click on the wheel menu in messages + this.$element.find('div.oe_mail_thread_display').delegate('img.oe_mail_msg_menu_icon', 'click', function (event) { + self.$element.find('ul.oe_mail_msg_menu').toggle(); + }); }, destroy: function () { diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index f034b04311d..7fe1d39a661 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -58,6 +58,12 @@
  • + menu +
    From 318ef517ab037bafba43c2e5d85301f0f88432c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 20 Apr 2012 11:36:45 +0200 Subject: [PATCH 006/397] [REF] mail.message.common model: renamed subtype to content_subtype. Purpose: make a distinction between content and message model. Type and subtype will be used to distinguish messages, content_subtype to distinguish the content itself. bzr revid: tde@openerp.com-20120420093645-tt78zzrmq2nxomvb --- addons/base_calendar/base_calendar.py | 2 +- addons/crm/wizard/mail_compose_message.py | 2 +- addons/email_template/email_template.py | 4 +- addons/mail/mail_message.py | 78 ++++++++++++---------- addons/mail/mail_message_view.xml | 4 +- addons/mail/mail_thread.py | 22 +++--- addons/mail/wizard/mail_compose_message.py | 12 ++-- 7 files changed, 65 insertions(+), 59 deletions(-) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index 4dffd8bcbf8..09033bffa84 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -515,7 +515,7 @@ property or property parameter."), sub, body, attachments=attach and {'invitation.ics': attach} or None, - subtype='html', + content_subtype='html', reply_to=email_from, context=context ) diff --git a/addons/crm/wizard/mail_compose_message.py b/addons/crm/wizard/mail_compose_message.py index b82c3083f3a..11d340b2f76 100644 --- a/addons/crm/wizard/mail_compose_message.py +++ b/addons/crm/wizard/mail_compose_message.py @@ -54,7 +54,7 @@ class mail_compose_message(osv.osv_memory): 'email_cc' : tools.ustr(data.email_cc or ''), 'model': model, 'res_id': res_id, - 'subtype': 'plain', + 'content_subtype': 'plain', }) if hasattr(data, 'section_id'): result.update({'reply_to' : data.section_id and data.section_id.reply_to or False}) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 86f136ca5b9..31bf9a76441 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -310,7 +310,7 @@ class email_template(osv.osv): 'attachment_ids': False, 'message_id': False, 'state': 'outgoing', - 'subtype': 'plain', + 'content_subtype': 'plain', } if not template_id: return values @@ -326,7 +326,7 @@ class email_template(osv.osv): or False if values['body_html']: - values.update(subtype='html') + values.update(content_subtype='html') if template.user_signature: signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 9228861b139..1595fbae077 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -63,18 +63,21 @@ def to_email(text): if not text: return [] return re.findall(r'([^ ,<@]+@[^> ,]+)', text) -class mail_message_common(osv.osv_memory): +class mail_message_common(osv.TransientModel): """Common abstract class for holding the main attributes of a message object. It could be reused as parent model for any database model or wizard screen that needs to hold a kind of - message""" + message. + All internal logic should be in a database-based model. For example, + a wizard for writing emails should inherit from this class and not + from mail.message.""" def get_body(self, cr, uid, ids, name, arg, context=None): if context is None: context = {} result = dict.fromkeys(ids, '') for message in self.browse(cr, uid, ids, context=context): - if message.subtype == 'html': + if message.content_subtype == 'html': result[message.id] = message.body_html else: result[message.id] = message.body_text @@ -85,7 +88,7 @@ class mail_message_common(osv.osv_memory): - obj: mail.message object - name: 'body' - args: [('body', 'ilike', 'blah')]""" - return ['|', '&', ('subtype', '=', 'html'), ('body_html', args[0][1], args[0][2]), ('body_text', args[0][1], args[0][2])] + return ['|', '&', ('content_subtype', '=', 'html'), ('body_html', args[0][1], args[0][2]), ('body_text', args[0][1], args[0][2])] def get_record_name(self, cr, uid, ids, name, arg, context=None): if context is None: @@ -115,19 +118,20 @@ class mail_message_common(osv.osv_memory): help="Full message headers, e.g. SMTP session headers (usually available on inbound messages only)"), 'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1), 'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1), - 'subtype': fields.char('Message type', size=32, help="Type of message, usually 'html' or 'plain', used to " - "select plaintext or rich text contents accordingly", readonly=1), + 'content_subtype': fields.char('Message content subtype', size=32, + help="Type of message, usually 'html' or 'plain', used to " + "select plain-text or rich-text contents accordingly", readonly=1), 'body_text': fields.text('Text contents', help="Plain-text version of the message"), 'body_html': fields.text('Rich-text contents', help="Rich-text/HTML version of the message"), 'body': fields.function(get_body, fnct_search = search_body, string='Message content', type='text', - help="Content of the message. This content equals the body_text field for plain-test messages, and body_html for rich-text/HTML messages. This allows having one field if we want to access the content matching the message subtype."), + help="Content of the message. This content equals the body_text field for plain-test messages, and body_html for rich-text/HTML messages. This allows having one field if we want to access the content matching the message content_subtype."), 'parent_id': fields.many2one('mail.message', 'Parent message', help="Parent message, used for displaying as threads with hierarchy", select=True, ondelete='set null',), 'child_ids': fields.one2many('mail.message', 'parent_id', 'Child messages'), } _defaults = { - 'subtype': 'plain', + 'content_subtype': 'plain', 'date': (lambda *a: fields.datetime.now()), } @@ -206,12 +210,12 @@ class mail_message(osv.osv): ('comment', 'Comment'), ('notification', 'System notification'), ], 'Type', help="Message type: e-mail for e-mail message, notification for system message, comment for other messages such as user replies"), - 'message_subtype': fields.selection([ + 'message_tmptype': fields.selection([ ('email', 'e-mail'), ('comment', 'Comment'), ('create', 'Create'), ('cancel', 'Cancel'), - ], 'Type', help="Message subtype, such as create or cancel. May be overriden by addons."), + ], 'Type', help="Message temptype, such as create or cancel. May be overriden by addons."), 'partner_id': fields.many2one('res.partner', 'Related partner'), 'user_id': fields.many2one('res.users', 'Related user', readonly=1), 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'), @@ -230,7 +234,7 @@ class mail_message(osv.osv): _defaults = { 'type': 'email', - 'message_subtype': 'email', + 'message_tmptype': 'email', 'state': 'received', } @@ -250,18 +254,18 @@ class mail_message(osv.osv): default.update(message_id=False,original=False,headers=False) return super(mail_message,self).copy(cr, uid, id, default=default, context=context) - def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, email_cc=None, - email_bcc=None, reply_to=False, attachments=None, message_id=False, references=False, - res_id=False, subtype='plain', headers=None, mail_server_id=False, auto_delete=False, - context=None): + def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, + email_cc=None, email_bcc=None, reply_to=False, attachments=None, + message_id=False, references=False, res_id=False, content_subtype='plain', + headers=None, mail_server_id=False, auto_delete=False, context=None): """Schedule sending a new email message, to be sent the next time the mail scheduler runs, or the next time :meth:`process_email_queue` is called explicitly. :param string email_from: sender email address :param list email_to: list of recipient addresses (to be joined with commas) :param string subject: email subject (no pre-encoding/quoting necessary) - :param string body: email body, according to the ``subtype`` (by default, plaintext). - If html subtype is used, the message will be automatically converted + :param string body: email body, according to the ``content_subtype`` + (by default, plaintext). If html content_subtype is used, the message will be automatically converted to plaintext and wrapped in multipart/alternative. :param list email_cc: optional list of string values for CC header (to be joined with commas) :param list email_bcc: optional list of string values for BCC header (to be joined with commas) @@ -272,9 +276,9 @@ class mail_message(osv.osv): be used to generate a tracking id, used to match any response related to the same document) :param string reply_to: optional value of Reply-To header - :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'), - must match the format of the ``body`` parameter. Default is 'plain', - making the content part of the mail "text/plain". + :param string content_subtype: optional mime content_subtype for the text body (usually 'plain' or 'html'), + must match the format of the ``body`` parameter. Default is 'plain', + making the content part of the mail "text/plain". :param dict attachments: map of filename to filecontents, where filecontents is a string containing the bytes of the attachment :param dict headers: optional map of headers to set on the outgoing mail (may override the @@ -299,8 +303,8 @@ class mail_message(osv.osv): 'model': model, 'res_id': res_id, 'type': 'email', - 'body_text': body if subtype != 'html' else False, - 'body_html': body if subtype == 'html' else False, + 'body_text': body if content_subtype != 'html' else False, + 'body_html': body if content_subtype == 'html' else False, 'email_from': email_from, 'email_to': email_to and ','.join(email_to) or '', 'email_cc': email_cc and ','.join(email_cc) or '', @@ -308,7 +312,7 @@ class mail_message(osv.osv): 'reply_to': reply_to, 'message_id': message_id, 'references': references, - 'subtype': subtype, + 'content_subtype': content_subtype, 'headers': headers, # serialize the dict on the fly 'mail_server_id': mail_server_id, 'state': 'outgoing', @@ -388,7 +392,7 @@ class mail_message(osv.osv): 'headers' : { 'X-Mailer': mailer, #.. all X- headers... }, - 'subtype': msg_mime_subtype, + 'content_subtype': msg_mime_subtype, 'body_text': plaintext_body 'body_html': html_body, 'attachments': [('file1', 'bytes'), @@ -464,7 +468,7 @@ class mail_message(osv.osv): msg['in-reply-to'] = msg_txt.get('In-Reply-To') msg['headers'] = {} - msg['subtype'] = 'plain' + msg['content_subtype'] = 'plain' for item in msg_txt.items(): if item[0].startswith('X-'): msg['headers'].update({item[0]: item[1]}) @@ -473,7 +477,7 @@ class mail_message(osv.osv): body = msg_txt.get_payload(decode=True) if 'text/html' in msg.get('content-type', ''): msg['body_html'] = body - msg['subtype'] = 'html' + msg['content_subtype'] = 'html' if body: body = tools.html2plaintext(body) msg['body_text'] = tools.ustr(body, encoding) @@ -482,9 +486,9 @@ class mail_message(osv.osv): if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''): body = "" if 'multipart/alternative' in msg.get('content-type', ''): - msg['subtype'] = 'alternative' + msg['content_subtype'] = 'alternative' else: - msg['subtype'] = 'mixed' + msg['content_subtype'] = 'mixed' for part in msg_txt.walk(): if part.get_content_maintype() == 'multipart': continue @@ -498,7 +502,7 @@ class mail_message(osv.osv): content = tools.ustr(content, encoding) if part.get_content_subtype() == 'html': msg['body_html'] = content - msg['subtype'] = 'html' # html version prevails + msg['content_subtype'] = 'html' # html version prevails body = tools.ustr(tools.html2plaintext(content)) body = body.replace(' ', '') elif part.get_content_subtype() == 'plain': @@ -515,7 +519,7 @@ class mail_message(osv.osv): # for backwards compatibility: msg['body'] = msg['body_text'] - msg['sub_type'] = msg['subtype'] or 'plain' + msg['sub_type'] = msg['content_subtype'] or 'plain' return msg def _postprocess_sent_message(self, cr, uid, message, context=None): @@ -561,15 +565,17 @@ class mail_message(osv.osv): for attach in message.attachment_ids: attachments.append((attach.datas_fname, base64.b64decode(attach.datas))) - body = message.body_html if message.subtype == 'html' else message.body_text + body = message.body_html if message.content_subtype == 'html' else message.body_text body_alternative = None - subtype_alternative = None - if message.subtype == 'html' and message.body_text: + content_subtype_alternative = None + if message.content_subtype == 'html' and message.body_text: # we have a plain text alternative prepared, pass it to # build_message instead of letting it build one body_alternative = message.body_text - subtype_alternative = 'plain' + content_subtype_alternative = 'plain' + # build an RFC2822 email.message.Message object adn send it + # without queuing msg = ir_mail_server.build_email( email_from=message.email_from, email_to=to_email(message.email_to), @@ -582,8 +588,8 @@ class mail_message(osv.osv): attachments=attachments, message_id=message.message_id, references = message.references, object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)), - subtype=message.subtype, - subtype_alternative=subtype_alternative, + subtype=message.content_subtype, + subtype_alternative=content_subtype_alternative, headers=message.headers and ast.literal_eval(message.headers)) res = ir_mail_server.send_email(cr, uid, msg, mail_server_id=message.mail_server_id.id, diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index ecd106e05e5..1a7d511de3b 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -103,7 +103,7 @@ - + @@ -113,7 +113,7 @@ - +