From e46502580a5c9e19c31e15dd308e99307bbb68ed Mon Sep 17 00:00:00 2001 From: "Bharat Devnani (OpenERP)" Date: Wed, 25 Jul 2012 16:43:27 +0530 Subject: [PATCH 01/43] [IMP] changed the condition in def create_move of account_asset/account_asset.py bzr revid: bde@tinyerp.com-20120725111327-rd1se9de0yoj8wgg --- addons/account_asset/account_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 8cd73c60aa7..fe0ee8b3922 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -366,8 +366,6 @@ class account_asset_depreciation_line(osv.osv): currency_obj = self.pool.get('res.currency') created_move_ids = [] for line in self.browse(cr, uid, ids, context=context): - if currency_obj.is_zero(cr, uid, line.asset_id.currency_id, line.remaining_value): - can_close = True depreciation_date = line.asset_id.prorata and line.asset_id.purchase_date or time.strftime('%Y-%m-%d') period_ids = period_obj.find(cr, uid, depreciation_date, context=context) company_currency = line.asset_id.company_id.currency_id.id @@ -419,6 +417,8 @@ class account_asset_depreciation_line(osv.osv): }) self.write(cr, uid, line.id, {'move_id': move_id}, context=context) created_move_ids.append(move_id) + if currency_obj.is_zero(cr, uid, line.asset_id.currency_id, line.asset_id.value_residual): + can_close = True if can_close: asset_obj.write(cr, uid, [line.asset_id.id], {'state': 'close'}, context=context) return created_move_ids From d10c4397d6d66b9b686ad026e74c96b9e48ce089 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Wed, 7 Nov 2012 13:38:16 +0100 Subject: [PATCH 02/43] [FIX] bug 1023047 bzr revid: api@openerp.com-20121107123816-tdsol3doxnzz3hgt --- addons/project_timesheet/project_timesheet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index 439ff6d835d..4d807f104c3 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -199,8 +199,9 @@ class project_work(osv.osv): if amount_unit and 'amount' in amount_unit.get('value',{}): vals_line['amount'] = amount_unit['value']['amount'] - - self.pool.get('hr.analytic.timesheet').write(cr, uid, [line_id.id], vals_line, context=context) + state = self.pool.get('hr.analytic.timesheet').browse(cr,uid,line_id.id,context=context).sheet_id.state + if state != 'confirm' and state != 'done': + self.pool.get('hr.analytic.timesheet').write(cr, uid, [line_id.id], vals_line, context=context) return super(project_work,self).write(cr, uid, ids, vals, context) @@ -241,7 +242,6 @@ class task(osv.osv): if vals.get('project_id',False): project_obj = self.pool.get('project.project').browse(cr, uid, vals['project_id'], context=context) acc_id = project_obj.analytic_account_id.id - for task_obj in self.browse(cr, uid, ids, context=context): if len(task_obj.work_ids): for task_work in task_obj.work_ids: From 153ab3b020f26cc3d51db899d86486119fbc2032 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Wed, 7 Nov 2012 15:48:45 +0100 Subject: [PATCH 03/43] [FIX] domain resolution for Document button in project FORM lp bug: https://launchpad.net/bugs/1075436 fixed bzr revid: api@openerp.com-20121107144845-yi437u8w9fbcgs3u --- addons/project/project.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/addons/project/project.py b/addons/project/project.py index 62c1b34dbf9..2811f638f73 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -203,11 +203,7 @@ class project(osv.osv): def attachment_tree_view(self, cr, uid, ids, context): task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)]) - domain = [ - ('|', - '&', 'res_model', '=', 'project.project'), ('res_id', 'in', ids), - '&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids) - ] + domain = ['|', '&', ('res_model', '=', 'project.project'), ('res_id', 'in', ids), '&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)] res_id = ids and ids[0] or False return { 'name': _('Attachments'), From f5375d0eaadf2fa25a001cfa4f4d52201502364c Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Wed, 7 Nov 2012 17:13:13 +0100 Subject: [PATCH 04/43] [FIX] Analytic name_get fixed lp bug: https://launchpad.net/bugs/1076015 fixed bzr revid: api@openerp.com-20121107161313-c4np82ftmwcy0wvb --- addons/analytic/analytic.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 78c7e103946..d452b48155a 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -98,9 +98,13 @@ class account_analytic_account(osv.osv): def name_get(self, cr, uid, ids, context=None): res = [] - for id in ids: - elmt = self.browse(cr, uid, id, context=context) - res.append((id, self._get_one_full_name(elmt))) + if isinstance(ids,list): + for id in ids: + elmt = self.browse(cr, uid, id, context=context) + res.append((id, self._get_one_full_name(elmt))) + else: + elmt = self.browse(cr, uid, ids, context=context) + res.append((ids, self._get_one_full_name(elmt))) return res def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None): From 02bac97be9d79cd0b55d98bf6c6534fae1370809 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Thu, 8 Nov 2012 09:25:40 +0100 Subject: [PATCH 05/43] [FIX] Analytic lp bug: https://launchpad.net/bugs/1075436 fixed bzr revid: api@openerp.com-20121108082540-026nmjhrhyiuw710 --- addons/analytic/analytic.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index d452b48155a..31732708322 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -98,13 +98,11 @@ class account_analytic_account(osv.osv): def name_get(self, cr, uid, ids, context=None): res = [] - if isinstance(ids,list): - for id in ids: - elmt = self.browse(cr, uid, id, context=context) - res.append((id, self._get_one_full_name(elmt))) - else: - elmt = self.browse(cr, uid, ids, context=context) - res.append((ids, self._get_one_full_name(elmt))) + full_ids = [] + full_ids.append(ids) + for id in full_ids: + elmt = self.browse(cr, uid, id, context=context) + res.append((id, self._get_one_full_name(elmt))) return res def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None): From 784133eac6583f176d7ba29e48fe770f6863fc03 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Thu, 8 Nov 2012 09:48:46 +0100 Subject: [PATCH 06/43] [FIX] Project_timesheet lp bug: https://launchpad.net/bugs/1023047 fixed bzr revid: api@openerp.com-20121108084846-xv350qki85cd5ouq --- addons/project_timesheet/project_timesheet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index 4d807f104c3..3f85e5c2e79 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -199,8 +199,7 @@ class project_work(osv.osv): if amount_unit and 'amount' in amount_unit.get('value',{}): vals_line['amount'] = amount_unit['value']['amount'] - state = self.pool.get('hr.analytic.timesheet').browse(cr,uid,line_id.id,context=context).sheet_id.state - if state != 'confirm' and state != 'done': + if line_id.sheet_id.state in ['confirm' 'done']: self.pool.get('hr.analytic.timesheet').write(cr, uid, [line_id.id], vals_line, context=context) return super(project_work,self).write(cr, uid, ids, vals, context) From d37a79d0da41c4dd9e537bf18f2aa8ee19c95325 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Thu, 8 Nov 2012 10:19:48 +0100 Subject: [PATCH 07/43] [FIX] Analytic name_get fixed lp bug: https://launchpad.net/bugs/1076015 fixed bzr revid: api@openerp.com-20121108091948-8ynn3bn8pjj554q8 --- addons/analytic/analytic.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 31732708322..8dd73f6e767 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -99,7 +99,10 @@ class account_analytic_account(osv.osv): def name_get(self, cr, uid, ids, context=None): res = [] full_ids = [] - full_ids.append(ids) + if isinstance(ids,list): + full_ids.extend(ids) + else: + full_ids.append(ids) for id in full_ids: elmt = self.browse(cr, uid, id, context=context) res.append((id, self._get_one_full_name(elmt))) From f76ac922421727ca67ed7ade636bb36fda081228 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Wed, 14 Nov 2012 16:01:33 +0100 Subject: [PATCH 08/43] [FIX]fix problem when iterating on several ids in create_move (value_residual was not recomputed) bzr revid: csn@openerp.com-20121114150133-8hvrdkrwor7v9i2m --- addons/account_asset/account_asset.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 80be8f3ce91..a44226e76c8 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -377,6 +377,7 @@ class account_asset_depreciation_line(osv.osv): move_line_obj = self.pool.get('account.move.line') currency_obj = self.pool.get('res.currency') created_move_ids = [] + asset_ids = [] for line in self.browse(cr, uid, ids, context=context): depreciation_date = time.strftime('%Y-%m-%d') period_ids = period_obj.find(cr, uid, depreciation_date, context=context) @@ -407,8 +408,8 @@ class account_asset_depreciation_line(osv.osv): 'period_id': period_ids and period_ids[0] or False, 'journal_id': journal_id, 'partner_id': partner_id, - 'currency_id': company_currency <> current_currency and current_currency or False, - 'amount_currency': company_currency <> current_currency and - sign * line.amount or 0.0, + 'currency_id': company_currency != current_currency and current_currency or False, + 'amount_currency': company_currency != current_currency and - sign * line.amount or 0.0, 'date': depreciation_date, }) move_line_obj.create(cr, uid, { @@ -421,18 +422,19 @@ class account_asset_depreciation_line(osv.osv): 'period_id': period_ids and period_ids[0] or False, 'journal_id': journal_id, 'partner_id': partner_id, - 'currency_id': company_currency <> current_currency and current_currency or False, - 'amount_currency': company_currency <> current_currency and sign * line.amount or 0.0, + 'currency_id': company_currency != current_currency and current_currency or False, + 'amount_currency': company_currency != current_currency and sign * line.amount or 0.0, 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id, 'date': depreciation_date, 'asset_id': line.asset_id.id }) self.write(cr, uid, line.id, {'move_id': move_id}, context=context) created_move_ids.append(move_id) - if currency_obj.is_zero(cr, uid, line.asset_id.currency_id, line.asset_id.value_residual): - can_close = True - if can_close: - asset_obj.write(cr, uid, [line.asset_id.id], {'state': 'close'}, context=context) + asset_ids.append(line.asset_id.id) + # we re-evaluate the assets to determine whether we can close them + for asset in asset_obj.browse(cr, uid, list(set(asset_ids)), context=context): + if currency_obj.is_zero(cr, uid, asset.currency_id, asset.value_residual): + asset.write({'state': 'close'}) return created_move_ids account_asset_depreciation_line() From 739775253f2d0f9e4fc9bdfd197369a14d72223a Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 15 Nov 2012 10:21:49 +0100 Subject: [PATCH 09/43] [FIX] web: FieldOne2ManyBinaryMultiFiles fix the loading for default ids files list bzr revid: chm@openerp.com-20121115092149-lsbxb9ougxwkkktn --- addons/web/static/src/js/view_form.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 867569e2ea5..1be42849a96 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -5017,6 +5017,19 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel this._super(this); this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change ); }, + set_value: function(value_) { + var self = this; + if (value_ && typeof value_[0] != 'object') { + this.ds_file.call('read', [value_, ['name', 'datas_fname']]).done(function(data) { + _.each(data, function (val) { + val.no_unlink = true; + }); + self.set_value(data); + }); + } else { + this._super(value_); + } + }, get_value: function() { return _.map(this.get('value'), function (value) { return commands.link_to( value.id ); }); }, @@ -5111,7 +5124,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel if(file_id != this.get('value')[i].id){ files.push(this.get('value')[i]); } - else { + else if(!this.get('value')[i].no_unlink) { this.ds_file.unlink([file_id]); } } From edb1e07948416d12351dbc9e5d4b9896a53f0de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 15 Nov 2012 10:53:09 +0100 Subject: [PATCH 10/43] [FIX] composer: default content subtype is html (note that plaintext management will be removed asap), name_get is now using an id list instead of a single id. bzr revid: tde@openerp.com-20121115095309-zu4m6nzndlpmfe5b --- addons/mail/wizard/mail_compose_message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 45082b9be4d..839a3a6970e 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -114,7 +114,7 @@ class mail_compose_message(osv.TransientModel): _defaults = { 'composition_mode': 'comment', - 'content_subtype': lambda self, cr, uid, ctx={}: 'plain', + 'content_subtype': lambda self, cr, uid, ctx={}: 'html', 'body_text': lambda self, cr, uid, ctx={}: False, 'body': lambda self, cr, uid, ctx={}: '', 'subject': lambda self, cr, uid, ctx={}: False, @@ -135,7 +135,7 @@ class mail_compose_message(osv.TransientModel): related to. :param int res_id: id of the document record this mail is related to """ - doc_name_get = self.pool.get(model).name_get(cr, uid, res_id, context=context) + doc_name_get = self.pool.get(model).name_get(cr, uid, [res_id], context=context) if doc_name_get: record_name = doc_name_get[0][1] else: From 71c2be227cd1a8188db6f961d7d358099873e37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 15 Nov 2012 10:53:29 +0100 Subject: [PATCH 11/43] [IMP] mail: updated tests to fit the new composer behavior. bzr revid: tde@openerp.com-20121115095329-whh6z7ckto87yven --- addons/mail/tests/test_mail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py index 1cdb966ccbc..02b55cef9f7 100644 --- a/addons/mail/tests/test_mail.py +++ b/addons/mail/tests/test_mail.py @@ -463,7 +463,8 @@ class test_mail(test_mail_mockup.TestMailMockups): # 1. Comment group_pigs with body_text and subject compose_id = mail_compose.create(cr, uid, {'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]}, - {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id}) + {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id, + 'default_content_subtype': 'plaintext'}) compose = mail_compose.browse(cr, uid, compose_id) # Test: mail.compose.message: composition_mode, model, res_id self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode') From 25c684b1712796fad879596d19fa27cafbe03846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 15 Nov 2012 10:54:00 +0100 Subject: [PATCH 12/43] [FIX] Chatter: fixed posting on user/partner. Cleaned a bit default options of mail.js. bzr revid: tde@openerp.com-20121115095400-u7h6rw8ltp0xprtt --- addons/mail/mail_thread_view.xml | 2 +- addons/mail/res_partner.py | 3 ++- addons/mail/res_users.py | 11 ++++------- addons/mail/static/src/js/mail.js | 8 ++------ 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml index cdeade04d84..3a37218c813 100644 --- a/addons/mail/mail_thread_view.xml +++ b/addons/mail/mail_thread_view.xml @@ -7,7 +7,7 @@ mail.message { 'default_model': 'res.users', - 'default_res_id': uid + 'default_res_id': uid, } Date: Thu, 15 Nov 2012 11:05:33 +0100 Subject: [PATCH 13/43] [FIX] Chatter: fixed rpely to a lonely message, that was badly indented. bzr revid: tde@openerp.com-20121115100533-ly99op6fpfrzp9jw --- addons/mail/static/src/js/mail.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 75852a9d9b1..37090543b8c 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -598,16 +598,15 @@ openerp.mail = function (session) { this.parent_thread.context ]).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; + var hread = thread.parent_message.parent_thread; } + var root = thread == self.options.root_thread; // create object and attach to the thread object 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 ); + thread.insert_message( message, root ? undefined : self.$el, root ); if (thread.parent_message) { self.$el.remove(); self.parent_thread.compose_message = null; @@ -1272,7 +1271,7 @@ openerp.mail = function (session) { * The sort is define by the thread_level (O for newer on top). * @param : {object} ThreadMessage object */ - insert_message: function (message, dom_insert_after) { + insert_message: function (message, dom_insert_after, prepend) { var self=this; if (this.options.show_compact_message > this.thread_level) { this.instantiate_compose_message(); @@ -1284,6 +1283,8 @@ openerp.mail = function (session) { if (dom_insert_after) { message.insertAfter(dom_insert_after); + }if (prepend) { + message.prependTo(self.$el); } else { message.appendTo(self.$el); } From bd494ad7f41862662950d813f6b4aaea94277526 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 15 Nov 2012 11:13:29 +0100 Subject: [PATCH 14/43] [FIX] project_issue: missing module prefix when updating project demo data Fixes test breakage by FP - commit 8045 revision-id: fp@openerp.com-20121114224452-4djljq2ny7f1lumx bzr revid: odo@openerp.com-20121115101329-3d2epoctqwkq9mlq --- addons/project_issue/project_issue_demo.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/project_issue/project_issue_demo.xml b/addons/project_issue/project_issue_demo.xml index 86e4c59b571..63fa37c84c3 100644 --- a/addons/project_issue/project_issue_demo.xml +++ b/addons/project_issue/project_issue_demo.xml @@ -2,19 +2,19 @@ - + - + - + - + - + From d530905b93493430aaea0d359a52140659490f1d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 15 Nov 2012 11:22:23 +0100 Subject: [PATCH 15/43] [IMP] Add default filter on groups bzr revid: fp@tinyerp.com-20121115102223-i2jgjkgqf9pxez08 --- addons/mail/mail_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index d4c9c565bf3..b163882577b 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -130,7 +130,7 @@ class mail_group(osv.Model): params = { 'search_view_id': search_ref and search_ref[1] or False, 'domain': [('model', '=', 'mail.group'), ('res_id', '=', mail_group_id)], - 'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id}, + 'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id, 'search_default_message_unread': True}, 'res_model': 'mail.message', 'thread_level': 1, } From 8fe036423c78467c9edce38abbdd0acbf60d07d9 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 15 Nov 2012 11:25:22 +0100 Subject: [PATCH 16/43] [FIX] show of image groups bzr revid: fp@tinyerp.com-20121115102522-jquurlypugesxu1e --- addons/mail/static/src/css/mail_group.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/static/src/css/mail_group.css b/addons/mail/static/src/css/mail_group.css index 467bc53d80f..c1cefee9070 100644 --- a/addons/mail/static/src/css/mail_group.css +++ b/addons/mail/static/src/css/mail_group.css @@ -52,7 +52,7 @@ border-collapse: separate; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); - box-shadow: 0 1px 4px 3px rgba(0, 0, 0, 0.4); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); } From d3cf004baeba4e1d0b26a6d4f0ce1cb0e2138575 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 15 Nov 2012 11:27:41 +0100 Subject: [PATCH 17/43] [IMP] web_linkedin: adapted to new linkedin api bzr revid: nicolas.vanhoren@openerp.com-20121115102741-9vzb8qprvrh1lsmz --- addons/web_linkedin/static/src/js/linkedin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web_linkedin/static/src/js/linkedin.js b/addons/web_linkedin/static/src/js/linkedin.js index ec58c3736f4..d6237e48d51 100644 --- a/addons/web_linkedin/static/src/js/linkedin.js +++ b/addons/web_linkedin/static/src/js/linkedin.js @@ -20,7 +20,7 @@ openerp.web_linkedin = function(instance) { var tag = document.createElement('script'); tag.type = 'text/javascript'; tag.src = "http://platform.linkedin.com/in.js"; - tag.innerHTML = 'api_key : ' + self.api_key + '\nauthorize : true'; + tag.innerHTML = 'api_key : ' + self.api_key + '\nauthorize : true\nscope: r_network r_contactinfo'; document.getElementsByTagName('head')[0].appendChild(tag); self.linkedin_added = true; $(tag).load(function() { @@ -107,7 +107,7 @@ openerp.web_linkedin = function(instance) { } to_change.website = entity.websiteUrl; to_change.phone = false; - _.each(entity.locations.values || [], function(el) { + _.each((entity.locations || {}).values || [], function(el) { to_change.phone = el.contactInfo.phone1; }); var children_def = $.Deferred(); From 29e0ab965f899f6c68681640bed4a658177d59f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 15 Nov 2012 11:34:12 +0100 Subject: [PATCH 18/43] [IMP] res_users: removed hack about redirecting the fields view get. Added a simplified contact form the the user, used for example when viewing a salesman profile. bzr revid: tde@openerp.com-20121115103412-jl0o40t8d3d76ysd --- openerp/addons/base/res/res_users.py | 11 ------ openerp/addons/base/res/res_users_view.xml | 43 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index f975d39b300..3aee2ed630b 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -250,17 +250,6 @@ class res_users(osv.osv): 'image': lambda self, cr, uid, ctx={}: self.pool.get('res.partner')._get_default_image(cr, uid, False, ctx, colorize=True), } - def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): - """ Override of res.users fields_view_get. - - if the view is specified: resume with normal behavior - - else: the default view is overrided and redirected to the partner - view - """ - #made a lot of views crash because methods of open chatter are not available on users - #if not view_id and view_type == 'form': - # return self.pool.get('res.partner').fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu) - return super(res_users, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu) - # User can write on a few of his own fields (but not his groups for example) SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz'] # User can read a few of his own fields diff --git a/openerp/addons/base/res/res_users_view.xml b/openerp/addons/base/res/res_users_view.xml index cfab4b7d341..b5c88e4c24b 100644 --- a/openerp/addons/base/res/res_users_view.xml +++ b/openerp/addons/base/res/res_users_view.xml @@ -83,6 +83,49 @@ + + res.users.simplified.form + res.users + 1 + +
+ + +
+
+
+

