diff --git a/addons/account/account.py b/addons/account/account.py index c9dd1950347..cfba30856a9 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -541,10 +541,18 @@ class account_account(osv.osv): return False return True + def _check_company_account(self, cr, uid, ids, context=None): + for account in self.browse(cr, uid, ids, context=context): + if account.parent_id: + if account.company_id != account.parent_id.company_id: + return False + return True + _constraints = [ (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id']), (_check_type, 'Configuration Error!\nYou cannot define children to an account with internal type different of "View".', ['type']), (_check_account_type, 'Configuration Error!\nYou cannot select an account type with a deferral method different of "Unreconciled" for accounts with internal type "Payable/Receivable".', ['user_type','type']), + (_check_company_account, 'Error!\nYou cannot create an account which has parent account of different company.', ['parent_id']), ] _sql_constraints = [ ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') diff --git a/addons/account/account_analytic_line.py b/addons/account/account_analytic_line.py index f0a420f7496..066f8d1abec 100644 --- a/addons/account/account_analytic_line.py +++ b/addons/account/account_analytic_line.py @@ -107,7 +107,7 @@ class account_analytic_line(osv.osv): if journal_id: journal = analytic_journal_obj.browse(cr, uid, journal_id, context=context) if journal.type == 'sale': - product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context) + product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context=context) if product_price_type_ids: pricetype = product_price_type_obj.browse(cr, uid, product_price_type_ids, context=context)[0] # Take the company currency as the reference one diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 8e79376aa87..4422537007f 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -486,6 +486,19 @@ class account_bank_statement(osv.osv): default['move_line_ids'] = [] return super(account_bank_statement, self).copy(cr, uid, id, default, context=context) + def button_journal_entries(self, cr, uid, ids, context=None): + ctx = (context or {}).copy() + ctx['journal_id'] = self.browse(cr, uid, ids[0], context=context).journal_id.id + return { + 'view_type':'form', + 'view_mode':'tree', + 'res_model':'account.move.line', + 'view_id':False, + 'type':'ir.actions.act_window', + 'domain':[('statement_id','in',ids)], + 'context':ctx, + } + account_bank_statement() class account_bank_statement_line(osv.osv): diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index b01fc974bef..409551585fa 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -402,6 +402,7 @@ class account_invoice(osv.osv): 'default_res_id': ids[0], 'default_use_template': True, 'default_template_id': template_id, + 'default_composition_mode': 'comment', }) return { 'view_type': 'form', @@ -983,13 +984,13 @@ class account_invoice(osv.osv): for i in line: i[2]['period_id'] = period_id + ctx.update(invoice=inv) move_id = move_obj.create(cr, uid, move, context=ctx) new_move_name = move_obj.browse(cr, uid, move_id, context=ctx).name # make the invoice point to that move self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name}, context=ctx) # Pass invoice in context in method post: used if you want to get the same # account move reference when creating the same invoice after a cancelled one: - ctx.update({'invoice':inv}) move_obj.post(cr, uid, [move_id], context=ctx) self._log_event(cr, uid, ids) return True diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 67a55d8f451..302a74c27af 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -208,7 +208,7 @@ class account_move_line(osv.osv): if type(period_id) == str: ids = period_obj.search(cr, uid, [('name', 'ilike', period_id)]) context.update({ - 'period_id': ids[0] + 'period_id': ids and ids[0] or False }) return context @@ -582,7 +582,7 @@ class account_move_line(osv.osv): lines = self.browse(cr, uid, ids, context=context) for l in lines: if l.account_id.type == 'view': - raise osv.except_osv(_('Error!'), _('You cannot create journal items on “View” type account %s %s.') % (l.account_id.code, l.account_id.name)) + return False return True def _check_no_closed(self, cr, uid, ids, context=None): @@ -917,7 +917,7 @@ class account_move_line(osv.osv): if lines and lines[0]: partner_id = lines[0].partner_id and lines[0].partner_id.id or False - if not partner_obj.has_something_to_reconcile(cr, uid, partner_id, context=context): + if partner_id and not partner_obj.has_something_to_reconcile(cr, uid, partner_id, context=context): partner_obj.mark_as_reconciled(cr, uid, [partner_id], context=context) return r_id @@ -975,7 +975,7 @@ class account_move_line(osv.osv): if context is None: context = {} result = super(account_move_line, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu) - if view_type != 'tree': + if (view_type != 'tree') or view_id: #Remove the toolbar from the form view if view_type == 'form': if result.get('toolbar', False): diff --git a/addons/account/account_report.xml b/addons/account/account_report.xml index d258bb0b140..0017409f416 100644 --- a/addons/account/account_report.xml +++ b/addons/account/account_report.xml @@ -41,13 +41,5 @@ groups="group_account_user,group_account_manager" parent="account.menu_finance_generic_reporting" sequence="3"/> - - diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 19128b42d6e..f7d64f43405 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -302,11 +302,11 @@ [('parent_id','=',False)] - + Unrealized Gain or Loss account.account - + @@ -322,7 +322,7 @@ - + Unrealized Gain or Loss account.account form @@ -650,7 +650,7 @@ - + @@ -661,7 +661,7 @@ - + @@ -671,15 +671,24 @@ - - - + + account.bank.statement.journal.items.form.inherit + account.bank.statement + + + + + + - diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index bd0ade31b68..c72af5eadaf 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -20,6 +20,7 @@ ############################################################################## import logging +import pdb import tools from email.header import decode_header @@ -50,8 +51,9 @@ class mail_message(osv.Model): _description = 'Message' _inherit = ['ir.needaction_mixin'] _order = 'id desc' + _rec_name = 'record_name' - _message_read_limit = 10 + _message_read_limit = 30 _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from', 'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids'] _message_record_name_length = 18 @@ -120,21 +122,26 @@ class mail_message(osv.Model): "message, comment for other messages such as user replies"), 'email_from': fields.char('From', help="Email address of the sender. This field is set when no matching partner is found for incoming emails."), - 'author_id': fields.many2one('res.partner', 'Author', + 'author_id': fields.many2one('res.partner', 'Author', select=1, + ondelete='set null', help="Author of the message. If not set, email_from may hold an email address that did not match any partner."), 'partner_ids': fields.many2many('res.partner', string='Recipients'), 'notified_partner_ids': fields.many2many('res.partner', 'mail_notification', - 'message_id', 'partner_id', 'Recipients'), + 'message_id', 'partner_id', 'Notified partners', + help='Partners that have a notification pushing this message in their mailboxes'), 'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel', 'message_id', 'attachment_id', 'Attachments'), - 'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."), + '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'), '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='char', store=True, string='Message Record Name', help="Name get of the related document."), - 'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'), + 'notification_ids': fields.one2many('mail.notification', 'message_id', + string='Notifications', + help='Technical field holding the message notifications. Use notified_partner_ids to access notified partners.'), 'subject': fields.char('Subject'), 'date': fields.datetime('Date'), 'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1), @@ -142,7 +149,8 @@ class mail_message(osv.Model): 'to_read': fields.function(_get_to_read, fnct_search=_search_to_read, type='boolean', string='To read', help='Functional field to search for messages the current user has to read'), - 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype'), + 'subtype_id': fields.many2one('mail.message.subtype', 'Subtype', + ondelete='set null', select=1,), 'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes', help='Users that voted for this message'), @@ -200,67 +208,99 @@ class mail_message(osv.Model): # Message loading for web interface #------------------------------------------------------ - def _message_get_dict(self, cr, uid, message, context=None): - """ Return a dict representation of the message. This representation is - used in the JS client code, to display the messages. + def _message_read_dict_postprocess(self, cr, uid, messages, message_tree, context=None): + """ Post-processing on values given by message_read. This method will + handle partners in batch to avoid doing numerous queries. - :param dict message: read result of a mail.message + :param list messages: list of message, as get_dict result + :param dict message_tree: {[msg.id]: msg browse record} """ - # TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP - is_author = False - if message['author_id']: - is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] - author_id = message['author_id'] - elif message['email_from']: - author_id = (0, message['email_from']) + res_partner_obj = self.pool.get('res.partner') + ir_attachment_obj = self.pool.get('ir.attachment') + pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0] - has_voted = False - if uid in message.get('vote_user_ids'): - has_voted = True + # 1. Aggregate partners (author_id and partner_ids) and attachments + partner_ids = set() + attachment_ids = set() + for key, message in message_tree.iteritems(): + if message.author_id: + partner_ids |= set([message.author_id.id]) + if message.partner_ids: + partner_ids |= set([partner.id for partner in message.partner_ids]) + if message.attachment_ids: + attachment_ids |= set([attachment.id for attachment in message.attachment_ids]) - is_favorite = False - if uid in message.get('favorite_user_ids'): - is_favorite = True + # Filter author_ids uid can see + # partner_ids = self.pool.get('res.partner').search(cr, uid, [('id', 'in', partner_ids)], context=context) + partners = res_partner_obj.name_get(cr, uid, list(partner_ids), context=context) + partner_tree = dict((partner[0], partner) for partner in partners) - is_private = True - if message.get('model') and message.get('res_id'): - is_private = False + # 2. Attachments + attachments = ir_attachment_obj.read(cr, uid, list(attachment_ids), ['id', 'datas_fname'], context=context) + attachments_tree = dict((attachment['id'], {'id': attachment['id'], 'filename': attachment['datas_fname']}) for attachment in attachments) - try: - attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)] - except (orm.except_orm, osv.except_osv): - attachment_ids = [] - - # TDE note: should we send partner_ids ? - # TDE note: shouldn't we separated followers and other partners ? costly to compute maybe , - try: - partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context) - except (orm.except_orm, osv.except_osv): + # 3. Update message dictionaries + for message_dict in messages: + message_id = message_dict.get('id') + message = message_tree[message_id] + if message.author_id: + author = partner_tree[message.author_id.id] + else: + author = (0, message.email_from) partner_ids = [] + for partner in message.partner_ids: + if partner.id in partner_tree: + partner_ids.append(partner_tree[partner.id]) + attachment_ids = [] + for attachment in message.attachment_ids: + if attachment.id in attachments_tree: + attachment_ids.append(attachments_tree[attachment.id]) + message_dict.update({ + 'is_author': pid == author[0], + 'author_id': author, + 'partner_ids': partner_ids, + 'attachment_ids': attachment_ids, + }) + return True - return { - 'id': message['id'], - 'type': message['type'], - 'attachment_ids': attachment_ids, - 'body': message['body'], - 'model': message['model'], - 'res_id': message['res_id'], - 'record_name': message['record_name'], - 'subject': message['subject'], - 'date': message['date'], - 'author_id': author_id, - 'is_author': is_author, - 'partner_ids': partner_ids, - 'parent_id': False, - 'vote_nb': len(message['vote_user_ids']), - 'has_voted': has_voted, - 'is_private': is_private, - 'is_favorite': is_favorite, - 'to_read': message['to_read'], - } + def _message_read_dict(self, cr, uid, message, parent_id=False, context=None): + """ Return a dict representation of the message. This representation is + used in the JS client code, to display the messages. Partners and + attachments related stuff will be done in post-processing in batch. - def _message_read_add_expandables(self, cr, uid, message_list, read_messages, - thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None): + :param dict message: mail.message browse record + """ + # private message: no model, no res_id + is_private = False + if not message.model or not message.res_id: + is_private = True + # votes and favorites: res.users ids, no prefetching should be done + vote_nb = len(message.vote_user_ids) + has_voted = uid in [user.id for user in message.vote_user_ids] + is_favorite = uid in [user.id for user in message.favorite_user_ids] + + return {'id': message.id, + 'type': message.type, + 'body': message.body, + 'model': message.model, + 'res_id': message.res_id, + 'record_name': message.record_name, + 'subject': message.subject, + 'date': message.date, + 'to_read': message.to_read, + 'parent_id': parent_id, + 'is_private': is_private, + 'author_id': False, + 'is_author': False, + 'partner_ids': [], + 'vote_nb': vote_nb, + 'has_voted': has_voted, + 'is_favorite': is_favorite, + 'attachment_ids': [], + } + + def _message_read_add_expandables(self, cr, uid, messages, message_tree, parent_tree, + message_unload_ids=[], thread_level=0, domain=[], parent_id=False, context=None): """ Create expandables for message_read, to load new messages. 1. get the expandable for new threads if display is flat (thread_level == 0): @@ -275,96 +315,82 @@ class mail_message(osv.Model): for each hole in the child list based on message displayed, create an expandable - :param list message_list:list of message structure for the Chatter + :param list messages: list of message structure for the Chatter widget to which expandables are added - :param dict read_messages: dict [id]: read result of the messages to - easily have access to their values, given their ID + :param dict message_tree: dict [id]: browse record of this message + :param dict parent_tree: dict [parent_id]: [child_ids] + :param list message_unload_ids: list of message_ids we do not want + to load :return bool: True """ - def _get_expandable(domain, message_nb, parent_id, id, model): + def _get_expandable(domain, message_nb, parent_id, max_limit): return { 'domain': domain, 'nb_messages': message_nb, 'type': 'expandable', 'parent_id': parent_id, - 'id': id, - # TDE note: why do we need model sometimes, and sometimes not ??? - 'model': model, + 'max_limit': max_limit, } - # all_not_loaded_ids = [] - id_list = sorted(read_messages.keys()) - if not id_list: - return message_list + if not messages: + return True + message_ids = sorted(message_tree.keys()) # 1. get the expandable for new threads if thread_level == 0: - exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))] + exp_domain = domain + [('id', '<', min(message_unload_ids + message_ids))] else: - exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)] + exp_domain = domain + ['!', ('id', 'child_of', message_unload_ids + parent_tree.keys())] ids = self.search(cr, uid, exp_domain, context=context, limit=1) if ids: - message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None)) + # inside a thread: prepend + if parent_id: + messages.insert(0, _get_expandable(exp_domain, -1, parent_id, True)) + # new threads: append + else: + messages.append(_get_expandable(exp_domain, -1, parent_id, True)) # 2. get the expandables for new messages inside threads if display is not flat if thread_level == 0: return True - for message_id in id_list: - message = read_messages[message_id] + for message_id in message_ids: + message = message_tree[message_id] - # message is not a thread header (has a parent_id) - # TDE note: parent_id is false is there is a parent we can not see -> ok - if message.get('parent_id'): + # generate only for thread header messages (TDE note: parent_id may be False is uid cannot see parent_id, seems ok) + if message.parent_id: continue - # TDE note: check search is correctly implemented in mail.message - not_loaded_ids = self.search(cr, uid, [ - ('id', 'child_of', message['id']), - ('id', 'not in', message_loaded_ids), - ], context=context, limit=self._message_read_more_limit) - if not not_loaded_ids: + # check there are message for expandable + child_ids = set([child.id for child in message.child_ids]) - set(message_unload_ids) + child_ids = sorted(list(child_ids), reverse=True) + if not child_ids: continue - # all_not_loaded_ids += not_loaded_ids - # group childs not read - id_min, id_max, nb = max(not_loaded_ids), 0, 0 - for not_loaded_id in not_loaded_ids: - if not read_messages.get(not_loaded_id): + # make groups of unread messages + id_min, id_max, nb = max(child_ids), 0, 0 + for child_id in child_ids: + if not child_id in message_ids: nb += 1 - if id_min > not_loaded_id: - id_min = not_loaded_id - if id_max < not_loaded_id: - id_max = not_loaded_id + if id_min > child_id: + id_min = child_id + if id_max < child_id: + id_max = child_id elif nb > 0: exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)] - message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model'))) - id_min, id_max, nb = max(not_loaded_ids), 0, 0 + messages.append(_get_expandable(exp_domain, nb, message_id, False)) + id_min, id_max, nb = max(child_ids), 0, 0 else: - id_min, id_max, nb = max(not_loaded_ids), 0, 0 + id_min, id_max, nb = max(child_ids), 0, 0 if nb > 0: exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)] - message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model'))) - - # message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids)) + idx = [msg.get('id') for msg in messages].index(message_id) + 1 + # messages.append(_get_expandable(exp_domain, nb, message_id, id_min)) + messages.insert(idx, _get_expandable(exp_domain, nb, message_id, False)) return True - def _get_parent(self, cr, uid, message, context=None): - """ Tools method that tries to get the parent of a mail.message. If - no parent, or if uid has no access right on the parent, False - is returned. - - :param dict message: read result of a mail.message - """ - if not message['parent_id']: - return False - parent_id = message['parent_id'][0] - try: - return self.read(cr, uid, parent_id, self._message_read_fields, context=context) - except (orm.except_orm, osv.except_osv): - return False - - def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None): + def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, + thread_level=0, context=None, parent_id=False, limit=None): """ Read messages from mail.message, and get back a list of structured messages to be displayed as discussion threads. If IDs is set, fetch these records. Otherwise use the domain to fetch messages. @@ -388,46 +414,56 @@ class mail_message(osv.Model): ancestors and expandables :return list: list of message structure for the Chatter widget """ - # print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level domain = domain if domain is not None else [] message_unload_ids = message_unload_ids if message_unload_ids is not None else [] if message_unload_ids: domain += [('id', 'not in', message_unload_ids)] limit = limit or self._message_read_limit - read_messages = {} + message_tree = {} message_list = [] + parent_tree = {} # no specific IDS given: fetch messages according to the domain, add their parents if uid has access to if ids is None: ids = self.search(cr, uid, domain, context=context, limit=limit) - for message in self.read(cr, uid, ids, self._message_read_fields, context=context): - message_id = message['id'] - # if not in tree and not in message_loaded list - if not message_id in read_messages and not message_id in message_unload_ids: - read_messages[message_id] = message - message_list.append(self._message_get_dict(cr, uid, message, context=context)) + # fetch parent if threaded, sort messages + for message in self.browse(cr, uid, ids, context=context): + message_id = message.id + if message_id in message_tree: + continue + message_tree[message_id] = message - # get the older ancestor the user can read, update its ancestor field - if not thread_level: - message_list[-1]['parent_id'] = parent_id - continue - parent = self._get_parent(cr, uid, message, context=context) - while parent and parent.get('id') != parent_id: - message_list[-1]['parent_id'] = parent.get('id') - message = parent - parent = self._get_parent(cr, uid, message, context=context) - # if in thread: add its ancestor to the list of messages - if not message['id'] in read_messages and not message['id'] in message_unload_ids: - read_messages[message['id']] = message - message_list.append(self._message_get_dict(cr, uid, message, context=context)) + # find parent_id + if thread_level == 0: + tree_parent_id = parent_id + else: + tree_parent_id = message_id + parent = message + while parent.parent_id and parent.parent_id.id != parent_id: + parent = parent.parent_id + tree_parent_id = parent.id + if not parent.id in message_tree: + message_tree[parent.id] = parent + # newest messages first + parent_tree.setdefault(tree_parent_id, []) + if tree_parent_id != message_id: + parent_tree[tree_parent_id].append(self._message_read_dict(cr, uid, message_tree[message_id], parent_id=tree_parent_id, context=context)) + + if thread_level: + for key, message_id_list in parent_tree.iteritems(): + message_id_list.sort(key=lambda item: item['id']) + message_id_list.insert(0, self._message_read_dict(cr, uid, message_tree[key], context=context)) + + parent_list = parent_tree.items() + parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item[1]]) if item[1] else item[0], reverse=True) + message_list = [message for (key, msg_list) in parent_list for message in msg_list] # get the child expandable messages for the tree - message_list = sorted(message_list, key=lambda k: k['id']) - self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level, - message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit) - + self._message_read_dict_postprocess(cr, uid, message_list, message_tree, context=context) + self._message_read_add_expandables(cr, uid, message_list, message_tree, parent_tree, + thread_level=thread_level, message_unload_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context) return message_list # TDE Note: do we need this ? @@ -461,7 +497,6 @@ class mail_message(osv.Model): - otherwise: remove the id """ # Rules do not apply to administrator - # print '_search', uid, args if uid == SUPERUSER_ID: return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count, access_rights_uid=access_rights_uid) @@ -592,11 +627,14 @@ class mail_message(osv.Model): other_ids = other_ids - set(document_related_ids) if not other_ids: return + raise orm.except_orm(_('Access Denied'), _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ (self._description, operation)) def create(self, cr, uid, values, context=None): + if 'default_res_model' in context: + values['model']=context.get('default_res_model') if not values.get('message_id') and values.get('res_id') and values.get('model'): values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) newid = super(mail_message, self).create(cr, uid, values, context) diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 93b35797fd5..7d2e9483773 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -59,21 +59,26 @@ + + - + + @@ -84,7 +89,6 @@ form tree,form - {'search_default_to_read_message':True} diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index f3203336615..ee00b88dbd9 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -74,17 +74,17 @@ class mail_thread(osv.AbstractModel): - message_unread: has uid unread message for the document - message_summary: html snippet summarizing the Chatter for kanban views """ res = dict((id, dict(message_unread=False, message_summary='')) for id in ids) + user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0] - # search for unread messages, by reading directly mail.notification, as SUPERUSER - notif_obj = self.pool.get('mail.notification') - notif_ids = notif_obj.search(cr, SUPERUSER_ID, [ - ('partner_id.user_ids', 'in', [uid]), - ('message_id.res_id', 'in', ids), - ('message_id.model', '=', self._name), - ('read', '=', False) - ], context=context) - for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context): - res[notif.message_id.res_id]['message_unread'] = True + # search for unread messages, directly in SQL to improve performances + cr.execute(""" SELECT m.res_id FROM mail_message m + RIGHT JOIN mail_notification n + ON (n.message_id = m.id AND n.partner_id = %s AND n.read = False) + WHERE m.model = %s AND m.res_id in %s""", + (user_pid, self._name, tuple(ids),)) + msg_ids = [result[0] for result in cr.fetchall()] + for msg_id in msg_ids: + res[msg_id]['message_unread'] = True for thread in self.browse(cr, uid, ids, context=context): cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or '' diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml index 07b24725b05..cdeade04d84 100644 --- a/addons/mail/mail_thread_view.xml +++ b/addons/mail/mail_thread_view.xml @@ -4,48 +4,126 @@ Inbox mail.wall - + mail.message + { + 'default_model': 'res.users', + 'default_res_id': uid + } + + +

+ Good Job! Your inbox is empty. +

+ Your inbox contains private messages or emails sent to you + as well as information related to documents or people you + follow. +

+
To: me mail.wall - + mail.message + { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_message_unread': True + } + + +

+ No private message. +

+ This list contains messages sent to you. +

+
- Favorites + Todo mail.wall - + mail.message + { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_message_unread': True + } + + +

+ No todo! +

+ When you process messages in your inbox, you can mark some + as todo. From this menu, you can process all your todo. +

+
Archives mail.wall - + { + 'default_model': 'res.users', + 'default_res_id': uid, + 'search_default_message_read': True + } + + +

+ No message found. +

+
Sent mail.wall - + { + 'default_model': 'res.users', + 'default_res_id': uid + } + + +

+ No message sent yet. +

+ Click on the top-right icon to compose a message. This + message will be sent by email if it's an internal contact. +

+
- - + @@ -61,7 +139,7 @@ - Favorites + Todo diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index af3cc3ed359..18d19a848cc 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -1,7 +1,36 @@ +/* ------------ TOPBAR MAIL BUTTON --------------- */ + +/* FIXME this css is not very pretty because it uses a + * 'button' element wich comes with a lot of inappropriate + * styling. Entypo is also a headache to center properly + * */ + +.openerp .oe_topbar_item.oe_topbar_compose_full_email{ + padding: 0px; + width: 32px; + height: 32px; +} +.openerp .oe_topbar_item.oe_topbar_compose_full_email button{ + position: relative; + top: -3px; /* centering entypo ... urgh */ + box-sizing: border-box; + border: none; + box-shadow: none; + color: white; + background: none; + text-shadow: 0px 1px 2px black; + width: 32px; + height: 32px; + padding: 0px; + margin: 0px + border-radius: 0px; +} /* ------------ MAIL WIDGET --------------- */ .openerp .oe_mail, .openerp .oe_mail *{ - box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } .openerp .oe_mail { display: block; @@ -42,9 +71,10 @@ } .openerp .oe_mail .oe_msg .oe_msg_footer{ padding-left: 4px; + padding-top: 3px; overflow: hidden; - opacity:0.8; - -webkit-transition: opacity 0.2s linear; + margin-bottom: 4px; + font-size: 11px; } .openerp .oe_mail .oe_msg .oe_msg_content{ display: block; @@ -80,18 +110,24 @@ .openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_content{ padding-top:2px; } +.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_footer{ + margin-bottom: 0px; +} + /* b) Votes (likes) */ + .openerp .oe_mail .oe_mail_vote_count{ display: inline; position: relative; - background: #7C7BAD; - color: white; + background: white; + box-shadow: 0px 0px 0px 1px rgba(124, 123, 173, 0.36) inset; + color: #7c7bad; text-shadow: none; border-radius: 3px; margin: 0px; padding-left: 3px; - padding-right: 18px; - margin-right: 3px; + padding-right: 15px; + margin-right: 5px; } .openerp .oe_mail .oe_mail_vote_count .oe_e{ position: absolute; @@ -102,12 +138,6 @@ /* c) Message action icons */ -.openerp .oe_mail .oe_msg.oe_msg_unread .oe_unread{ - display:none; -} -.openerp .oe_mail .oe_msg.oe_msg_read .oe_read{ - display:none; -} .openerp .oe_mail .oe_msg .oe_msg_icons{ float: right; margin-top: 4px; @@ -115,6 +145,9 @@ margin-left: 8px; height: 24px; -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .openerp .oe_mail .oe_msg .oe_msg_icons span{ float:right; @@ -128,10 +161,16 @@ color: #FFF; text-shadow: 0px 1px #AAA,0px -1px #AAA, -1px 0px #AAA, 1px 0px #AAA, 0px 3px 3px rgba(0,0,0,0.1); -webkit-transition: all 0.2s linear; + -moz-transition: all 0.2s linear; + -o-transition: all 0.2s linear; + transition: all 0.2s linear; } .openerp .oe_mail .oe_msg:hover .oe_msg_icons a{ opacity: 1; -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + -o-transition: all 0.1s linear; + transition: all 0.1s linear; } .openerp .oe_mail .oe_msg .oe_msg_icons .oe_star:hover a{ color: #FFF6C0; @@ -155,7 +194,7 @@ } .openerp .oe_mail .oe_msg .oe_msg_content textarea{ width: 100%; - height: 32px; + height: 64px; margin: 0px; padding: 0px; resize: vertical; @@ -171,6 +210,150 @@ width: 100%; } +/* --------------------- ATTACHMENTS --------------------- */ + +.openerp .oe_mail .oe_msg_attachment_list{ + display: none; + margin-top: 12px; + margin-bottom: 12px; +} +.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list{ + display: block; +} +.openerp .oe_mail .oe_attachment{ + display: inline-block; + width: 100px; + margin: 2px; + min-height: 80px; + position: relative; + border-radius: 3px; + text-align: center; + vertical-align: top; +} +.openerp .oe_mail .oe_attachment .oe_name{ + display: inline-block; + max-width: 100%; + padding: 1px 3px; + margin-top: 50px; + margin-bottom: 5px; + background: rgba(124, 123, 173, 0.13); + overflow: hidden; + color: #4c4c4c; + text-shadow: none; + border-radius: 3px; +} + +.openerp .oe_mail .oe_attachment.oe_preview{ + background: url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVQYV2MsLS39z4AGLCws0IUYGIeCwrVr12J45sSJE5ieGQIKAbuZKf/EMCs7AAAAAElFTkSuQmCC ); +} +.openerp .oe_mail .oe_attachment .oe_progress_bar{ + display: none; + position: absolute; + top: 18px; + left: 16px; + right: 16px; + height: 17px; + line-height: 13px; + padding: 0px; + background: #4BBD00; + color: white; + text-align: center; + border-radius: 3px; + border: solid 1px rgba(0,0,0,0.2); + box-shadow: 0px 3px 10px rgba(0, 0, 0, 0.34); + -webkit-animation: oe_mail_attach_loading_anim 0.75s infinite linear; + -moz-animation: oe_mail_attach_loading_anim 0.75s infinite linear; + -o-animation: oe_mail_attach_loading_anim 0.75s infinite linear; + animation: oe_mail_attach_loading_anim 0.75s infinite linear; +} +.openerp .oe_mail .oe_attachment.oe_uploading .oe_progress_bar{ + display: block; +} +@-webkit-keyframes oe_mail_attach_loading_anim{ + 0% { background: #4BBD00 } + 50% { background: #009123 } + 100% { background: #4BBD00 } +} +@-moz-keyframes oe_mail_attach_loading_anim{ + 0% { background: #4BBD00 } + 50% { background: #009123 } + 100% { background: #4BBD00 } +} +@-o-keyframes oe_mail_attach_loading_anim{ + 0% { background: #4BBD00 } + 50% { background: #009123 } + 100% { background: #4BBD00 } +} +@keyframes oe_mail_attach_loading_anim{ + 0% { background: #4BBD00 } + 50% { background: #009123 } + 100% { background: #4BBD00 } +} +.openerp .oe_mail .oe_attachment.oe_preview .oe_name{ + position: absolute; + bottom: 0px; + margin: 0px; + left: 0px; + right: 0px; + max-height: 64px; + background: rgba(0,0,0,0.8); + color: white; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} +.openerp .oe_mail .oe_attachment.oe_preview:hover .oe_name{ + opacity: 1; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} +.openerp .oe_mail .oe_attachment img{ + position: absolute; + width: 48px; + height: 48px; + top: 0px; + left: 50%; + margin-left: -24px; +} +.openerp .oe_mail .oe_attachment.oe_preview img{ + display: block; + position: relative; + margin:0px; + width: 100px; + height: 100px; + border-radius: 3px; + margin-left: -50px; +} +.openerp .oe_mail .oe_attachment .oe_delete{ + display: none; +} +.openerp .oe_mail .oe_msg_composer .oe_attachment .oe_delete{ + display: block; + position: absolute; + top: -7px; + right: 0px; + color: black; + text-shadow: 1px 0px white, -1px 0px white, 0px 1px white, 0px -1px white; + cursor: pointer; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} +.openerp .oe_mail .oe_msg_composer .oe_attachment:hover .oe_delete{ + opacity: 1; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} /* ---------------- MESSAGE QUICK COMPOSER --------------- */ .openerp .oe_mail .oe_msg_composer .oe_msg_footer{ @@ -178,37 +361,6 @@ padding-top: 2px; padding-bottom:6px; } -.openerp .oe_mail .oe_msg_attachments.oe_hidden, -.openerp .oe_mail .oe_msg_images.oe_hidden{ - margin:0px; - border: none; - display: none; -} -.openerp .oe_mail .oe_msg_attachments{ - margin-bottom: 4px; - margin-right: 0px; - font-size: 12px; - border-radius: 2px; - border: solid 1px rgba(124,123,173,0.14); -} -.openerp .oe_mail .oe_msg_attachments .oe_attachment{ - padding: 2px; - padding-left: 4px; - padding-right: 4px; -} -.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e{ - font-size: 23px; - margin-top: -5px; -} -.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e:hover{ - text-decoration: none; -} -.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(odd){ - background:white; -} -.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(even){ - background: #F4F5FA; -} .openerp .oe_mail .oe_msg_images { display: block; } @@ -269,6 +421,7 @@ .openerp .oe_mail .oe_msg_content.oe_msg_more_message{ text-align: right; + cursor: pointer; } .openerp .oe_mail .oe_msg_content.oe_msg_more_message .oe_separator{ height: 0; @@ -280,7 +433,7 @@ } .openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more { background: white; - margin-right: 280px; + margin-right: 210px; padding-left: 8px; padding-right: 8px; text-decoration: none; @@ -298,6 +451,7 @@ padding-top: 5px; width: 160px; float: right; + margin-right: 16px; } /* a) THE FOLLOW BUTTON */ @@ -308,9 +462,22 @@ width:100%; } .openerp .oe_followers button.oe_follower.oe_following{ + color: white; background-color: #3465A4; background-image: -webkit-linear-gradient(top, #729FCF, #3465A4); + background-image: -moz-linear-gradient(top, #729FCF, #3465A4); + background-image: -ms-linear-gradient(top, #729FCF, #3465A4); + background-image: -o-linear-gradient(top, #729FCF, #3465A4); + background-image: linear-gradient(to bottom, #729FCF, #3465A4); +} +.openerp .oe_followers button.oe_follower.oe_following:hover{ color: white; + background-color: #A21A1A; + background-image: -webkit-linear-gradient(top, #DF3F3F, #A21A1A); + background-image: -moz-linear-gradient(top, #DF3F3F, #A21A1A); + background-image: -ms-linear-gradient(top, #DF3F3F, #A21A1A); + background-image: -o-linear-gradient(top, #DF3F3F, #A21A1A); + background-image: linear-gradient(to bottom, #DF3F3F, #A21A1A); } .openerp .oe_followers button.oe_follower .oe_follow, @@ -371,12 +538,17 @@ .openerp .oe_record_thread{ display: block; - margin-right: 180px; + margin-left: 16px; + margin-right: 212px; } /* ----------- INBOX INTEGRATION ----------- */ .openerp .oe_mail_wall .oe_mail{ margin: 16px; - width: 720px; + width: 600px; +} + +.openerp .oe_mail .oe_view_nocontent > p { + padding-left: 15px; } diff --git a/addons/mail/static/src/css/mail_group.css b/addons/mail/static/src/css/mail_group.css index b700cb8c80f..3146247c1bc 100644 --- a/addons/mail/static/src/css/mail_group.css +++ b/addons/mail/static/src/css/mail_group.css @@ -68,30 +68,31 @@ min-height: 120px; } -.oe_group_details a, .oe_group_details a:hover { - font-weight: bold; - color: #4c4c4c; -} - .oe_group_details h4 { margin: 0; font-size: 13px; } -.oe_group_details h4 a { - color: #4c4c4c; -} - -.oe_group_details h4 a:hover { - text-decoration: underline; -} - .oe_group_details ul { margin: 3px 0 5px; padding: 0; list-style: none; } -.oe_group_details li { +.openerp .oe_group_details li { margin: 2px 0; } + +.openerp .oe_group_button { + padding-top: 7px; +} + +.openerp .oe_group_button .oe_group_join { + color: white; + background-color: #3465A4; + background-image: -webkit-linear-gradient(top, #729FCF, #3465A4); + background-image: -moz-linear-gradient(top, #729FCF, #3465A4); + background-image: -ms-linear-gradient(top, #729FCF, #3465A4); + background-image: -o-linear-gradient(top, #729FCF, #3465A4); + background-image: linear-gradient(to bottom, #729FCF, #3465A4); +} diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 1eb3c29e556..e214168cb25 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -26,7 +26,8 @@ openerp.mail = function (session) { 'default_use_template', 'default_partner_ids', 'default_model', 'default_res_id', 'default_content_subtype', , 'default_subject', 'default_body', 'active_id', 'lang', 'bin_raw', 'tz', - 'active_model', 'edi_web_url_view', 'active_ids'] + 'active_model', 'edi_web_url_view', 'active_ids', + 'default_attachment_ids'] for (var key in action.context) { if (_.indexOf(context_keys, key) == -1) { action.context[key] = null; @@ -54,8 +55,8 @@ openerp.mail = function (session) { mail.ChatterUtils = { /* Get an image in /web/binary/image?... */ - get_image: function (session, model, field, id) { - return session.prefix + '/web/binary/image?session_id=' + session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); + get_image: function (session, model, field, id, resize) { + return session.prefix + '/web/binary/image?session_id=' + session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || '') + '&resize=' + (resize ? encodeURIComponent(resize) : ''); }, /* Get the url of an attachment {'id': id} */ @@ -111,10 +112,265 @@ openerp.mail = function (session) { } return domain; - } + }, + + // inserts zero width space between each letter of a string so that + // the word will correctly wrap in html boxes smaller than the text + breakword: function(str){ + var out = ''; + if (!str) { + return str; + } + for(var i = 0, len = str.length; i < len; i++){ + out += _.str.escapeHTML(str[i]) + '​'; + } + return out; + }, + + // returns the file type of a file based on its extension + // As it only looks at the extension it is quite approximative. + filetype: function(url){ + url = url.filename || url; + var tokens = url.split('.'); + if(tokens.length <= 1){ + return 'unknown'; + } + var extension = tokens[tokens.length -1]; + if(extension.length === 0){ + return 'unknown'; + }else{ + extension = extension.toLowerCase(); + } + var filetypes = { + 'webimage': ['png','jpg','jpeg','jpe','gif'], // those have browser preview + 'image': ['tif','tiff','tga', + 'bmp','xcf','psd','ppm','pbm','pgm','pnm','mng', + 'xbm','ico','icon','exr','webp','psp','pgf','xcf', + 'jp2','jpx','dng','djvu','dds'], + 'vector': ['ai','svg','eps','vml','cdr','xar','cgm','odg','sxd'], + 'print': ['dvi','pdf','ps'], + 'document': ['doc','docx','odm','odt'], + 'presentation': ['key','keynote','odp','pps','ppt'], + 'font': ['otf','ttf','woff','eot'], + 'archive': ['zip','7z','ace','apk','bzip2','cab','deb','dmg','gzip','jar', + 'rar','tar','gz','pak','pk3','pk4','lzip','lz','rpm'], + 'certificate': ['cer','key','pfx','p12','pem','crl','der','crt','csr'], + 'audio': ['aiff','wav','mp3','ogg','flac','wma','mp2','aac', + 'm4a','ra','mid','midi'], + 'video': ['asf','avi','flv','mkv','m4v','mpeg','mpg','mpe','wmv','mp4','ogm'], + 'text': ['txt','rtf','ass'], + 'html': ['html','xhtml','xml','htm','css'], + 'disk': ['iso','nrg','img','ccd','sub','cdi','cue','mds','mdx'], + 'script': ['py','js','c','cc','cpp','cs','h','java','bat','sh', + 'd','rb','pl','as','cmd','coffee','m','r','vbs','lisp'], + 'spreadsheet': ['123','csv','ods','numbers','sxc','xls','vc','xlsx'], + 'binary': ['exe','com','bin','app'], + }; + for(filetype in filetypes){ + var ext_list = filetypes[filetype]; + for(var i = 0, len = ext_list.length; i < len; i++){ + if(extension === ext_list[i]){ + return filetype; + } + } + } + return 'unknown'; + }, + }; + /** + * ------------------------------------------------------------ + * MessageCommon + * ------------------------------------------------------------ + * + * Common base for expandables, chatter messages and composer. It manages + * the various variables common to those models. + */ + + mail.MessageCommon = session.web.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.is_author = datasets.is_author || 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 || false, + 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.format_data(); + + // record options and data + this.show_record_name = this.record_name && !this.thread_level && this.model != 'res.partner'; + 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 = parent.messages != undefined ? parent : this.options.root_thread; + this.thread = false; + }, + + /* Convert date, timerelative and avatar in displayable data. */ + format_data: function () { + + //formating and add some fields for render + if (this._date) { + this.date = session.web.format_value(this._date, {type:"datetime"}); + this.timerelative = $.timeago(this.date); + } + 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.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', this.author_id[0]); + } else { + this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid); + } + + }, + + + /* 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.ChatterUtils.get_attachment_url(this.session, attach); + attach.filetype = mail.ChatterUtils.filetype(attach.filename); + attach.name = mail.ChatterUtils.breakword(attach.name || attach.filename); + attach.formating = true; + } + } + this.$(".oe_msg_attachment_list").html( session.web.qweb.render('mail.thread.message.attachments', {'widget': this}) ); + }, + + /* return the link to resized image + */ + attachments_resize_image: function (id, resize) { + return mail.ChatterUtils.get_image(this.session, 'ir.attachment', 'datas', id, resize); + }, + + /* get all child message id linked. + * @return array of id + */ + get_child_ids: function () { + return _.map(this.get_childs(), function (val) { return val.id; }); + }, + + /* 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; + }, + + /** + * 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) { + // goto the wall thread for launch browse + if (!options._go_thread_wall) { + options._go_thread_wall = true; + for (var i in this.options.root_thread.messages) { + var res=this.options.root_thread.messages[i].browse_message(options); + if (res) return res; + } + } + + if (this.id==options.id) + return this; + + for (var i in this.thread.messages) { + if (this.thread.messages[i].thread) { + var res=this.thread.messages[i].browse_message(options); + if (res) return res; + } + } + + return false; + }, + + /** + * call on_message_delete on his parent thread + */ + destroy: function () { + + this._super(); + this.parent_thread.on_message_detroy(this); + + } + + }); + /** * ------------------------------------------------------------ * ComposeMessage widget @@ -126,7 +382,7 @@ openerp.mail = function (session) { * When the user focuses the textarea, the compose message is instantiated. */ - mail.ThreadComposeMessage = session.web.Widget.extend({ + mail.ThreadComposeMessage = mail.MessageCommon.extend({ template: 'mail.compose_message', /** @@ -138,45 +394,22 @@ openerp.mail = function (session) { */ init: function (parent, datasets, options) { - var self = this; - this._super(parent); - this.context = options.context || {}; - this.options = options.options; - + this._super(parent, datasets, options); this.show_compact_message = false; - - // data of this compose message - this.attachment_ids = []; - this.id = datasets.id; - this.model = datasets.model; - this.res_model = datasets.res_model; - this.is_private = datasets.is_private || false; - this.partner_ids = datasets.partner_ids || []; - this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid); - this.thread_level = datasets.thread_level; - this.parent_thread= parent.messages!= undefined ? parent : false; - - this.ds_attachment = new session.web.DataSetSearch(this, 'ir.attachment'); this.show_delete_attachment = true; - - this.fileupload_id = _.uniqueId('oe_fileupload_temp'); - $(window).on(self.fileupload_id, self.on_attachment_loaded); }, start: function () { + this._super.apply(this, arguments); + + this.ds_attachment = new session.web.DataSetSearch(this, 'ir.attachment'); + this.fileupload_id = _.uniqueId('oe_fileupload_temp'); + $(window).on(this.fileupload_id, this.on_attachment_loaded); + this.display_attachments(); this.bind_events(); }, - /* upload the file on the server, add in the attachments list and reload display - */ - display_attachments: function () { - this.$(".oe_msg_attachment_list").html( - session.web.qweb.render('mail.thread.message.attachments', {'widget': this}) ); - // event: delete an attachment - this.$(".oe_msg_attachment_list").on('click', '.oe_mail_attachment_delete', this.on_attachment_delete); - }, - /* when a user click on the upload button, send file read on_attachment_loaded */ on_attachment_change: function (event) { @@ -263,24 +496,31 @@ openerp.mail = function (session) { this.$('textarea.oe_compact').on('focus', _.bind( this.on_compose_expandable, this)); // set the function called when attachments are added - this.$el.on('change', 'input.oe_form_binary_file', _.bind( this.on_attachment_change, this) ); + this.$('input.oe_form_binary_file').on('change', _.bind( this.on_attachment_change, this) ); - this.$el.on('click', '.oe_cancel', _.bind( this.on_cancel, this) ); - this.$el.on('click', '.oe_post', _.bind( this.on_message_post, this) ); - this.$el.on('click', '.oe_full', _.bind( this.on_compose_fullmail, this, 'reply') ); + this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this) ); + this.$('.oe_post').on('click', _.bind( this.on_message_post, this) ); + 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.$el.on('mousedown', '.oe_msg_footer', _.bind( function () { this.stay_open = true; }, this)); - this.$('textarea:not(.oe_compact):first').on('focus, mouseup, keydown', _.bind( function () { this.stay_open = false; }, this)); - this.$('textarea:not(.oe_compact):first').autosize(); + this.$('.oe_msg_footer').on('mousedown', _.bind( function () { this.stay_open = true; }, this)); + var ev_stay = {}; + ev_stay.mouseup = ev_stay.keydown = ev_stay.focus = function () { self.stay_open = false; }; + this.$('textarea:not(.oe_compact)').on(ev_stay); + this.$('textarea:not(.oe_compact)').autosize(); // auto close - this.$el.on('blur', 'textarea:not(.oe_compact):first', _.bind( this.on_compose_expandable, this)); + this.$('textarea:not(.oe_compact)').on('blur', _.bind( this.on_compose_expandable, this)); + + // event: delete child attachments off the oe_msg_attachment_list box + this.$(".oe_msg_attachment_list").on('click', '.oe_delete', this.on_attachment_delete); }, on_compose_fullmail: function (default_composition_mode) { if (default_composition_mode == 'reply') { var context = { + 'default_model': this.context.default_model, + 'default_res_id': this.context.default_res_id, 'default_composition_mode': default_composition_mode, 'default_parent_id': this.id, 'default_body': mail.ChatterUtils.get_text2html(this.$el ? (this.$el.find('textarea:not(.oe_compact)').val() || '') : ''), @@ -357,10 +597,15 @@ openerp.mail = function (session) { this.context.default_parent_id, attachments, this.parent_thread.context - ]).then(function (record) { + ]).done(function (record) { var thread = self.parent_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(false, false, [record], function (arg, data) { + thread.message_fetch([['id', 'child_of', [self.id]]], false, [record], function (arg, data) { + data[0].no_sorted = true; var message = thread.create_message_object( data[0] ); // insert the message on dom thread.insert_message( message, self.$el ); @@ -419,30 +664,16 @@ openerp.mail = function (session) { * - - visible message * - - expandable */ - mail.ThreadExpandable = session.web.Widget.extend({ + mail.ThreadExpandable = mail.MessageCommon.extend({ template: 'mail.thread.expandable', - init: function (parent, datasets, context) { - this._super(parent); - this.domain = datasets.domain || []; - this.options = datasets.options; - this.context = _.extend({ - default_model: 'mail.thread', - default_res_id: 0, - default_parent_id: false }, context || {}); - - // data of this expandable message - this.id = datasets.id || -1, - this.model = datasets.model || false, - this.parent_id = datasets.parent_id || false, - this.nb_messages = datasets.nb_messages || 0, - this.thread_level = datasets.thread_level || 0, - this.type = 'expandable', - this.max_limit = this.id < 0 || false, - this.flag_used = false, - this.parent_thread= parent.messages!= undefined ? parent : this.options._parents[0]; + 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); @@ -460,7 +691,7 @@ openerp.mail = function (session) { * Bind events in the widget. Each event is slightly described * in the function. */ bind_events: function () { - this.$el.on('click', 'a.oe_msg_fetch_more', this.on_expandable); + this.$('.oe_msg_more_message').on('click', this.on_expandable); }, animated_destroy: function (fadeTime) { @@ -480,154 +711,36 @@ openerp.mail = function (session) { } this.flag_used = true; - this.animated_destroy(200); - this.parent_thread.message_fetch(this.domain, this.context); + var self = this; + + // read messages + self.parent_thread.message_fetch(this.domain, this.context, false, function (arg, data) { + // insert the message on dom after this message + self.id = false; + self.parent_thread.switch_new_message( data, self.$el ); + self.animated_destroy(200); + }); + return false; }, - /** - * call on_message_delete on his parent thread - */ - destroy: function () { - - this._super(); - this.parent_thread.on_message_detroy(this); - - } }); - /** - * ------------------------------------------------------------ - * Thread Message Widget - * ------------------------------------------------------------ - * 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 - */ - mail.ThreadMessage = session.web.Widget.extend({ + mail.ThreadMessage = mail.MessageCommon.extend({ template: 'mail.thread.message', - /** - * @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} [thread] read obout mail.Thread object - * @param {Object} [message] - * @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] - *... @param {int} [show_reply_button] number thread level to display the reply button - *... @param {int} [show_read_unread_button] number thread level to display the read/unread button - */ - init: function (parent, datasets, context) { - this._super(parent); - - // record domain and context - this.domain = datasets.domain || []; - this.context = _.extend({ - default_model: 'mail.thread', - default_res_id: 0, - default_parent_id: false }, context || {}); - - // record options - this.options = datasets.options; - - // data of this message - this.id = datasets.id || -1, - this.model = datasets.model || false, - this.parent_id = datasets.parent_id || false, - this.res_id = datasets.res_id || false, - this.type = datasets.type || false, - this.is_author = datasets.is_author || 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 || false, - 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 || [], - this.attachment_ids = datasets.attachment_ids || [], - this._date = datasets.date; - - - this.show_reply_button = this.options.show_compose_message && this.options.show_reply_button > this.thread_level; - this.show_read_unread_button = this.options.show_read_unread_button > this.thread_level; - - // record options and data - this.parent_thread= parent.messages!= undefined ? parent : this.options._parents[0]; - this.thread = false; - - if ( this.id > 0 ) { - this.formating_data(); - } - - this.ds_notification = new session.web.DataSetSearch(this, 'mail.notification'); - this.ds_message = new session.web.DataSetSearch(this, 'mail.message'); - this.ds_follow = new session.web.DataSetSearch(this, 'mail.followers'); - }, - - /* Convert date, timerelative and avatar in displayable data. */ - formating_data: function () { - - //formating and add some fields for render - this.date = session.web.format_value(this._date, {type:"datetime"}); - this.timerelative = $.timeago(this.date); - if (this.type == 'email') { - this.avatar = ('/mail/static/src/img/email_icon.png'); - } else { - this.avatar = mail.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', this.author_id[0]); - } - for (var l in this.attachment_ids) { - var attach = this.attachment_ids[l]; - attach['url'] = mail.ChatterUtils.get_attachment_url(this.session, attach); - - if ((attach.filename || attach.name).match(/[.](jpg|jpg|gif|png|tif|svg)$/i)) { - attach.is_image = true; - attach['url'] = mail.ChatterUtils.get_image(this.session, 'ir.attachment', 'datas', attach.id); - } - } - }, start: function () { this._super.apply(this, arguments); this.expender(); - this.$el.hide().fadeIn(750, function () {$(this).css('display', '');}); - this.resize_img(); this.bind_events(); if(this.thread_level < this.options.display_indented_thread) { this.create_thread(); } this.$('.oe_msg_attachments, .oe_msg_images').addClass("oe_hidden"); - }, - resize_img: function () { - var resize = function () { - var h = $(this).height(); - var w = $(this).width(); - if ( h > 100 || w >100 ) { - var ratio = 100 / (h > w ? h : w); - $(this).attr("width", parseInt( w*ratio )).attr("height", parseInt( h*ratio )); - } - }; - this.$("img").load(resize).each(resize); + this.ds_notification = new session.web.DataSetSearch(this, 'mail.notification'); + this.ds_message = new session.web.DataSetSearch(this, 'mail.message'); }, /** @@ -635,30 +748,15 @@ openerp.mail = function (session) { * in the function. */ bind_events: function () { var self = this; + // 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_view_attachments').on('click', this.on_view_attachments); - // event: click on 'Attachment(s)' in msg - this.$('.oe_mail_msg_view_attachments').on('click', function (event) { - var attach = self.$('.oe_msg_attachments:first, .oe_msg_images:first'); - if ( self.$('.oe_msg_attachments:first').hasClass("oe_hidden") ) { - attach.removeClass("oe_hidden"); - } else { - attach.addClass("oe_hidden"); - } - self.resize_img(); - }); - // event: click on icone 'Read' in header - this.$el.on('click', '.oe_read', this.on_message_read_unread); - // event: click on icone 'UnRead' in header - this.$el.on('click', '.oe_unread', this.on_message_read_unread); - // event: click on 'Delete' in msg side menu - this.$el.on('click', '.oe_msg_delete', this.on_message_delete); - - // event: click on 'Reply' in msg - this.$el.on('click', '.oe_reply', this.on_message_reply); - // event: click on 'Vote' button - this.$el.on('click', '.oe_msg_vote', this.on_vote); - // event: click on 'starred/favorite' button - this.$el.on('click', '.oe_star', this.on_star); }, /* Call the on_compose_message on the thread of this message. */ @@ -673,7 +771,7 @@ openerp.mail = function (session) { this.$('.oe_msg_body:first').expander({ slicePoint: this.options.truncate_limit, expandText: 'read more', - userCollapseText: '[^]', + userCollapseText: 'read less', detailClass: 'oe_msg_tail', moreClass: 'oe_mail_expand', lessClass: 'oe_mail_reduce', @@ -717,6 +815,17 @@ openerp.mail = function (session) { } }, + /* Call the on_compose_message on the thread of this message. */ + on_view_attachments:function (event) { + event.stopPropagation(); + var self = this; + if (!this.toggle_attachment) { + self.display_attachments(); + this.toggle_attachment = true; + } + this.$('.oe_msg_attachment_list').toggle(200); + }, + /** * Wait a confirmation for delete the message on the DB. * Make an animate destroy @@ -732,81 +841,90 @@ openerp.mail = function (session) { 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 messages = [this].concat(this.get_childs()); + var message_ids = _.map(messages, function (msg) { return msg.id;}); + var domain = mail.ChatterUtils.expand_domain( this.options.root_thread.domain ) + .concat([["id", "in", message_ids ]]); + + return this.parent_thread.ds_message.call('message_read', [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() + } + }); + + }); + }, + + on_message_read: function (event) { + event.stopPropagation(); + this.on_message_read_unread(true); + return false; + }, + + on_message_unread: function (event) { + event.stopPropagation(); + this.on_message_read_unread(false); + return false; + }, + /*The selected thread and all childs (messages/thread) became read * @param {object} mouse envent */ - on_message_read_unread: function (event) { - event.stopPropagation(); - var self=this; + on_message_read_unread: function (read_value) { + var self = this; + var messages = [this].concat(this.get_childs()); - if ( (this.to_read && this.options.typeof_thread == 'inbox') || - (!this.to_read && this.options.typeof_thread == 'archives')) { - this.animated_destroy(150); - } - - // if this message is read, all childs message display is read - this.ds_notification.call('set_message_read', [ [this.id].concat( this.get_child_ids() ) , this.to_read, this.context]).pipe(function () { - self.$el.removeClass(self.to_read ? 'oe_msg_unread':'oe_msg_read').addClass(self.to_read ? 'oe_msg_read':'oe_msg_unread'); - self.to_read = !self.to_read; - }); - return 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 thread object - */ - browse_message: function (options) { - // goto the wall thread for launch browse - if (!options._go_thread_wall) { - options._go_thread_wall = true; - for (var i in this.options._parents[0].messages) { - var res=this.options._parents[0].messages[i].browse_message(options); - if (res) return res; - } - } - - if (this.id==options.id) - return this; - - for (var i in this.thread.messages) { - if (this.thread.messages[i].thread) { - var res=this.thread.messages[i].browse_message(options); - if (res) return res; + // 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) { + var messages = _.filter(messages, function (val) { return !val.is_favorite && val.id; }); + if (!messages.length) { + this.check_for_rerender(); + return false; } } + var message_ids = _.map(messages, function (val) { return val.id; }); + this.ds_notification.call('set_message_read', [message_ids, read_value, 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(); + }); return false; }, - /* get all child message id linked. - * @return array of id - */ - get_child_ids: function () { - var res=[] - if (arguments[0]) res.push(this.id); - if (this.thread) { - res = res.concat( this.thread.get_child_ids(true) ); - } - return res; - }, - /** * add or remove a vote for a message and display the result */ on_vote: function (event) { event.stopPropagation(); - var self=this; - return this.ds_message.call('vote_toggle', [[self.id]]).pipe(function (vote) { - self.has_voted = vote; - self.vote_nb += self.has_voted ? 1 : -1; - self.display_vote(); - }); + 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; }, @@ -814,10 +932,10 @@ openerp.mail = function (session) { * Display the render of this message's vote */ display_vote: function () { - var self = this; - var vote_element = session.web.qweb.render('mail.thread.message.vote', {'widget': self}); - self.$(".oe_msg_vote:first").remove(); - self.$(".oe_mail_vote_count:first").replaceWith(vote_element); + var vote_element = session.web.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); }, /** @@ -827,34 +945,29 @@ openerp.mail = function (session) { event.stopPropagation(); var self=this; var button = self.$('.oe_star:first'); - return this.ds_message.call('favorite_toggle', [[self.id]]).pipe(function (star) { - self.is_favorite=star; - if (self.is_favorite) { - button.addClass('oe_starred'); - } else { - button.removeClass('oe_starred'); - if ( self.options.typeof_thread == 'stared' ) { - self.animated_destroy(150); + + this.ds_message.call('favorite_toggle', [[self.id]]) + .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(); + } + }); return false; }, - /** - * call on_message_delete on his parent thread - */ - destroy: function () { - - this._super(); - this.parent_thread.on_message_detroy(this); - - } - }); /** - * ------------------------------------------------------------ + * ------------------------------------------------------------ * Thread Widget * ------------------------------------------------------------ * @@ -881,14 +994,11 @@ openerp.mail = function (session) { * @param {Object} [thread] * @param {int} [display_indented_thread] number thread level to indented threads. * other are on flat mode - * @param {Select} [typeof_thread] inbox/archives/stared/sent - * type of thread and option for user application like animate - * destroy for read/unread * @param {Array} [parents] liked with the parents thread * use with browse, fetch... [O]= top parent */ init: function (parent, datasets, options) { - this._super(parent); + this._super(parent, options); this.domain = options.domain || []; this.context = _.extend({ default_model: 'mail.thread', @@ -896,21 +1006,24 @@ openerp.mail = function (session) { default_parent_id: false }, options.context || {}); this.options = options.options; - this.options._parents = (options.options._parents != undefined ? options.options._parents : []).concat( [this] ); - + 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.options.display_indented_thread >= this.thread_level || !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.model = datasets.model || 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, this.partner_ids = _.filter(datasets.partner_ids, function (partner) { return partner[0]!=datasets.author_id[0]; } ) this.messages = []; - this.show_compose_message = this.options.show_compose_message && (this.options.show_reply_button > this.thread_level || !this.thread_level); + + this.options.flat_mode = !!(this.options.display_indented_thread > this.thread_level ? this.options.display_indented_thread - this.thread_level : 0); // object compose message this.compose_message = false; @@ -934,13 +1047,10 @@ openerp.mail = function (session) { 'context': this.context, 'options': this.options, }); - if (!this.thread_level) { - // root view + if (!this.thread_level || this.thread_level > this.options.display_indented_thread) { this.compose_message.insertBefore(this.$el); - } else if (this.thread_level > this.options.display_indented_thread) { - this.compose_message.insertAfter(this.$el); } else { - this.compose_message.appendTo(this.$el); + this.compose_message.prependTo(this.$el); } } }, @@ -948,21 +1058,20 @@ openerp.mail = function (session) { /* When the expandable object is visible on screen (with scrolling) * then the on_expandable function is launch */ - on_scroll: function (event) { - if (event)event.stopPropagation(); - this.$('.oe_msg_expandable:last'); - - var message = this.messages[this.messages.length-1]; - if (message && message.type=="expandable" && message.max_limit) { - var pos = message.$el.position(); + on_scroll: function () { + var expandables = + _.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) { - message.on_expandable(); + val.on_expandable(); + // load only one time + val.loading = true; } } - } + }); }, /** @@ -970,8 +1079,8 @@ openerp.mail = function (session) { * in the function. */ bind_events: function () { var self = this; - self.$el.on('click', '.oe_mail_list_recipients .oe_more', self.on_show_recipients); - self.$el.on('click', '.oe_mail_compose_textarea .oe_more_hidden', self.on_hide_recipients); + 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); }, /** @@ -981,6 +1090,7 @@ openerp.mail = function (session) { var p=$(this).parent(); p.find('.oe_more_hidden, .oe_hidden').show(); p.find('.oe_more').hide(); + return false; }, /** @@ -990,15 +1100,14 @@ openerp.mail = function (session) { var p=$(this).parent(); p.find('.oe_more_hidden, .oe_hidden').hide(); p.find('.oe_more').show(); + return false; }, /* get all child message/thread id linked. * @return array of id */ get_child_ids: function () { - var res=[]; - _(this.get_childs()).each(function (val, key) { res.push(val.id); }); - return res; + return _.map(this.get_childs(), function (val) { return val.id; }); }, /* get all child message/thread linked. @@ -1033,17 +1142,17 @@ openerp.mail = function (session) { // goto the wall thread for launch browse if (!options._go_thread_wall) { options._go_thread_wall = true; - return this.options._parents[0].browse_thread(options); + return this.options.root_thread.browse_thread(options); } - if (this.id==options.id) { + if (this.id == options.id) { return this; } 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}); + var res = this.messages[i].thread.browse_thread({'id':options.id, '_go_thread_wall':true}); if (res) return res; } } @@ -1067,8 +1176,8 @@ openerp.mail = function (session) { * @return message object */ browse_message: function (options) { - if (this.options._parents[0].messages[0]) - return this.options._parents[0].messages[0].browse_message(options); + if (this.options.root_thread.messages[0]) + return this.options.root_thread.messages[0].browse_message(options); }, /** @@ -1079,6 +1188,7 @@ openerp.mail = function (session) { on_compose_message: function () { this.instantiate_compose_message(); this.compose_message.on_compose_expandable(); + return false; }, /** @@ -1086,8 +1196,8 @@ openerp.mail = function (session) { */ no_message: function () { var no_message = $(session.web.qweb.render('mail.wall_no_message', {})); - if (this.options.no_message) { - no_message.html(this.options.no_message); + if (this.options.help) { + no_message.html(this.options.help); } no_message.appendTo(this.$el); }, @@ -1101,18 +1211,20 @@ openerp.mail = function (session) { * @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) { - var self = this; - - // domain and context: options + additional - fetch_domain = replace_domain ? replace_domain : this.domain; - fetch_context = replace_context ? replace_context : this.context; - var message_loaded_ids = this.id ? [this.id].concat( self.get_child_ids() ) : self.get_child_ids(); - - // CHM note : option for sending in flat mode by server - var thread_level = this.options.display_indented_thread > this.thread_level ? this.options.display_indented_thread - this.thread_level : 0; - - return this.ds_message.call('message_read', [ids, fetch_domain, message_loaded_ids, thread_level, fetch_context, this.context.default_parent_id || undefined]) - .then(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message')); + return this.ds_message.call('message_read', [ + // ids force to read + ids == false ? undefined : ids, + // 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 + ]).done(callback ? _.bind(callback, this, arguments) : this.proxy('switch_new_message')); }, /** @@ -1128,22 +1240,23 @@ openerp.mail = function (session) { data.options = _.extend(self.options, data.options); if (data.type=='expandable') { - var message = new mail.ThreadExpandable(self, data, { + var message = new mail.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 { - var message = new mail.ThreadMessage(self, data, { + var message = new mail.ThreadMessage(self, data, {'context':{ 'default_model': data.model, 'default_res_id': data.res_id, 'default_parent_id': data.id, - }); + }}); } // check if the message is already create for (var i in self.messages) { if (self.messages[i] && self.messages[i].id == message.id) { + console.log('Reload message', message.id); self.messages[i].destroy(); } } @@ -1163,74 +1276,18 @@ openerp.mail = function (session) { */ insert_message: function (message, dom_insert_after) { var self=this; - - if (this.show_compose_message && this.options.show_compact_message) { + if (this.options.show_compact_message > this.thread_level) { this.instantiate_compose_message(); this.compose_message.do_show_compact(); } - this.$('.oe_wall_no_message').remove(); + this.$('.oe_view_nocontent').remove(); if (dom_insert_after) { message.insertAfter(dom_insert_after); - return message - } - - // check older and newer message for insertion - var message_newer = false; - var message_older = false; - if (message.id > 0) { - for (var i in self.messages) { - if (self.messages[i].id > message.id) { - if (!message_newer || message_newer.id > self.messages[i].id) { - message_newer = self.messages[i]; - } - } else if (self.messages[i].id > 0 && self.messages[i].id < message.id) { - if (!message_older || message_older.id < self.messages[i].id) { - message_older = self.messages[i]; - } - } - } - } - - var sort = (!!self.thread_level || message.id<0); - - if (sort) { - if (message_older) { - - message.insertAfter(message_older.thread ? (message_older.thread.compose_message ? message_older.thread.compose_message.$el : message_older.thread.$el) : message_older.$el); - - } else if (message_newer) { - - message.insertBefore(message_newer.$el); - - } else if (message.id < 0) { - - message.appendTo(self.$el); - - } else { - - message.prependTo(self.$el); - } } else { - if (message_older) { - - message.insertBefore(message_older.$el); - - } else if (message_newer) { - - message.insertAfter(message_newer.thread ? (message_newer.thread.compose_message ? message_newer.thread.compose_message.$el : message_newer.thread.$el) : message_newer.$el ); - - } else if (message.id < 0) { - - message.prependTo(self.$el); - - } else { - - message.appendTo(self.$el); - - } + message.appendTo(self.$el); } return message @@ -1241,7 +1298,7 @@ openerp.mail = function (session) { * 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" */ - switch_new_message: function (records) { + switch_new_message: function (records, dom_insert_after) { var self=this; _(records).each(function (record) { var thread = self.browse_thread({ @@ -1251,7 +1308,7 @@ openerp.mail = function (session) { // create object and attach to the thread object var message = thread.create_message_object( record ); // insert the message on dom - thread.insert_message( message ); + thread.insert_message( message, typeof dom_insert_after == 'object' ? dom_insert_after : false); }); }, @@ -1262,6 +1319,10 @@ openerp.mail = function (session) { on_message_detroy: function (message) { this.messages = _.filter(this.messages, function (val) { return !val.isDestroyed(); }); + if (this.options.root_thread == this && !this.messages.length) { + this.no_message(); + } + return false; }, @@ -1323,7 +1384,6 @@ openerp.mail = function (session) { } else { // create a expandable message var expandable = new mail.ThreadExpandable(this, { - 'id': message.id, 'model': message.model, 'parent_id': message.parent_id, 'nb_messages': 1, @@ -1332,9 +1392,11 @@ openerp.mail = function (session) { 'domain': message_dom, 'options': message.options, }, { - 'default_model': message.model || this.context.default_model, - 'default_res_id': message.res_id || this.context.default_res_id, - 'default_parent_id': this.id, + '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, + } }); // add object on array and DOM @@ -1350,7 +1412,7 @@ openerp.mail = function (session) { }); /** - * ------------------------------------------------------------ + * ------------------------------------------------------------ * mail : root Widget * ------------------------------------------------------------ * @@ -1360,7 +1422,7 @@ openerp.mail = function (session) { */ session.web.client_actions.add('mail.Widget', 'session.mail.Widget'); mail.Widget = session.web.Widget.extend({ - template: 'mail.Widget', + template: 'mail.Root', /** * @param {Object} parent parent @@ -1368,15 +1430,13 @@ openerp.mail = function (session) { * @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 {Select} [typeof_thread=(mail|stared|archives|send|other)] - * options for destroy message when the user click on a button * @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 {int} [show_reply_button] number thread level to display the reply button - *... @param {int} [show_read_unread_button] number thread level to display the read/unread button + *... @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 @@ -1387,35 +1447,25 @@ openerp.mail = function (session) { * @param {String} [no_message] Message to display when there are no message */ init: function (parent, action) { - var options = action.params || {}; - this._super(parent); - this.domain = options.domain || []; - this.context = options.context || {}; - this.search_results = {'domain': [], 'context': {}, 'groupby': {}}; + this._super(parent, action); + var self = this; + this.action = _.clone(action); + this.domain = this.action.domain || this.action.params.domain || []; + this.context = this.action.context || this.action.params.context || {}; - this.options = _.extend({ - 'typeof_thread' : 'inbox', + this.action.params = _.extend({ 'display_indented_thread' : -1, - 'show_reply_button' : -1, - 'show_read_unread_button' : -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, + 'view_inbox': false, 'message_ids': undefined, - 'no_message': false - }, options); + }, this.action.params); - if (this.display_indented_thread === false) { - this.display_indented_thread = -1; - } - if (this.show_reply_button === false) { - this.show_reply_button = -1; - } - if (this.show_read_unread_button === false) { - this.show_read_unread_button = -1; - } - + this.action.params.help = this.action.help || false; }, start: function (options) { @@ -1423,7 +1473,6 @@ openerp.mail = function (session) { this.message_render(); this.bind_events(); }, - /** *Create the root thread and display this object in the DOM. @@ -1435,35 +1484,32 @@ openerp.mail = function (session) { this.thread = new mail.Thread(this, {}, { 'domain' : this.domain, 'context' : this.context, - 'options': this.options, + 'options': this.action.params, }); this.thread.appendTo( this.$el ); this.thread.no_message(); - this.thread.message_fetch(null, null, this.options.message_ids); - if (this.options.show_compose_message) { + if (this.action.params.show_compose_message) { this.thread.instantiate_compose_message(); - if (this.options.show_compact_message) { - this.thread.compose_message.do_show_compact(); - } else { - this.thread.compose_message.do_hide_compact(); - } + this.thread.compose_message.do_show_compact(); } + + this.thread.message_fetch(null, null, this.action.params.message_ids); + }, bind_events: function () { - if (this.context['typeof_thread']!='other') { - $(document).scroll( this.thread.on_scroll ); - $(window).resize( this.thread.on_scroll ); - window.setTimeout( this.thread.on_scroll, 500 ); - } + $(document).scroll( _.bind(this.thread.on_scroll, this.thread) ); + $(window).resize( _.bind(this.thread.on_scroll, this.thread) ); + this.$el.resize( _.bind(this.thread.on_scroll, this.thread) ); + window.setTimeout( _.bind(this.thread.on_scroll, this.thread), 500 ); } }); /** - * ------------------------------------------------------------ + * ------------------------------------------------------------ * mail_thread Widget * ------------------------------------------------------------ * @@ -1476,10 +1522,19 @@ openerp.mail = function (session) { mail.RecordThread = session.web.form.AbstractField.extend({ template: 'mail.record_thread', - init: function () { + init: function (parent, node) { this._super.apply(this, arguments); - this.options.domain = this.options.domain || []; - this.options.context = {'default_model': 'mail.thread', 'default_res_id': false}; + this.node = _.clone(node); + + this.node.params = _.extend({ + 'display_indented_thread': -1, + 'show_reply_button': false, + 'show_read_unread_button': false, + 'show_compose_message': this.view.is_action_enabled('edit'), + 'show_compact_message': 1, + }, this.node.params); + + this.domain = this.node.params && this.node.params.domain || []; }, start: function () { @@ -1492,42 +1547,32 @@ openerp.mail = function (session) { _check_visibility: function () { this.$el.toggle(this.view.get("actual_mode") !== "create"); }, + render_value: function () { var self = this; + if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { this.$('oe_mail_thread').hide(); return; } - // update context - _.extend(this.options.context, { - default_res_id: this.view.datarecord.id, - default_model: this.view.model, - default_is_private: false }); - // update domain - var domain = this.options.domain.concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]); - var show_compose_message = this.view.is_action_enabled('edit') || - (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value()); - - var message_ids = this.getParent().fields.message_ids && this.getParent().fields.message_ids.get_value(); + this.node.params = _.extend({ + 'message_ids': this.getParent().fields.message_ids ? this.getParent().fields.message_ids.get_value() : undefined, + }, this.node.params); + this.node.context = { + 'default_res_id': this.view.datarecord.id || false, + 'default_model': this.view.model || false, + }; if (this.root) { + $('').insertAfter(this.root.$el); this.root.destroy(); } // create and render Thread widget - this.root = new mail.Widget(this, { params: { - 'domain' : domain, - 'context' : this.options.context, - 'typeof_thread': this.options.context['typeof_thread'] || 'other', - 'display_indented_thread': -1, - 'show_reply_button': 0, - 'show_read_unread_button': -1, - 'show_compose_message': show_compose_message, - 'message_ids': message_ids, - 'show_compact_message': true, - 'no_message': this.node.attrs.help - }} - ); + this.root = new mail.Widget(this, _.extend(this.node, { + 'domain' : (this.domain || []).concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]), + + })); return this.root.replace(this.$('.oe_mail-placeholder')); }, @@ -1535,7 +1580,7 @@ openerp.mail = function (session) { /** - * ------------------------------------------------------------ + * ------------------------------------------------------------ * Wall Widget * ------------------------------------------------------------ * @@ -1543,6 +1588,7 @@ openerp.mail = function (session) { * use is to receive a context and a domain, and to delegate the message * fetching and displaying to the Thread widget. */ + session.web.client_actions.add('mail.wall', 'session.mail.Wall'); mail.Wall = session.web.Widget.extend({ template: 'mail.wall', @@ -1555,35 +1601,53 @@ openerp.mail = function (session) { * contain default_model, default_res_id, to give it to the threads. */ init: function (parent, action) { - this._super(parent); - var options = action.params || {}; - this.options = options; - this.options.domain = options.domain || []; - this.options.context = options.context || {}; - this.search_results = {'domain': [], 'context': {}, 'groupby': {}} - this.ds_msg = new session.web.DataSetSearch(this, 'mail.message'); + this._super(parent, action); + + this.action = _.clone(action); + this.domain = this.action.params.domain || this.action.domain || []; + this.context = this.action.params.context || this.action.context || {}; + + this.defaults = {}; + for (var 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_compact_message': this.action.params.view_mailbox ? false : 1, + 'view_inbox': false, + }, this.action.params); }, start: function () { - this._super.apply(this, arguments); - var searchview_ready = this.load_searchview({}, false); - var thread_displayed = this.message_render(); - this.options.domain = this.options.domain.concat(this.search_results['domain']); + this._super.apply(this); this.bind_events(); - return $.when(searchview_ready, thread_displayed); + var searchview_loaded = this.load_searchview(this.defaults); + if (! this.searchview.has_defaults) { + this.message_render(); + } + }, /** * Load the mail.message search view * @param {Object} defaults ?? - * @param {Boolean} hidden some kind of trick we do not care here */ - load_searchview: function (defaults, hidden) { + load_searchview: function (defaults) { var self = this; - this.searchview = new session.web.SearchView(this, this.ds_msg, false, defaults || {}, hidden || false); - return this.searchview.appendTo(this.$('.oe_view_manager_view_search')).then(function () { - self.searchview.on('search_data', self, self.do_searchview_search); - }); + var ds_msg = new session.web.DataSetSearch(this, 'mail.message'); + this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, false); + this.searchview.appendTo(this.$('.oe_view_manager_view_search')) + .then(function () { self.searchview.on('search_data', self, self.do_searchview_search); }); + if (this.searchview.has_defaults) { + this.searchview.ready.then(this.searchview.do_search); + } + return this.searchview }, /** @@ -1600,32 +1664,25 @@ openerp.mail = function (session) { contexts: contexts || [], group_by_seq: groupbys || [] }).then(function (results) { - self.search_results['context'] = results.context; - self.search_results['domain'] = results.domain; - self.root.destroy(); - return self.message_render(); + if(self.root) { + $('').insertAfter(self.root.$el); + self.root.destroy(); + } + return self.message_render(results); }); }, - /** - *Create the root thread widget and display this object in the DOM - */ + * Create the root thread widget and display this object in the DOM + */ message_render: function (search) { - var domain = this.options.domain.concat(this.search_results['domain']); - var context = _.extend(this.options.context, search&&search.search_results['context'] ? search.search_results['context'] : {}); - this.root = new mail.Widget(this, { params: { + var domain = this.domain.concat(search && search['domain'] ? search['domain'] : []); + var context = _.extend(this.context, search && search['context'] ? search['context'] : {}); + + this.root = new mail.Widget(this, _.extend(this.action, { 'domain' : domain, 'context' : context, - 'typeof_thread': context['typeof_thread'] || 'other', - 'display_indented_thread': 1, - 'show_reply_button': 10, - 'show_read_unread_button': 11, - 'show_compose_message': true, - 'show_compact_message': false, - }} - ); - + })); return this.root.replace(this.$('.oe_mail-placeholder')); }, @@ -1653,19 +1710,18 @@ openerp.mail = function (session) { /** - * ------------------------------------------------------------ + * ------------------------------------------------------------ * UserMenu * ------------------------------------------------------------ * * Add a link on the top user bar for write a full mail */ session.web.ComposeMessageTopButton = session.web.Widget.extend({ - template:'mail.compose_message.button_top_bar', + template:'mail.ComposeMessageTopButton', - start: function (parent, params) { - var self = this; - this.$el.on('click', 'button', self.on_compose_message ); - this._super(parent, params); + start: function () { + this.$('button').on('click', this.on_compose_message ); + this._super(); }, on_compose_message: function (event) { @@ -1675,26 +1731,22 @@ openerp.mail = function (session) { res_model: 'mail.compose.message', view_mode: 'form', view_type: 'form', - action_from: 'mail.ThreadComposeMessage', views: [[false, 'form']], target: 'new', - context: { - 'default_model': '', - 'default_res_id': false, - 'default_content_subtype': 'html', - }, + context: { 'default_content_subtype': 'html' }, }; session.client.action_manager.do_action(action); }, - }); - session.web.UserMenu = session.web.UserMenu.extend({ - start: function (parent, params) { - var render = new session.web.ComposeMessageTopButton(); - render.insertAfter(this.$el); - this._super(parent, params); - } + session.web.UserMenu.include({ + do_update: function(){ + var self = this; + this._super.apply(this, arguments); + this.update_promise.then(function() { + var mail_button = new session.web.ComposeMessageTopButton(); + mail_button.appendTo(session.webclient.$el.find('.oe_systray')); + }); + }, }); - }; diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index a13d2048bf3..7bb019cde4f 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -30,6 +30,8 @@ openerp_mail_followers = function(session, mail) { this.ds_model = new session.web.DataSetSearch(this, this.view.model); this.ds_follow = new session.web.DataSetSearch(this, this.field.relation); this.ds_users = new session.web.DataSetSearch(this, 'res.users'); + + this.value = []; }, start: function() { @@ -41,6 +43,11 @@ openerp_mail_followers = function(session, mail) { this._super(); }, + set_value: function(_value) { + this.value = _value; + this._super(_value); + }, + _check_visibility: function() { this.$el.toggle(this.view.get("actual_mode") !== "create"); }, @@ -85,22 +92,23 @@ openerp_mail_followers = function(session, mail) { read_value: function () { var self = this; - return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) { - self.set_value(results[0].message_follower_ids); + 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 () { this.reinit(); - return this.fetch_followers(this.get("value")); + return this.fetch_followers(this.value); }, fetch_followers: function (value_) { this.value = value_ || {}; return this.ds_follow.call('read', [this.value, ['name', 'user_ids']]) - .pipe(this.proxy('display_followers'), this.proxy('fetch_generic')) - .pipe(this.proxy('display_buttons')) - .pipe(this.proxy('fetch_subtypes')); + .then(this.proxy('display_followers'), this.proxy('fetch_generic')) + .then(this.proxy('display_buttons')) + .then(this.proxy('fetch_subtypes')); }, /** Read on res.partner failed: fall back on a generic case @@ -109,10 +117,10 @@ openerp_mail_followers = function(session, mail) { fetch_generic: function (error, event) { var self = this; event.preventDefault(); - return this.ds_users.call('read', [this.session.uid, ['partner_id']]).pipe(function (results) { + return this.ds_users.call('read', [this.session.uid, ['partner_id']]).then(function (results) { var pid = results['partner_id'][0]; - self.message_is_follower = (_.indexOf(self.get('value'), pid) != -1); - }).pipe(self.proxy('display_generic')); + self.message_is_follower = (_.indexOf(self.value, pid) != -1); + }).then(self.proxy('display_generic')); }, _format_followers: function(count){ // TDE note: why redefining _t ? @@ -131,7 +139,7 @@ openerp_mail_followers = function(session, mail) { display_generic: function () { var self = this; var node_user_list = this.$('.oe_follower_list').empty(); - this.$('.oe_follower_title').html(this._format_followers(this.get('value').length)); + this.$('.oe_follower_title').html(this._format_followers(this.value.length)); }, /** Display the followers */ @@ -179,7 +187,7 @@ openerp_mail_followers = function(session, mail) { var subtype_list_ul = this.$('.oe_subtype_list').empty(); if (! this.message_is_follower) return; var context = new session.web.CompoundContext(this.build_context(), {}); - this.ds_model.call('message_get_subscription_data', [[this.view.datarecord.id], context]).pipe(this.proxy('display_subtypes')); + this.ds_model.call('message_get_subscription_data', [[this.view.datarecord.id], context]).then(this.proxy('display_subtypes')); }, /** Display subtypes: {'name': default, followed} */ @@ -206,7 +214,8 @@ openerp_mail_followers = function(session, mail) { $(record).attr('checked',false); }); var context = new session.web.CompoundContext(this.build_context(), {}); - return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context]).pipe(this.proxy('read_value')); + return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context]) + .then(this.proxy('read_value')); }, do_update_subscription: function (event) { @@ -220,8 +229,8 @@ openerp_mail_followers = function(session, mail) { }); var context = new session.web.CompoundContext(this.build_context(), {}); - return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], this.message_is_follower ? checklist:undefined, context]) - .pipe(this.proxy('read_value')); + return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], this.message_is_follower ? checklist : undefined, context]) + .then(this.proxy('read_value')); }, }); }; diff --git a/addons/mail/static/src/js/many2many_tags_email.js b/addons/mail/static/src/js/many2many_tags_email.js index 52dcbf7aeb5..cc701e8a585 100644 --- a/addons/mail/static/src/js/many2many_tags_email.js +++ b/addons/mail/static/src/js/many2many_tags_email.js @@ -46,7 +46,7 @@ instance.web.form.FieldMany2ManyTagsEmail = instance.web.form.FieldMany2ManyTags ["email", "=", false], ["notification_email_send", "in", ['all', 'comment']] ]], {context: this.build_context()}) - .pipe(function (record_ids) { + .then(function (record_ids) { // valid partner var valid_partner = _.difference(ids, record_ids); self.values = self.values.concat(valid_partner); diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index caa85cd3deb..2ba1733043a 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -1,8 +1,9 @@