+ +

+ +
+ + + + + + + + + + +
+
+
+
res.users.form res.users From 15b23a76aeded2cff747d1dedaeab6ae6357eb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 15 Nov 2012 11:35:01 +0100 Subject: [PATCH 19/43] [REM] Chatter: removed unused template in xml. bzr revid: tde@openerp.com-20121115103501-wt837acx8kj1ycht --- addons/mail/static/src/xml/mail.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 2ba1733043a..89fbe45dc36 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -95,14 +95,6 @@ - -
- - This email is private. - I wrote for contacts and all my followers. -
-
- From c6b5ca0a078c83f211dc469f3ca764400f3586e6 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 15 Nov 2012 11:39:47 +0100 Subject: [PATCH 20/43] [FIX] typo in a message bzr revid: fp@tinyerp.com-20121115103947-zre3ja8ghnojesd1 --- addons/mail/data/mail_group_data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/data/mail_group_data.xml b/addons/mail/data/mail_group_data.xml index 6d3e5395eda..64d989f9e9d 100644 --- a/addons/mail/data/mail_group_data.xml +++ b/addons/mail/data/mail_group_data.xml @@ -20,7 +20,7 @@ Welcome to OpenERP! Your homepage is a summary of messages you received and key information about documents you follow.

-The top menu bar contains all applications you installed. You can use this Settings menu to install more applications, activate others features or give access to new users.

+The top menu bar contains all applications you installed. You can use the Settings menu to install more applications, activate others features or give access to new users.

To setup your preferences (name, email signature, avatar), click on the top right corner.

]]>
From c2667f13adadbe9f0cbb0f65c7283dd5b1a71dfa Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 15 Nov 2012 11:43:45 +0100 Subject: [PATCH 21/43] [IMP] Inverting personnal tasks and notes importancies bzr revid: fp@tinyerp.com-20121115104345-1dkpg3rrk2j84wnj --- addons/note/__openerp__.py | 1 + addons/project_gtd/__openerp__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/note/__openerp__.py b/addons/note/__openerp__.py index d87ee70086d..797270637e2 100644 --- a/addons/note/__openerp__.py +++ b/addons/note/__openerp__.py @@ -38,6 +38,7 @@ Notes can be found in the 'Home' menu. 'author': 'OpenERP SA', 'website': 'http://openerp.com', 'summary': 'Sticky notes, Collaborative, Memos', + 'sequence': 9, 'depends': [ 'mail', ], diff --git a/addons/project_gtd/__openerp__.py b/addons/project_gtd/__openerp__.py index 441f8859dc6..c99535f63fd 100644 --- a/addons/project_gtd/__openerp__.py +++ b/addons/project_gtd/__openerp__.py @@ -24,7 +24,7 @@ 'name': 'Todo Lists', 'version': '1.0', 'category': 'Project Management', - 'sequence': 9, + 'sequence': 100, 'summary': 'Personal Tasks, Contexts, Timeboxes', 'description': """ Implement concepts of the "Getting Things Done" methodology From 6227e4b5c04b9ee861511a45f7b50806e27cf33c Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 15 Nov 2012 13:03:31 +0100 Subject: [PATCH 22/43] [FIX] CrashManager do not have destroy() method bzr revid: chs@openerp.com-20121115120331-ltujwzobh19muygl --- addons/web/static/src/js/chrome.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 2753cb45d1e..f5fc7b8f632 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -190,7 +190,14 @@ instance.web.Dialog = instance.web.Widget.extend({ }); instance.web.CrashManager = instance.web.Class.extend({ + init: function() { + this.active = true; + }, + rpc_error: function(error) { + if (!this.active) { + return; + } if (error.data.fault_code) { var split = ("" + error.data.fault_code).split('\n')[0].split(' -- '); if (split.length > 1) { @@ -205,6 +212,9 @@ instance.web.CrashManager = instance.web.Class.extend({ } }, show_warning: function(error) { + if (!this.active) { + return; + } instance.web.dialog($('
' + QWeb.render('CrashManager.warning', {error: error}) + '
'), { title: "OpenERP " + _.str.capitalize(error.type), buttons: [ @@ -213,7 +223,9 @@ instance.web.CrashManager = instance.web.Class.extend({ }); }, show_error: function(error) { - var self = this; + if (!this.active) { + return; + } var buttons = {}; buttons[_t("Ok")] = function() { $(this).dialog("close"); @@ -642,7 +654,7 @@ instance.web.client_actions.add("login", "instance.web.Login"); instance.web.redirect = function(url, wait) { // Dont display a dialog if some xmlhttprequest are in progress if (instance.client && instance.client.crashmanager) { - instance.client.crashmanager.destroy(); + instance.client.crashmanager.active = false; } var wait_server = function() { @@ -658,7 +670,7 @@ instance.web.redirect = function(url, wait) { } else { window.location = url; } -} +}; /** * Client action to reload the whole interface. From 81d4467c8fb4e15129d5dd6b8242fa1a8d4e5562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 15 Nov 2012 13:24:06 +0100 Subject: [PATCH 23/43] [REV] mail.compose.message using email_template: _reopen is back, as the spec of a wizard should be that hitting a button close it. Therefore _reopen is necessaru to reopen the wizard with its previous value, to continue working on it. bzr revid: tde@openerp.com-20121115122406-d88ozw5iclsqdyff --- .../wizard/mail_compose_message.py | 28 ++++++++++++++----- .../wizard/mail_compose_message_view.xml | 3 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 129c32722f5..ba4cef83bdb 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -25,6 +25,21 @@ from osv import osv from osv import fields +def _reopen(self, res_id, model): + return {'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'view_type': 'form', + 'res_id': res_id, + 'res_model': self._name, + 'target': 'new', + # save original model in context, because selecting the list of available + # templates requires a model in context + 'context': { + 'default_model': model, + }, + } + + class mail_compose_message(osv.TransientModel): _inherit = 'mail.compose.message' @@ -42,10 +57,8 @@ class mail_compose_message(osv.TransientModel): else: model = context.get('default_model', context.get('active_model')) - if model: - record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context) - return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')] - return [] + record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context) + return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')] def default_get(self, cr, uid, fields, context=None): if context is None: @@ -109,7 +122,7 @@ class mail_compose_message(osv.TransientModel): onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])] onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])] record.write(onchange_res) - return True + return _reopen(self, record.id, record.model) def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None): """ onchange_use_template (values: True or False). If use_template is @@ -141,8 +154,9 @@ class mail_compose_message(osv.TransientModel): 'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])] } template_id = email_template.create(cr, uid, values, context=context) - record.write({'template_id': template_id, 'use_template': True}) - return True + # record.write({'template_id': template_id, 'use_template': True}) + record.write(record.onchange_template_id(True, template_id, record.composition_mode, record.model, record.res_id)['value']) + return _reopen(self, record.id, record.model) #------------------------------------------------------ # Wizard validation and send diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml index 0a530299d4a..5afaf9745e1 100644 --- a/addons/email_template/wizard/mail_compose_message_view.xml +++ b/addons/email_template/wizard/mail_compose_message_view.xml @@ -14,12 +14,11 @@ -
Use template +
Use template
- or + +
- 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 @@