diff --git a/addons/account/account_analytic_line.py b/addons/account/account_analytic_line.py
index 2117f6e49ea..f0a420f7496 100644
--- a/addons/account/account_analytic_line.py
+++ b/addons/account/account_analytic_line.py
@@ -39,7 +39,6 @@ class account_analytic_line(osv.osv):
}
_defaults = {
- 'date': fields.date.context_today,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
}
_order = 'date desc'
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index 16393f0ea49..b01fc974bef 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -865,8 +865,11 @@ class account_invoice(osv.osv):
self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
# I disabled the check_total feature
- #if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
- # raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
+ group_check_total_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'group_supplier_inv_check_total')[1]
+ group_check_total = self.pool.get('res.groups').browse(cr, uid, group_check_total_id, context=context)
+ if group_check_total and uid in [x.id for x in group_check_total.users]:
+ if (inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0)):
+ raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe encoded total does not match the computed total.'))
if inv.payment_term:
total_fixed = total_percent = 0
diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml
index 5ed373456f3..2f722d07aeb 100644
--- a/addons/account/account_invoice_view.xml
+++ b/addons/account/account_invoice_view.xml
@@ -186,6 +186,7 @@
+
@@ -279,8 +280,8 @@
diff --git a/addons/account/res_config.py b/addons/account/res_config.py
index 17df4377548..41121db20f7 100644
--- a/addons/account/res_config.py
+++ b/addons/account/res_config.py
@@ -120,6 +120,8 @@ class account_config_settings(osv.osv_memory):
'group_analytic_accounting': fields.boolean('Analytic accounting',
implied_group='analytic.group_analytic_accounting',
help="Allows you to use the analytic accounting."),
+ 'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices',
+ implied_group="account.group_supplier_inv_check_total"),
}
def _default_company(self, cr, uid, context=None):
diff --git a/addons/account/res_config_view.xml b/addons/account/res_config_view.xml
index 728eefe8078..d4bafca41be 100644
--- a/addons/account/res_config_view.xml
+++ b/addons/account/res_config_view.xml
@@ -220,6 +220,10 @@
+
+
+
+
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index 4b90e0c8ad5..cc44f866169 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -26,6 +26,11 @@
+
+ Check Total on supplier invoices
+
+
+
Account Entry
diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py
index f25318727df..e1caf190316 100644
--- a/addons/account_voucher/account_voucher.py
+++ b/addons/account_voucher/account_voucher.py
@@ -988,11 +988,11 @@ class account_voucher(osv.osv):
if amount_residual > 0:
account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
if not account_id:
- raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Expense Currency Rate' on the company, then create accounting entry for currency rate difference."))
+ raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
else:
account_id = line.voucher_id.company_id.income_currency_exchange_account_id
if not account_id:
- raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Income Currency Rate' on the company, then create accounting entry for currency rate difference."))
+ raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
# Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
# the receivable/payable account may have a secondary currency, which render this field mandatory
account_currency_id = company_currency <> current_currency and current_currency or False
diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml
index 4b02d697982..babe60b640e 100644
--- a/addons/account_voucher/account_voucher_view.xml
+++ b/addons/account_voucher/account_voucher_view.xml
@@ -109,8 +109,8 @@
-
+
diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml
index e1a48c6c535..6666ac77d2f 100644
--- a/addons/account_voucher/voucher_payment_receipt_view.xml
+++ b/addons/account_voucher/voucher_payment_receipt_view.xml
@@ -240,8 +240,8 @@
-
+
@@ -512,8 +512,8 @@
-
+
diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml
index bda9b9c2532..dd6917358a9 100644
--- a/addons/account_voucher/voucher_sales_purchase_view.xml
+++ b/addons/account_voucher/voucher_sales_purchase_view.xml
@@ -147,8 +147,8 @@
-
+
@@ -303,8 +303,8 @@
-
+
diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py
index 3cb98d81f43..fea0e330143 100644
--- a/addons/analytic/analytic.py
+++ b/addons/analytic/analytic.py
@@ -96,9 +96,29 @@ class account_analytic_account(osv.osv):
res[row['id']][field] = row[field]
return self._compute_level_tree(cr, uid, ids, child_ids, res, fields, context)
- def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
- res = self.name_get(cr, uid, ids)
- return dict(res)
+ 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)))
+ return res
+
+ def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None):
+ if context == None:
+ context = {}
+ res = {}
+ for elmt in self.browse(cr, uid, ids, context=context):
+ res[elmt.id] = self._get_one_full_name(elmt)
+ return res
+
+ def _get_one_full_name(self, elmt, level=6):
+ if level<=0:
+ return '...'
+ if elmt.parent_id:
+ parent_path = self._get_one_full_name(elmt.parent_id, level-1) + "/"
+ else:
+ parent_path = ''
+ return parent_path + elmt.name
def _child_compute(self, cr, uid, ids, name, arg, context=None):
result = {}
@@ -139,7 +159,7 @@ class account_analytic_account(osv.osv):
_columns = {
'name': fields.char('Account/Contract Name', size=128, required=True),
- 'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
+ 'complete_name': fields.function(_get_full_name, type='char', string='Full Account Name'),
'code': fields.char('Reference', size=24, select=True),
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True,
help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\
@@ -305,8 +325,15 @@ class account_analytic_line(osv.osv):
'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
}
+
+ def _get_default_date(self, cr, uid, context=None):
+ return fields.date.context_today(self, cr, uid, context=context)
+
+ def __get_default_date(self, cr, uid, context=None):
+ return self._get_default_date(cr, uid, context=context)
+
_defaults = {
- 'date': lambda *a: time.strftime('%Y-%m-%d'),
+ 'date': __get_default_date,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
'amount': 0.00
}
diff --git a/addons/analytic/analytic_view.xml b/addons/analytic/analytic_view.xml
index 8098279b3ea..2c6136f2629 100644
--- a/addons/analytic/analytic_view.xml
+++ b/addons/analytic/analytic_view.xml
@@ -56,8 +56,8 @@
-
+
diff --git a/addons/auth_openid/controllers/main.py b/addons/auth_openid/controllers/main.py
index 7ee562d4d29..be59b9ae5b5 100644
--- a/addons/auth_openid/controllers/main.py
+++ b/addons/auth_openid/controllers/main.py
@@ -22,6 +22,7 @@
import logging
import os
import tempfile
+import getpass
import urllib
import werkzeug.urls
@@ -43,7 +44,16 @@ from .. import utils
_logger = logging.getLogger(__name__)
oidutil.log = _logger.debug
-_storedir = os.path.join(tempfile.gettempdir(), 'openerp-auth_openid-store')
+def get_system_user():
+ """Return system user info string, such as USERNAME-EUID"""
+ info = getpass.getuser()
+ euid = getattr(os, 'geteuid', None) # Non available on some platforms
+ if euid is not None:
+ info = '%s-%d' % (info, euid())
+ return info
+
+_storedir = os.path.join(tempfile.gettempdir(),
+ 'openerp-auth_openid-%s-store' % get_system_user())
class GoogleAppsAwareConsumer(consumer.GenericConsumer):
def complete(self, message, endpoint, return_to):
diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py
index cbdc43b23af..bb432aa4d61 100644
--- a/addons/base_calendar/base_calendar.py
+++ b/addons/base_calendar/base_calendar.py
@@ -1151,7 +1151,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
context = {}
result = []
- for data in super(calendar_event, self).read(cr, uid, select, context=context):
+ for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'exdate', 'exrule', 'date'], context=context):
if not data['rrule']:
result.append(data['id'])
continue
diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml
index 5292ef3c3ed..8612d20be1f 100644
--- a/addons/base_calendar/crm_meeting_view.xml
+++ b/addons/base_calendar/crm_meeting_view.xml
@@ -219,8 +219,8 @@
-
+
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index a0dce6a7ea9..b6890a7de07 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -223,8 +223,8 @@
-
+
@@ -525,8 +525,8 @@
-
+
diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml
index 21b0d295476..9cb09e71be2 100644
--- a/addons/crm/crm_phonecall_view.xml
+++ b/addons/crm/crm_phonecall_view.xml
@@ -150,8 +150,8 @@
-
+
diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml
index ba46cc023ab..5e2034d6373 100644
--- a/addons/crm/crm_view.xml
+++ b/addons/crm/crm_view.xml
@@ -132,8 +132,8 @@
-
+
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml
index 0d9b5adef43..7d7d6459cba 100644
--- a/addons/crm_claim/crm_claim_view.xml
+++ b/addons/crm_claim/crm_claim_view.xml
@@ -179,8 +179,8 @@
-
+
diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py
index 69afd5bc167..7af05f38b0e 100644
--- a/addons/email_template/tests/test_mail.py
+++ b/addons/email_template/tests/test_mail.py
@@ -165,11 +165,8 @@ class test_message_compose(test_mail.TestMailMockups):
self.assertEqual(message_pigs.body, _body_html1, 'mail.message body on Pigs incorrect')
self.assertEqual(message_bird.body, _body_html2, 'mail.message body on Bird incorrect')
# Test: partner_ids: p_a_id (default) + 3 newly created partners
- message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
- message_bird_pids = [partner.id for partner in message_bird.partner_ids]
+ message_pigs_pids = [partner.id for partner in message_pigs.notified_partner_ids]
+ message_bird_pids = [partner.id for partner in message_bird.notified_partner_ids]
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
- self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
- self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
-
- self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
- self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')
+ self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of notified_partner_ids')
+ self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird notified_partner_ids incorrect')
diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml
index a33135ca098..05d0e8dffa1 100644
--- a/addons/email_template/wizard/mail_compose_message_view.xml
+++ b/addons/email_template/wizard/mail_compose_message_view.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml
index 27106ce6abc..3face47de52 100644
--- a/addons/event/event_view.xml
+++ b/addons/event/event_view.xml
@@ -204,8 +204,8 @@
-
+
@@ -486,8 +486,8 @@
-
+
diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml
index b4e2df3965e..c4a622d7a4f 100644
--- a/addons/hr/hr_view.xml
+++ b/addons/hr/hr_view.xml
@@ -359,8 +359,8 @@
-
+
diff --git a/addons/hr/res_config.py b/addons/hr/res_config.py
index 148499190fd..299c5ca68fe 100644
--- a/addons/hr/res_config.py
+++ b/addons/hr/res_config.py
@@ -42,7 +42,7 @@ class hr_config_settings(osv.osv_memory):
help ="""This installs the module hr_contract."""),
'module_hr_evaluation': fields.boolean('Organize employees periodic evaluation',
help ="""This installs the module hr_evaluation."""),
- 'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (will install the sale application)',
+ 'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (the sale application will be installed)',
help ="""This installs the module account_analytic_analysis, which will install sales management too."""),
'module_hr_payroll': fields.boolean('Manage payroll',
help ="""This installs the module hr_payroll."""),
diff --git a/addons/hr_evaluation/hr_evaluation_view.xml b/addons/hr_evaluation/hr_evaluation_view.xml
index 7164e7d9415..697ed6bacea 100644
--- a/addons/hr_evaluation/hr_evaluation_view.xml
+++ b/addons/hr_evaluation/hr_evaluation_view.xml
@@ -210,8 +210,8 @@
-
+
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml
index 1b8fd17dece..935dbb4da2b 100644
--- a/addons/hr_expense/hr_expense_view.xml
+++ b/addons/hr_expense/hr_expense_view.xml
@@ -139,8 +139,8 @@
-
+
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml
index 1808e216906..7ccace1c536 100644
--- a/addons/hr_holidays/hr_holidays_view.xml
+++ b/addons/hr_holidays/hr_holidays_view.xml
@@ -121,8 +121,8 @@
-
+
@@ -158,8 +158,8 @@
-
+
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index 08484889cf1..1028db60890 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -183,8 +183,8 @@
-
+
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
index 4e51864db51..a9276ba6984 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
@@ -79,7 +79,7 @@ class hr_timesheet_sheet(osv.osv):
if not new_user_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
- raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlaps!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
+ raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
@@ -187,7 +187,7 @@ class hr_timesheet_sheet(osv.osv):
_constraints = [
- (_sheet_date, 'You cannot have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
+ (_sheet_date, 'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
]
def action_set_to_draft(self, cr, uid, ids, *args):
@@ -223,16 +223,26 @@ class hr_timesheet_sheet(osv.osv):
hr_timesheet_sheet()
-
-class hr_timesheet_line(osv.osv):
- _inherit = "hr.analytic.timesheet"
+class account_analytic_line(osv.osv):
+ _inherit = "account.analytic.line"
def _get_default_date(self, cr, uid, context=None):
if context is None:
context = {}
- if 'date' in context:
- return context['date']
- return time.strftime('%Y-%m-%d')
+ #get the default date (should be: today)
+ res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
+ #if we got the dates from and to from the timesheet and if the default date is in between, we use the default
+ #but if the default isn't included in those dates, we use the date start of the timesheet as default
+ if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
+ if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
+ return res
+ return context.get('timesheet_date_from')
+ #if we don't get the dates from the timesheet, we return the default value from super()
+ return res
+
+
+class hr_timesheet_line(osv.osv):
+ _inherit = "hr.analytic.timesheet"
def _sheet(self, cursor, user, ids, name, args, context=None):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
@@ -278,9 +288,6 @@ class hr_timesheet_line(osv.osv):
},
),
}
- _defaults = {
- 'date': _get_default_date,
- }
def _check_sheet_state(self, cr, uid, ids, context=None):
if context is None:
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
index c41aae4c21f..ee5a329c800 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
@@ -103,7 +103,7 @@
-
+
@@ -303,18 +303,21 @@
+ context="{'search_default_sheet_id': [active_id]}"
+ id="act_hr_timesheet_sheet_sheet_by_account"
+ name="Timesheet by Account"
+ groups="base.group_hr_attendance"
+ res_model="hr_timesheet_sheet.sheet.account"
+ src_model="hr_timesheet_sheet.sheet"/>
+ context="{'search_default_sheet_id': [active_id]}"
+ id="act_hr_timesheet_sheet_sheet_by_day"
+ name="Timesheet by Day"
+ groups="base.group_hr_attendance"
+ res_model="hr_timesheet_sheet.sheet.day"
+ src_model="hr_timesheet_sheet.sheet"/>
+
hr.timesheet.sheet.tree
hr_timesheet_sheet.sheet
diff --git a/addons/idea/idea_view.xml b/addons/idea/idea_view.xml
index 5a1482830e1..5d9054bf4b7 100644
--- a/addons/idea/idea_view.xml
+++ b/addons/idea/idea_view.xml
@@ -79,8 +79,8 @@
-
+
diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py
index 27565bc4102..0c32ba27d6f 100644
--- a/addons/mail/__openerp__.py
+++ b/addons/mail/__openerp__.py
@@ -84,7 +84,6 @@ Main Features
'css': [
'static/src/css/mail.css',
'static/src/css/mail_group.css',
- 'static/src/css/mail_compose_message.css',
],
'js': [
'static/lib/jquery.expander/jquery.expander.js',
diff --git a/addons/mail/data/mail_demo.xml b/addons/mail/data/mail_demo.xml
index 7bda32c002b..bda93fc485d 100644
--- a/addons/mail/data/mail_demo.xml
+++ b/addons/mail/data/mail_demo.xml
@@ -8,6 +8,7 @@
Your monthly meal vouchers arrived. You can get them at Christine's office.
This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.
comment
+
+
+
-
-
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index 35603d2d720..e8cf2648f29 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -84,9 +84,16 @@ class mail_notification(osv.Model):
return False
def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
- """ TDE note: add a comment, verify method calls, because js seems obfuscated. """
+ """ Set a message and its child messages as (un)read for uid.
+
+ :param bool read: read / unread
+ """
+ # TDE note: use child_of or front-end send correct values ?
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
- notif_ids = self.search(cr, uid, [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)], context=context)
+ notif_ids = self.search(cr, uid, [
+ ('partner_id', '=', user_pid),
+ ('message_id', 'in', msg_ids)
+ ], context=context)
# all message have notifications: already set them as (un)read
if len(notif_ids) == len(msg_ids):
@@ -130,7 +137,8 @@ class mail_notification(osv.Model):
def _notify(self, cr, uid, msg_id, context=None):
""" Send by email the notification depending on the user preferences """
- context = context or {}
+ if context is None:
+ context = {}
# mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail'):
return True
@@ -140,9 +148,15 @@ class mail_notification(osv.Model):
if not notify_partner_ids:
return True
+ # add the context in the email
+ # TDE FIXME: commented, to be improved in a future branch
+ # quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
+
mail_mail = self.pool.get('mail.mail')
# add signature
body_html = msg.body
+ # if quote_context:
+ # body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
if signature:
body_html = tools.append_content_to_html(body_html, signature)
diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml
index 6268cd425b1..2028e8d1b18 100644
--- a/addons/mail/mail_group_view.xml
+++ b/addons/mail/mail_group_view.xml
@@ -82,9 +82,8 @@
-
+
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 8719d59bf04..bd0ade31b68 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -24,11 +24,17 @@ import tools
from email.header import decode_header
from openerp import SUPERUSER_ID
-from osv import osv, orm, fields
-from tools.translate import _
+from openerp.osv import osv, orm, fields
+from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
+try:
+ from mako.template import Template as MakoTemplate
+except ImportError:
+ _logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
+
+
""" Some tools for parsing / creating email fields """
def decode(text):
"""Returns unicode() string conversion of the the given encoded smtp header text"""
@@ -46,7 +52,7 @@ class mail_message(osv.Model):
_order = 'id desc'
_message_read_limit = 10
- _message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read',
+ _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
_message_read_more_limit = 1024
@@ -57,17 +63,14 @@ class mail_message(osv.Model):
return name[:self._message_record_name_length] + '...'
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
- """ Return the related document name, using name_get. It is included in
- a try/except statement, because if uid cannot read the related
- document, he should see a void string instead of crashing. """
+ """ Return the related document name, using name_get. It is done using
+ SUPERUSER_ID, to be sure to have the record name correctly stored. """
+ # TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
- if not message['model'] or not message['res_id']:
+ if not message.get('model') or not message.get('res_id'):
continue
- try:
- result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, uid, [message['res_id']], context=context)[0][1])
- except (orm.except_orm, osv.except_osv):
- pass
+ result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1])
return result
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
@@ -78,10 +81,10 @@ class mail_message(osv.Model):
notif_ids = notif_obj.search(cr, uid, [
('partner_id', 'in', [partner_id]),
('message_id', 'in', ids),
- ('read', '=', False)
+ ('read', '=', False),
], context=context)
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
- res[notif.message_id.id] = not notif.read
+ res[notif.message_id.id] = True
return res
def _search_to_read(self, cr, uid, obj, name, domain, context=None):
@@ -115,16 +118,21 @@ class mail_message(osv.Model):
], 'Type',
help="Message type: email for email message, notification for system "\
"message, comment for other messages such as user replies"),
- 'author_id': fields.many2one('res.partner', 'Author', required=True),
- 'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
+ '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',
+ 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'),
'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."),
'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='string',
- string='Message Record Name',
+ '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'),
'subject': fields.char('Subject'),
@@ -198,19 +206,33 @@ class mail_message(osv.Model):
:param dict message: read result of a mail.message
"""
+ # 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'])
+
has_voted = False
- if uid in message['vote_user_ids']:
+ if uid in message.get('vote_user_ids'):
has_voted = True
is_favorite = False
- if uid in message['favorite_user_ids']:
+ if uid in message.get('favorite_user_ids'):
is_favorite = True
+ is_private = True
+ if message.get('model') and message.get('res_id'):
+ is_private = False
+
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):
@@ -226,95 +248,106 @@ class mail_message(osv.Model):
'record_name': message['record_name'],
'subject': message['subject'],
'date': message['date'],
- 'author_id': message['author_id'],
- 'is_author': message['author_id'] and message['author_id'][0] == uid,
- # TDE note: is this useful ? to check
+ 'author_id': author_id,
+ 'is_author': is_author,
'partner_ids': partner_ids,
- 'parent_id': message['parent_id'] and message['parent_id'][0] or False,
- # TDE note: see with CHM about votes, how they are displayed (only number, or name_get ?)
- # vote: should only use number of votes
+ 'parent_id': False,
'vote_nb': len(message['vote_user_ids']),
'has_voted': has_voted,
- 'is_private': message['model'] and message['res_id'],
+ 'is_private': is_private,
'is_favorite': is_favorite,
'to_read': message['to_read'],
}
- def _message_read_expandable(self, cr, uid, message_list, read_messages,
- message_loaded_ids=[], domain=[], context=None, parent_id=False, limit=None):
- """ Create the expandable message for all parent message read
- this function is used by message_read
+ 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):
+ """ Create expandables for message_read, to load new messages.
+ 1. get the expandable for new threads
+ if display is flat (thread_level == 0):
+ fetch message_ids < min(already displayed ids), because we
+ want a flat display, ordered by id
+ else:
+ fetch message_ids that are not childs of already displayed
+ messages
+ 2. get the expandables for new messages inside threads if display
+ is not flat
+ for each thread header, search for its childs
+ for each hole in the child list based on message displayed,
+ create an expandable
- :param list message_list: list of messages given by message_read to
- which we have to add expandables
+ :param list message_list: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
+ :return bool: True
"""
- # sort for group items / TDE: move to message_read
- # result = sorted(result, key=lambda k: k['id'])
- tree_not = []
- # expandable for not show message
+ def _get_expandable(domain, message_nb, parent_id, id, model):
+ 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,
+ }
+
+ # all_not_loaded_ids = []
id_list = sorted(read_messages.keys())
+ if not id_list:
+ return message_list
+
+ # 1. get the expandable for new threads
+ if thread_level == 0:
+ exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))]
+ else:
+ exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)]
+ 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))
+
+ # 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]
+ # 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'):
+ continue
+
# TDE note: check search is correctly implemented in mail.message
not_loaded_ids = self.search(cr, uid, [
- ('parent_id', '=', message['id']),
+ ('id', 'child_of', message['id']),
('id', 'not in', message_loaded_ids),
], context=context, limit=self._message_read_more_limit)
- # group childs not read
- id_min = None
- id_max = None
- nb = 0
+ if not not_loaded_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):
nb += 1
- if id_min == None or id_min > not_loaded_id:
+ if id_min > not_loaded_id:
id_min = not_loaded_id
- if id_max == None or id_max < not_loaded_id:
+ if id_max < not_loaded_id:
id_max = not_loaded_id
- tree_not.append(not_loaded_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
else:
- if nb > 0:
- message_list.append({
- 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
- 'nb_messages': nb,
- 'type': 'expandable',
- 'parent_id': message_id,
- 'id': id_min,
- 'model': message['model']
- })
- id_min = None
- id_max = None
- nb = 0
+ id_min, id_max, nb = max(not_loaded_ids), 0, 0
if nb > 0:
- message_list.append({
- 'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
- 'nb_messages': nb,
- 'type': 'expandable',
- 'parent_id': message_id,
- 'id': id_min,
- 'model': message['model'],
- })
+ 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')))
- for msg_id in read_messages.keys() + tree_not:
- message_loaded_ids.append(msg_id)
+ # message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
- # expandable for limit max
- ids = self.search(cr, uid, domain + [('id', 'not in', message_loaded_ids)], context=context, limit=1)
- if len(ids) > 0:
- message_list.append({
- 'domain': domain,
- 'nb_messages': 0,
- 'type': 'expandable',
- 'parent_id': parent_id,
- 'id': -1,
- 'max_limit': True,
- })
-
- return message_list
+ return True
def _get_parent(self, cr, uid, message, context=None):
""" Tools method that tries to get the parent of a mail.message. If
@@ -331,57 +364,70 @@ class mail_message(osv.Model):
except (orm.except_orm, osv.except_osv):
return False
- def message_read(self, cr, uid, ids=False, domain=[], message_loaded_ids=[], context=None, parent_id=False, limit=None):
- """ Read messages from mail.message, and get back a structured tree
- of messages to be displayed as discussion threads. If IDs is set,
+ 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.
- After having fetch messages, their parents & child will be added to obtain
- well formed threads.
+ After having fetch messages, their ancestors will be added to obtain
+ well formed threads, if uid has access to them.
- TDE note: update this comment after final method implementation
+ After reading the messages, expandable messages are added in the
+ message list (see ``_message_read_add_expandables``). It consists
+ in messages holding the 'read more' data: number of messages to
+ read, domain to apply.
- :param domain: optional domain for searching ids
- :param limit: number of messages to fetch
- :param parent_id: if parent_id reached, stop searching for
- further parents
- :return list: list of trees of messages
+ :param list ids: optional IDs to fetch
+ :param list domain: optional domain for searching ids if ids not set
+ :param list message_unload_ids: optional ids we do not want to fetch,
+ because i.e. they are already displayed somewhere
+ :param int parent_id: context of parent_id
+ - if parent_id reached when adding ancestors, stop going further
+ in the ancestor search
+ - if set in flat mode, ancestor_id is set to parent_id
+ :param int limit: number of messages to fetch, before adding the
+ ancestors and expandables
+ :return list: list of message structure for the Chatter widget
"""
- if message_loaded_ids:
- domain += [('id', 'not in', message_loaded_ids)]
+ # 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_list = []
- # specific IDs given: fetch those ids and return directly the message list
- if ids:
- for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
- message_list.append(self._message_get_dict(cr, uid, message, context=context))
- message_list = sorted(message_list, key=lambda k: k['id'])
- return message_list
-
- # TDE FIXME: check access rights on search are implemented for mail.message
- # fetch messages according to the domain, add their parents if uid has access to
- ids = self.search(cr, uid, domain, context=context, limit=limit)
+ # 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):
- # if not in tree and not in message_loded list
- if not read_messages.get(message.get('id')) and message.get('id') not in message_loaded_ids:
- read_messages[message.get('id')] = message
+ 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))
- # get all parented message if the user have the access
+ # 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:
- if not read_messages.get(parent.get('id')) and parent.get('id') not in message_loaded_ids:
- read_messages[parent.get('id')] = parent
- message_list.append(self._message_get_dict(cr, uid, parent, context=context))
- parent = self._get_parent(cr, uid, parent, context=context)
+ 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))
# get the child expandable messages for the tree
message_list = sorted(message_list, key=lambda k: k['id'])
- message_list = self._message_read_expandable(cr, uid, message_list, read_messages,
- message_loaded_ids=message_loaded_ids, domain=domain, context=context, parent_id=parent_id, limit=limit)
+ 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)
- # message_list = sorted(message_list, key=lambda k: k['id'])
return message_list
# TDE Note: do we need this ?
@@ -394,7 +440,7 @@ class mail_message(osv.Model):
# return attachment_list
#------------------------------------------------------
- # Email api
+ # mail_message internals
#------------------------------------------------------
def init(self, cr):
@@ -402,23 +448,75 @@ class mail_message(osv.Model):
if not cr.fetchone():
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
+ def _search(self, cr, uid, args, offset=0, limit=None, order=None,
+ context=None, count=False, access_rights_uid=None):
+ """ Override that adds specific access rights of mail.message, to remove
+ ids uid could not see according to our custom rules. Please refer
+ to check_access_rule for more details about those rules.
+
+ After having received ids of a classic search, keep only:
+ - if author_id == pid, uid is the author, OR
+ - a notification (id, pid) exists, uid has been notified, OR
+ - uid have read access to the related document is model, res_id
+ - 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)
+ # Perform a super with count as False, to have the ids, not a counter
+ ids = super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
+ context=context, count=False, access_rights_uid=access_rights_uid)
+ if not ids and count:
+ return 0
+ elif not ids:
+ return ids
+
+ pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
+ author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
+ model_ids = {}
+
+ messages = super(mail_message, self).read(cr, uid, ids, ['author_id', 'model', 'res_id', 'notified_partner_ids'], context=context)
+ for message in messages:
+ if message.get('author_id') and message.get('author_id')[0] == pid:
+ author_ids.add(message.get('id'))
+ elif pid in message.get('notified_partner_ids'):
+ partner_ids.add(message.get('id'))
+ elif message.get('model') and message.get('res_id'):
+ model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id'))
+
+ model_access_obj = self.pool.get('ir.model.access')
+ for doc_model, doc_dict in model_ids.iteritems():
+ if not model_access_obj.check(cr, uid, doc_model, 'read', False):
+ continue
+ doc_ids = doc_dict.keys()
+ allowed_doc_ids = self.pool.get(doc_model).search(cr, uid, [('id', 'in', doc_ids)], context=context)
+ allowed_ids |= set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
+
+ final_ids = author_ids | partner_ids | allowed_ids
+ if count:
+ return len(final_ids)
+ else:
+ return list(final_ids)
+
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Access rules of mail.message:
- read: if
- - notification exist (I receive pushed message) OR
- - author_id = pid (I am the author) OR
- - I can read the related document if res_model, res_id
- - Otherwise: raise
+ - author_id == pid, uid is the author, OR
+ - mail_notification (id, pid) exists, uid has been notified, OR
+ - uid have read access to the related document if model, res_id
+ - otherwise: raise
- create: if
- - I am in the document message_follower_ids OR
- - I can write on the related document if res_model, res_id OR
- - I create a private message (no model, no res_id)
- - Otherwise: raise
+ - no model, no res_id, I create a private message
+ - pid in message_follower_ids if model, res_id OR
+ - uid have write access on the related document if model, res_id, OR
+ - otherwise: raise
- write: if
- - I can write on the related document if res_model, res_id
+ - uid has write access on the related document if model, res_id
- Otherwise: raise
- unlink: if
- - I can write on the related document if res_model, res_id
+ - uid has write access on the related document if model, res_id
- Otherwise: raise
"""
if uid == SUPERUSER_ID:
@@ -428,15 +526,25 @@ class mail_message(osv.Model):
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
# Read mail_message.ids to have their values
- model_record_ids = {}
message_values = dict.fromkeys(ids)
+ model_record_ids = {}
cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
for id, rmod, rid, author_id in cr.fetchall():
message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
if rmod:
- model_record_ids.setdefault(rmod, set()).add(rid)
+ model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
- # Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field
+ # Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
+ if operation == 'read':
+ author_ids = [mid for mid, message in message_values.iteritems()
+ if message.get('author_id') and message.get('author_id') == partner_id]
+ elif operation == 'create':
+ author_ids = [mid for mid, message in message_values.iteritems()
+ if not message.get('model') and not message.get('res_id')]
+ else:
+ author_ids = []
+
+ # Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
not_obj = self.pool.get('mail.notification')
not_ids = not_obj.search(cr, SUPERUSER_ID, [
@@ -444,38 +552,24 @@ class mail_message(osv.Model):
('message_id', 'in', ids),
], context=context)
notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
- else:
- notified_ids = []
- # Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field
- if operation == 'read':
- author_ids = [mid for mid, message in message_values.iteritems()
- if message.get('author_id') and message.get('author_id') == partner_id]
- # Create: Check messages you create that are private messages -> ir.rule ?
elif operation == 'create':
- author_ids = [mid for mid, message in message_values.iteritems()
- if not message.get('model') and not message.get('res_id')]
- else:
- author_ids = []
-
- # Create: Check message_follower_ids
- if operation == 'create':
- doc_follower_ids = []
- for model, mids in model_record_ids.items():
+ notified_ids = []
+ for doc_model, doc_dict in model_record_ids.items():
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
- ('res_model', '=', model),
- ('res_id', 'in', list(mids)),
+ ('res_model', '=', doc_model),
+ ('res_id', 'in', list(doc_dict.keys())),
('partner_id', '=', partner_id),
], context=context)
fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
- doc_follower_ids += [mid for mid, message in message_values.iteritems()
- if message.get('res_model') == model and message.get('res_id') in fol_mids]
+ notified_ids += [mid for mid, message in message_values.iteritems()
+ if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
else:
- doc_follower_ids = []
+ notified_ids = []
# Calculate remaining ids, and related model/res_ids
model_record_ids = {}
- other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids))
+ other_ids = set(ids).difference(set(author_ids), set(notified_ids))
for id in other_ids:
if message_values[id]['res_model']:
model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
@@ -495,7 +589,7 @@ class mail_message(osv.Model):
if message.get('res_model') == model and message.get('res_id') in mids]
# Calculate remaining ids: if not void, raise an error
- other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids))
+ other_ids = other_ids - set(document_related_ids)
if not other_ids:
return
raise orm.except_orm(_('Access Denied'),
@@ -529,50 +623,123 @@ class mail_message(osv.Model):
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
return super(mail_message, self).unlink(cr, uid, ids, context=context)
- def _notify_followers(self, cr, uid, newid, message, context=None):
- """ Add the related record followers to the destination partner_ids.
+ def copy(self, cr, uid, id, default=None, context=None):
+ """ Overridden to avoid duplicating fields that are unique to each email """
+ if default is None:
+ default = {}
+ default.update(message_id=False, headers=False)
+ return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
+
+ #------------------------------------------------------
+ # Messaging API
+ #------------------------------------------------------
+
+ # TDE note: this code is not used currently, will be improved in a future merge, when quoted context
+ # will be added to email send for notifications. Currently only WIP.
+ MAIL_TEMPLATE = """
+ % if message:
+ ${display_message(message)}
+ % endif
+ % for ctx_msg in context_messages:
+ ${display_message(ctx_msg)}
+ % endfor
+ % if add_expandable:
+ ${display_expandable()}
+ % endif
+ ${display_message(header_message)}
+
+
+ <%def name="display_message(message)">
+
+ Subject: ${message.subject}
+ Body: ${message.body}
+
+ %def>
+
+ <%def name="display_expandable()">
+ This is an expandable.
+ %def>
+ """
+
+ def message_quote_context(self, cr, uid, id, context=None, limit=3, add_original=False):
"""
- partners_to_notify = set([])
- # message has no subtype_id: pure log message -> no partners, no one notified
- if not message.subtype_id:
- message.write({'partner_ids': [5]})
- return True
- # all partner_ids of the mail.message have to be notified
- if message.partner_ids:
- partners_to_notify |= set(partner.id for partner in message.partner_ids)
- # all followers of the mail.message document have to be added as partners and notified
- if message.model and message.res_id:
- fol_obj = self.pool.get("mail.followers")
- fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
- fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
- extra_notified = set(fol.partner_id.id for fol in fol_objs)
- missing_notified = extra_notified - partners_to_notify
- if missing_notified:
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
+ 1. message.parent_id = False: new thread, no quote_context
+ 2. get the lasts messages in the thread before message
+ 3. get the message header
+ 4. add an expandable between them
+
+ :param dict quote_context: options for quoting
+ :return string: html quote
+ """
+ add_expandable = False
+
+ message = self.browse(cr, uid, id, context=context)
+ if not message.parent_id:
+ return ''
+ context_ids = self.search(cr, uid, [
+ ('parent_id', '=', message.parent_id.id),
+ ('id', '<', message.id),
+ ], limit=limit, context=context)
+
+ if len(context_ids) >= limit:
+ add_expandable = True
+ context_ids = context_ids[0:-1]
+
+ context_ids.append(message.parent_id.id)
+ context_messages = self.browse(cr, uid, context_ids, context=context)
+ header_message = context_messages.pop()
+
+ try:
+ if not add_original:
+ message = False
+ result = MakoTemplate(self.MAIL_TEMPLATE).render_unicode(message=message,
+ context_messages=context_messages,
+ header_message=header_message,
+ add_expandable=add_expandable,
+ # context kw would clash with mako internals
+ ctx=context,
+ format_exceptions=True)
+ result = result.strip()
+ return result
+ except Exception:
+ _logger.exception("failed to render mako template for quoting message")
+ return ''
+ return result
def _notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids if is not a private message.
Call mail_notification.notify to manage the email sending
"""
- message = self.browse(cr, uid, newid, context=context)
- if message.model and message.res_id:
- self._notify_followers(cr, uid, newid, message, context=context)
+ message = self.read(cr, uid, newid, ['model', 'res_id', 'author_id', 'subtype_id', 'partner_ids'], context=context)
- # add myself if I wrote on my wall, otherwise remove myself author
- if ((message.model == "res.partner" and message.res_id == message.author_id.id)):
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, message.author_id.id)]}, context=context)
- else:
- self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, message.author_id.id)]}, context=context)
+ partners_to_notify = set([])
+ # message has no subtype_id: pure log message -> no partners, no one notified
+ if not message.get('subtype_id'):
+ return True
+ # all partner_ids of the mail.message have to be notified
+ if message.get('partner_ids'):
+ partners_to_notify |= set(message.get('partner_ids'))
+ # all followers of the mail.message document have to be added as partners and notified
+ if message.get('model') and message.get('res_id'):
+ fol_obj = self.pool.get("mail.followers")
+ fol_ids = fol_obj.search(cr, uid, [
+ ('res_model', '=', message.get('model')),
+ ('res_id', '=', message.get('res_id')),
+ ('subtype_ids', 'in', message.get('subtype_id')[0])
+ ], context=context)
+ fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
+ partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
+ # when writing to a wall
+ if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
+ partners_to_notify |= set([message.get('author_id')[0]])
+ elif message.get('author_id'):
+ partners_to_notify = partners_to_notify - set([message.get('author_id')[0]])
+
+ if partners_to_notify:
+ self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p_id) for p_id in partners_to_notify]}, context=context)
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
- def copy(self, cr, uid, id, default=None, context=None):
- """Overridden to avoid duplicating fields that are unique to each email"""
- if default is None:
- default = {}
- default.update(message_id=False, headers=False)
- return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
-
#------------------------------------------------------
# Tools
#------------------------------------------------------
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index 4677beee511..93b35797fd5 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -29,6 +29,7 @@
+
@@ -38,6 +39,7 @@
+
@@ -63,6 +65,9 @@
+
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 04da686b49d..e986f12b822 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -554,7 +554,11 @@ class mail_thread(osv.AbstractModel):
('file2', 'bytes')}
}
"""
- msg_dict = {}
+ msg_dict = {
+ 'type': 'email',
+ 'subtype': 'mail.mt_comment',
+ 'author_id': False,
+ }
if not isinstance(message, Message):
if isinstance(message, unicode):
# Warning: message_from_string doesn't always work correctly on unicode,
@@ -572,7 +576,7 @@ class mail_thread(osv.AbstractModel):
if 'Subject' in message:
msg_dict['subject'] = decode(message.get('Subject'))
- # Envelope fields not stored in mail.message but made available for message_new()
+ # Envelope fields not stored in mail.message but made available for message_new()
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
@@ -581,6 +585,8 @@ class mail_thread(osv.AbstractModel):
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg_dict['author_id'] = author_ids[0]
+ else:
+ msg_dict['email_from'] = message.get('from')
partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
msg_dict['partner_ids'] = partner_ids
@@ -670,6 +676,18 @@ class mail_thread(osv.AbstractModel):
if self._mail_flat_thread and not parent_id and thread_id:
message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
parent_id = message_ids and message_ids[0] or False
+ # we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
+ elif parent_id:
+ message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
+ # avoid loops when finding ancestors
+ processed_list = []
+ if message_ids:
+ _counter, _counter_max = 0, 200
+ message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
+ while (message.parent_id and message.parent_id.id not in processed_list):
+ processed_list.append(message.parent_id.id)
+ message = message.parent_id
+ parent_id = message.id
values = kwargs
values.update({
@@ -689,27 +707,28 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context)
- def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
- subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
- # TDE FIXME: body is plaintext: convert it into html
- # when writing on res.partner, without specific thread_id -> redirect to the user's partner
- if self._name == 'res.partner' and not thread_id:
- thread_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
+ """ Wrapper on message_post, used only in Chatter (JS). The purpose is
+ to handle attachments.
+ # TDE FIXME: body is plaintext: convert it into html
+ """
+ new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
+ subtype='mail.mt_comment', parent_id=parent_id, context=context)
- new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=type,
- subtype=subtype, parent_id=parent_id, context=context)
-
- # Chatter: attachments linked to the document (not done JS-side), load the message
- if attachments:
+ # HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
+ if attachment_ids:
ir_attachment = self.pool.get('ir.attachment')
mail_message = self.pool.get('mail.message')
- attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [('res_model', '=', 'mail.message'), ('res_id', '=', 0), ('create_uid', '=', uid), ('id', 'in', attachments)], context=context)
- if attachment_ids:
+ filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
+ ('res_model', '=', 'mail.compose.message'),
+ ('res_id', '=', 0),
+ ('create_uid', '=', uid),
+ ('id', 'in', attachment_ids)], context=context)
+ if filtered_attachment_ids:
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': self._name, 'res_id': thread_id}, context=context)
mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context)
- new_message = self.pool.get('mail.message').message_read(cr, uid, [new_message_id], context=context)
- return new_message
+ return new_message_id
#------------------------------------------------------
# Followers API
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
index 55cc1de1ec0..07b24725b05 100644
--- a/addons/mail/mail_thread_view.xml
+++ b/addons/mail/mail_thread_view.xml
@@ -39,7 +39,7 @@
-
diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml
index 2a8430926d5..a4aa56ae971 100644
--- a/addons/mail/res_partner_view.xml
+++ b/addons/mail/res_partner_view.xml
@@ -9,9 +9,8 @@
-
+
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 54b8a76703f..1b6388111ba 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -111,8 +111,7 @@ class res_users(osv.Model):
alias_pool.unlink(cr, uid, alias_ids, context=context)
return res
- def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
- subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
+ def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id.
@@ -124,7 +123,7 @@ class res_users(osv.Model):
thread_id = thread_id[0]
partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0]
return self.pool.get('res.partner').message_post_api(cr, uid, partner_id, body=body, subject=subject,
- type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
+ parent_id=parent_id, attachment_ids=attachment_ids, context=context)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index c609667c26e..52c6c605109 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -2,6 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0
+access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
@@ -12,6 +13,6 @@ access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
-access_mail_message_subtype,mail.message.subtype,model_mail_message_subtype,,1,1,1,1
-access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
+access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
+access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1
\ No newline at end of file
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index 50ce0e000a9..af3cc3ed359 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -1,439 +1,382 @@
-/* ------------------------------------------------------------ */
-/* Reset because of ugly display of end of August
-/* ------------------------------------------------------------ */
-.openerp .oe_mail_wall ul, .openerp .oe_mail_wall li {
- list-style-type: none;
- padding: 0;
- margin: 0;
+/* ------------ MAIL WIDGET --------------- */
+.openerp .oe_mail, .openerp .oe_mail *{
+ box-sizing: border-box;
}
-
-.openerp .oe_chatter ul, .openerp .oe_chatter li {
- list-style-type: none;
- padding: 0;
- margin: 0;
-}
-
-/* ------------------------------------------------------------ */
-/* Wall
-/* ------------------------------------------------------------ */
-
-.openerp div.oe_mail_wall {
- overflow: auto;
- padding: 0;
- background: white;
-}
-
-.openerp div.oe_mail_wall div.oe_mail_wall_aside {
- margin-left: 565px;
- margin: 8px;
-}
-
-.openerp div.oe_mail_wall ul.oe_mail_wall_threads {
- float: left;
- width: 560px;
- margin: 8px;
- list-style-type: none;
-}
-
-/* ------------------------------------------------------------ */
-/* Followers
-/* ------------------------------------------------------------ */
-
-.openerp div.oe_mail_recthread_aside h4 {
- display: inline-block;
-}
-.openerp div.oe_mail_recthread_aside button {
+.openerp .oe_mail {
+ display: block;
position: relative;
+ margin: 0px;
}
-.openerp div.oe_mail_recthread_aside label,
-.openerp div.oe_mail_recthread_aside input {
- cursor:pointer;
+.openerp .oe_mail .oe_thread{
+ margin-left: 32px;
+}
+.openerp .oe_mail > .oe_thread{
+ margin-left: 0px;
}
-/* Specific display of threads in the wall */
-/* ------------------------------------------------------------ */
+/* ---------------- MESSAGES ------------------ */
-.openerp ul.oe_mail_wall_threads .oe_msg_content textarea.oe_mail_compose_textarea {
- width: 434px;
- height: 30px;
- padding: 4px;
-}
-
-.openerp li.oe_mail_wall_thread:first .oe_msg_notification {
- border-top: 0;
-}
-
-.openerp div.oe_thread_placeholder img {
- width: 28px;
- height: 28px;
-}
-
-.openerp div.oe_thread_placeholder div.oe_msg_content {
- width: 440px;
-}
-
-
-/* ------------------------------------------------------------ */
-/* RecordThread
-/* ------------------------------------------------------------ */
-
-.openerp .oe_form div.oe_chatter {
- overflow: auto;
-}
-
-.openerp .oe_mail_record_wall {
- margin: auto;
- width: 560px;
-}
-
-.openerp .oe_mail_record_wall > .oe_mail_wall_threads {
- float: left;
-}
-
-.openerp div.oe_mail_recthread_aside {
- float: right;
- width: 250px;
-}
-
-.openerp div.oe_mail_recthread_actions {
- margin-bottom: 8px;
-}
-
-.openerp div.oe_mail_recthread_actions button {
- width: 120px;
-}
-
-.openerp .oe_mail_recthread_aside .oe_follower.oe_follow {
- color: white;
- background-color: #8a89ba;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4));
- background-image: -webkit-linear-gradient(top, #8a89ba, #807fb4);
- background-image: -moz-linear-gradient(top, #8a89ba, #807fb4);
- background-image: -ms-linear-gradient(top, #8a89ba, #807fb4);
- background-image: -o-linear-gradient(top, #8a89ba, #807fb4);
- background-image: linear-gradient(to bottom, #8a89ba, #807fb4);
-}
-.openerp .oe_mail_recthread_aside .oe_follower.oe_following {
- color: white;
- background-color: #dc5f59;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630));
- background-image: -webkit-linear-gradient(top, #dc5f59, #b33630);
- background-image: -moz-linear-gradient(top, #dc5f59, #b33630);
- background-image: -ms-linear-gradient(top, #dc5f59, #b33630);
- background-image: -o-linear-gradient(top, #dc5f59, #b33630);
- background-image: linear-gradient(to bottom, #dc5f59, #b33630);
-}
-
-
-.openerp .oe_mail_recthread_aside .oe_follower span {
- display:none;
-}
-.openerp .oe_mail_recthread_aside .oe_following span.oe_following,
-.openerp .oe_mail_recthread_aside .oe_notfollow span.oe_follow {
- display:block;
-}
-
-.openerp div.oe_mail_recthread_followers {
- margin-bottom: 8px;
-}
-
-
-/* ------------------------------------------------------------ */
-/* subtypes
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail_subtypes {
- display:inline-block;
+.openerp .oe_mail .oe_msg{
position: relative;
- z-index: 5;
+ background: #F4F5FA;
+ border-radius: 2px;
+ margin-bottom: 2px;
+ min-height: 42px;
+ border: solid 1px rgba(0,0,0,0.03);
}
-.openerp .oe_mail_subtypes .oe_recthread_subtypes {
- background: #fff;
- padding: 2px;
- border: 1px solid #aaaaaa;
- border-top: 0px;
+.openerp .oe_mail .oe_msg .oe_msg_left{
position: absolute;
- z-index: 2;
+ left:0; top: 0; bottom: 0; width: 40px;
+ overflow: hidden;
}
-.openerp .oe_mail_subtypes.oe_mouseout .oe_recthread_subtypes {
- display: none;
+.openerp .oe_mail .oe_msg .oe_msg_icon{
+ width: 32px;
+ margin: 4px;
+ border-radius: 2px;
}
-.openerp .oe_mail_subtypes.oe_mouseover .oe_recthread_subtypes {
- display: block;
-}
-
-/* ------------------------------------------------------------ */
-/* Thread
-/* ------------------------------------------------------------ */
-
-.openerp div.oe_mail_thread_action {
- white-space: normal;
- padding: 8px;
- z-index:5;
- background: #fff;
-}
-
-.openerp div.oe_mail_thread_action:after {
- content: "";
- display: block;
- clear: both;
-}
-
-/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
-.openerp .oe_msg_content textarea.oe_mail_compose_textarea:focus,
-.openerp .oe_msg_content div.oe_mail_compose_message_body textarea:focus {
- outline: 0;
- border-color: rgba(82, 168, 236, 0.8);
- -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
- -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
- -box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
-}
-
-.openerp .oe_mail_vote_count,
-.openerp .oe_msg_vote{
- vertical-align: bottom;
-}
-
-.openerp button.oe_mail_starbox{
- background: #ff0000;
-}
-.openerp button.oe_mail_starbox.oe_stared{
- background: #00FF00;
-}
-
-.openerp div.oe_mail_thread_display {
- white-space: normal;
-}
-
-.openerp div.oe_thread_placeholder {
- margin-left: 66px;
-}
-
-.openerp li.oe_mail_thread_msg {
- width: 560px;
-}
-
-.openerp div.oe_thread_placeholder li.oe_mail_thread_msg:last-child {
- margin-bottom: 8px;
-}
-
-.openerp div.oe_mail_thread_more {
- display: none;
- border-bottom: 1px solid #D2D9E7;
-}
-
-.openerp li.oe_mail_thread_msg:after {
- content: "";
- display: block;
- clear: both;
-}
-
-.openerp li.oe_mail_thread_msg.oe_mail_read,
-.openerp li.oe_mail_thread_msg.oe_mail_read div {
- border-left: #F0F0F0;
-}
-.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread,
-.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread div {
- background-color: #F6F6F6;
-}
-
-.openerp li.oe_mail_thread_msg.oe_mail_unread>div>ul>li.oe_unread,
-.openerp li.oe_mail_thread_msg.oe_mail_read>div>ul>li.oe_read {
- display: none;
-}
-
-.openerp li.oe_mail_thread_msg > div:after {
- content: "";
- display: block;
- clear: both;
-}
-
-.openerp div.oe_mail_msg {
- padding: 0;
- margin: 0 0 4px 0;
-}
-
-.openerp .oe_msg_notification,
-.openerp .oe_msg_expandable,
-.openerp .oe_msg_comment,
-.openerp .oe_msg_email {
- padding: 8px;
- background: white;
+.openerp .oe_mail .oe_msg .oe_msg_center{
position: relative;
-}
-
-.openerp .oe_msg_notification:after,
-.openerp .oe_msg_comment:after,
-.openerp .oe_msg_email:after {
- content: "";
display: block;
- clear: both;
+ margin-left: 40px;
+}
+.openerp .oe_mail .oe_msg .oe_msg_footer{
+ padding-left: 4px;
+ overflow: hidden;
+ opacity:0.8;
+ -webkit-transition: opacity 0.2s linear;
+}
+.openerp .oe_mail .oe_msg .oe_msg_content{
+ display: block;
+ overflow: hidden;
+ padding: 4px;
+ padding-bottom:1px;
+}
+.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_title{
+ font-size: 13px;
+ margin-bottom: 0px;
+ margin-top: 2px;
}
-.openerp div.oe_msg_content {
- float: left;
+/* a) Indented Messages */
+
+.openerp .oe_mail .oe_msg_indented{
+ background: #FFF;
+ border: none;
+ margin-bottom:0px;
+ min-height:38px;
+}
+.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_icon{
+ width:32px;
+ margin:2px;
+ border-radius:2px;
+}
+.openerp .oe_mail .oe_msg .oe_subtle{
+ color: #B7B7D5;
+}
+.openerp .oe_mail .oe_msg_indented .oe_msg_center{
+ margin-left:34px;
+}
+.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_content{
+ padding-top:2px;
+}
+/* b) Votes (likes) */
+.openerp .oe_mail .oe_mail_vote_count{
+ display: inline;
position: relative;
- width: 486px;
-}
-
-.openerp div.oe_msg_content > li {
- float: left;
+ background: #7C7BAD;
+ color: white;
+ text-shadow: none;
+ border-radius: 3px;
+ margin: 0px;
+ padding-left: 3px;
+ padding-right: 18px;
margin-right: 3px;
}
-
-.openerp .oe_msg_content:after {
- content: "";
- display: block;
- clear: both;
+.openerp .oe_mail .oe_mail_vote_count .oe_e{
+ position: absolute;
+ bottom: 1px;
+ right: 2px;
+ font-size: 26px;
}
-.openerp .oe_chatter a {
- cursor: pointer;
-}
+/* c) Message action icons */
-.openerp img.oe_mail_icon {
- width: 50px;
- height: 50px;
+.openerp .oe_mail .oe_msg.oe_msg_unread .oe_unread{
+ display:none;
}
-
-.openerp img.oe_mail_thumbnail {
- width: 28px;
- height: 28px;
- margin: 4px;
+.openerp .oe_mail .oe_msg.oe_msg_read .oe_read{
+ display:none;
}
-
-.openerp img.oe_mail_frame {
+.openerp .oe_mail .oe_msg .oe_msg_icons{
+ float: right;
+ margin-top: 4px;
+ margin-right: 8px;
+ margin-left: 8px;
+ height: 24px;
+ -webkit-user-select: none;
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons span{
+ float:right;
+ width:24px;
+ height:24px;
+ line-height:24px;
text-align: center;
- overflow: hidden;
- -moz-border-radius: 3px;
- -webkit-border-radius: 3px;
- -o-border-radius: 3px;
- -ms-border-radius: 3px;
- border-radius: 3px;
- -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- -o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
- clip: rect(5px, 40px, 45px, 0px);
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons a {
+ text-decoration: none;
+ 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;
+}
+.openerp .oe_mail .oe_msg:hover .oe_msg_icons a{
+ opacity: 1;
+ -webkit-transition: all 0.1s linear;
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star:hover a{
+ color: #FFF6C0;
+ text-shadow: 0px 1px #FFA162,0px -1px #FFA162, -1px 0px #FFA162, 1px 0px #FFA162, 0px 3px 3px rgba(0,0,0,0.1);
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star.oe_starred a{
+ color: #FFE41F;
+ text-shadow: 0px 1px #DF6200,0px -1px #DF6200, -1px 0px #DF6200, 1px 0px #DF6200, 0px 3px 3px rgba(0,0,0,0.1);
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_reply:hover a{
+ color: #1fc0ff;
+ text-shadow: 0px 1px #184fc5,0px -1px #184fc5, -1px 0px #184fc5, 1px 0px #184fc5, 0px 3px 3px rgba(0,0,0,0.1);
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_read:hover a{
+ color: #bbbaff;
+ text-shadow: 0px 1px #7c7bad,0px -1px #7c7bad, -1px 0px #7c7bad, 1px 0px #7c7bad, 0px 3px 3px rgba(0,0,0,0.1);
+}
+.openerp .oe_mail .oe_msg .oe_msg_icons .oe_unread:hover a{
+ color: #c2ff00;
+ text-shadow: 0px 1px #009441,0px -1px #009441, -1px 0px #009441, 1px 0px #009441, 0px 3px 3px rgba(0,0,0,0.1);
+}
+.openerp .oe_mail .oe_msg .oe_msg_content textarea{
+ width: 100%;
+ height: 32px;
+ margin: 0px;
+ padding: 0px;
+ resize: vertical;
+
+ padding: 4px;
+}
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact, .openerp .oe_mail .oe_msg.oe_msg_expandable{
+ padding:4px;
+ min-height:0px;
+}
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact textarea{
+ height: 24px;
+ width: 100%;
}
-.openerp .oe_mail_invisible {
+/* ---------------- MESSAGE QUICK COMPOSER --------------- */
+
+.openerp .oe_mail .oe_msg_composer .oe_msg_footer{
+ padding-right:4px;
+ 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;
}
-
-/* ------------------------------------------------------------ */
-/* Messages layout
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail_msg .oe_msg_title {
- margin: 0;
- font-size: 1.3em;
- font-weight: bold;
+.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_msg .oe_msg_title a:link,
-.openerp .oe_mail_msg .oe_msg_title a:visited {
- color: #4C4C4C;
+.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;
+}
+.openerp .oe_mail .oe_msg_footer button{
+ display: inline;
+ height: 24px;
+ font-size: 12px;
+ line-height: 12px;
+ vertical-align: middle;
+}
+.openerp .oe_mail .oe_msg_footer button.oe_attach{
+ width: 24px;
+ overflow: hidden;
+}
+.openerp .oe_mail .oe_msg_footer button.oe_attach .oe_e{
+ position: relative;
+ top: -1px;
+ left: -9px;
+}
+.openerp .oe_mail .oe_hidden_input_file, .openerp .oe_mail .oe_hidden_input_file form{
+ display:inline;
+}
+.openerp .oe_mail .oe_msg_footer button.oe_full{
+ width:24px;
+ overflow:hidden;
+ float: right;
+}
+.openerp .oe_mail .oe_msg_footer button.oe_full .oe_e{
+ position: relative;
+ top: -1px;
+ left: -9px;
+}
+.openerp .oe_mail button.oe_attach, .openerp .oe_mail button.oe_full{
+ background: transparent;
+ color: #7C7BAD;
+ box-shadow: none;
+ border: none;
+ text-shadow: none;
+}
+.openerp .oe_mail .oe_attach_label{
+ color: #7C7BAD;
+ margin-left: -3px;
+}
+.openerp .oe_mail .oe_msg_footer .oe_attachment_file .oe_form_binary_file{
+ display: inline-block;
+ margin-left: -47px;
+ height: 28px;
+ width: 52px;
+ margin-top: -6px;
+}
+.openerp .oe_mail .oe_mail_list_recipients{
+ font-size: 12px;
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+/* ---------------- HIDDEN MESSAGES ------------------ */
+
+.openerp .oe_mail .oe_msg_content.oe_msg_more_message{
+ text-align: right;
+}
+.openerp .oe_mail .oe_msg_content.oe_msg_more_message .oe_separator{
+ height: 0;
+ border-bottom: dashed 1px #e6e6e6;
+ margin-left: -4px;
+ margin-right: 8px;
+ margin-top: 6px;
+ margin-bottom: -9px;
+}
+.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more {
+ background: white;
+ margin-right: 280px;
+ padding-left: 8px;
+ padding-right: 8px;
+ text-decoration: none;
+ color: #b4b4b4;
+}
+.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more:hover{
text-decoration: none;
}
-.openerp .oe_mail_msg .oe_msg_body {
- margin-bottom: .5em;
- text-align: justify;
+/* ---------------- FOLLOWERS ------------------ */
+
+.openerp .oe_followers{
+ position: relative;
+ display: inline-block;
+ padding-top: 5px;
+ width: 160px;
+ float: right;
}
-.openerp .oe_mail_msg .oe_msg_body pre {
- font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
- margin: 0px;
- white-space: pre-wrap;
+/* a) THE FOLLOW BUTTON */
+
+.openerp .oe_followers button.oe_follower{
+ display: block;
+ text-align: center;
+ width:100%;
+}
+.openerp .oe_followers button.oe_follower.oe_following{
+ background-color: #3465A4;
+ background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
+ color: white;
+}
+
+.openerp .oe_followers button.oe_follower .oe_follow,
+.openerp .oe_followers button.oe_follower .oe_unfollow,
+.openerp .oe_followers button.oe_follower .oe_following{
+ display: none;
+}
+/* a.1) when following, show 'following' */
+.openerp .oe_followers button.oe_follower.oe_following .oe_following{
+ display: inline;
+}
+/* a.2) when following and hovering, show 'unfollow' */
+.openerp .oe_followers button.oe_follower.oe_following:hover .oe_following{
+ display: none;
+}
+.openerp .oe_followers button.oe_follower.oe_following:hover .oe_unfollow{
+ display: inline;
+}
+/* a.3) when not following show 'follow' */
+.openerp .oe_followers button.oe_follower.oe_notfollow .oe_follow{
display: inline;
}
-/* Read more/less link */
-.openerp .oe_mail_msg span.oe_mail_reduce {
- position: absolute;
- right: 0;
+.openerp .oe_followers .oe_subtype_list{
+ margin-top: 4px;
}
-/* Dropdown menu */
-
-.openerp .oe_mail ul.oe_mail_thread_display ul.oe_mail_thread_display {
- position: relative;
- border-left: 1px #DDD dashed;
+/* b) THE FOLLOWERS */
+.openerp .oe_followers .oe_follower_title{
+ display: inline;
}
-
-.openerp .oe_mail ul.oe_header {
- position: absolute;
- right: 3px;
- top: -6px;
- z-index: 10;
+.openerp .oe_followers .oe_follower_title_box{
+ margin-top: 12px;
+ margin-bottom: 4px;
}
-
-.openerp .oe_mail ul.oe_header a {
- text-decoration: none;
-}
-
-.openerp .oe_mail ul.oe_header>li {
- display: inline-block;
- height: 20px;
- text-align: right;
-}
-
-/* Message footer */
-.openerp .oe_mail_msg .oe_msg_footer {
- color: #888;
-}
-.openerp .oe_mail_msg .oe_msg_footer li {
- float: left;
- margin-right: 3px;
-}
-.openerp .oe_mail_msg .oe_msg_footer li:after {
- content: " · ";
-}
-.openerp .oe_mail_msg .oe_msg_footer li:last-child:after {
- content: "";
-}
-
-/* Attachments list */
-.openerp .oe_msg_content ul.oe_msg_attachments {
- width: 100%;
- margin: .5em 0 0 0;
- padding: .5em 0;
- list-style-position: inside;
-}
-.openerp .oe_msg_content ul.oe_msg_attachments.oe_hidden {
- display: none;
-}
-.openerp .oe_msg_content ul.oe_msg_attachments li {
- float: none;
- height: 20px;
- line-height: 20px;
- margin: 0;
- padding: 0;
- list-style-type: square;
-}
-.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process {
+.openerp .oe_followers .oe_invite{
float: right;
- width: 200px;
- height: 16px;
}
-.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process div {
- float: left;
- width: 38px;
- height: 16px;
- margin-right: 2px;
- background: #66FF66;
+.openerp .oe_followers .oe_partner {
+ height: 32px;
+ overflow: hidden;
}
-.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process span {
- color: #aaaaaa;
- position: absolute;
+.openerp .oe_followers .oe_partner img{
+ width: 32px;
+ margin-right:4px;
+ border-radius: 2px;
}
-/* ------------------------------------------------------------ */
-/* Topbar button
-/* ------------------------------------------------------------ */
+/* ---------------- MESSAGES BODY ------------------ */
+.openerp .oe_mail .oe_msg_content .oe_blockquote,
+.openerp .oe_mail .oe_msg_content blockquote {
+ padding: 4px;
+ border-radius: 2px;
+ border: solid 1px rgba(124,123,173,0.14);
+}
-.openerp .oe_topbar .oe_topbar_compose_full_email {
- float: right;
- margin: 3px 25px 0 0;
-}
\ No newline at end of file
+/* ----------- FORM INTEGRATION ------------ */
+
+.openerp .oe_record_thread{
+ display: block;
+ margin-right: 180px;
+}
+
+/* ----------- INBOX INTEGRATION ----------- */
+
+.openerp .oe_mail_wall .oe_mail{
+ margin: 16px;
+ width: 720px;
+}
diff --git a/addons/mail/static/src/css/mail_compose_message.css b/addons/mail/static/src/css/mail_compose_message.css
deleted file mode 100644
index c706c55bcde..00000000000
--- a/addons/mail/static/src/css/mail_compose_message.css
+++ /dev/null
@@ -1,166 +0,0 @@
-/* ------------------------------ */
-/* Compose Message */
-/* ------------------------------ */
-
-.openerp .oe_msg_content .oe_mail_compose_message_footer {
- height: 24px;
-}
-
-.openerp .oe_msg_content .oe_mail_compose_message_footer button.oe_mail_compose_message_button_send {
- float: left;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea
-{
- display: none;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_header,
-.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_footer,
-{
- position: relative;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel {
- position: absolute;
- right: -8px;
- top: -8px;
-}
-.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel:first-of-type {
- display:none;
-}
-
-.openerp .oe_mail .oe_mail_compose_textarea button.oe_full {
- float: right;
- position: relative;
- right: -10px;
-}
-
-/* ------------------------------------------------------------ */
-/* mail.compose.message : list_recipients
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail .oe_mail_list_recipients {
- display: inline;
-}
-.openerp .oe_mail .oe_mail_list_recipients .oe_all_follower {
- color: blue;
-}
-.openerp .oe_mail .oe_mail_list_recipients .oe_partner_follower a {
- color: red;
-}
-.openerp .oe_mail .oe_mail_list_recipients .oe_hidden,
-.openerp .oe_mail .oe_mail_list_recipients .oe_more_hidden {
- display: none;
-}
-
-/* ------------------------------------------------------------ */
-/* mail.compose.message : attachment
-/* ------------------------------------------------------------ */
-
-.openerp .oe_mail .oe_attachment_file {
- display: inline-block;
-}
-
-.openerp .oe_mail .oe_attachment_file .oe_add {
- float: left;
- width: 24px;
- height: 24px;
- position: relative;
- z-index: 10;
- left: +2px;
- top: +7px;
- overflow: hidden;
-}
-
-/* attachment button: override of openerp values */
-.openerp .oe_mail .oe_attachment_file .oe_add button,
-.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
- position: absolute;
- bottom: +0px;
- left: +0px;
- height: 24px;
- width: 24px;
- margin: 0px;
- padding: 0px;
-}
-.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
- z-index:2;
- width: 300px;
- left: -100px;
- background: transparent;
- border: 0;
- color: transparent;
-}
-.openerp .oe_mail .oe_attachment_file .oe_add button span {
- position: relative;
- bottom: +4px;
- font-size: 30px;
-}
-
-.openerp .oe_mail .oe_msg_attachments input {
- visibility: hidden;
-}
-
-.openerp .oe_mail .oe_mail_compose_attachment_list {
- clear: both;
-}
-
-/* ------------------------------------------------------------ */
-/* mail.compose.message
-/* ------------------------------------------------------------ */
-
-
-/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
-.openerp .oe_mail.oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text,
-.openerp .oe_mail div.oe_mail_compose_message_body textarea.field_text {
- width: 100%;
- min-height: 120px;
- height: auto;
- padding: 4px;
- font-size: 12px;
- border: 1px solid #cccccc;
-}
-
-/* not top textarea */
-.openerp .oe_mail.oe_semantic_html_override .oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text {
- height: 60px;
-}
-
-/* form_view: delete white background */
-.openerp .oe_msg_content div.oe_formview {
- background-color: transparent;
-}
-
-.openerp .oe_msg_content div.oe_form_nosheet {
- margin: 0px;
-}
-
-.openerp .oe_msg_content table.oe_form_group {
- margin: 0px;
-}
-
-.openerp .oe_msg_content table.oe_form_field,
-.openerp .oe_msg_content div.oe_form_field {
- padding: 0px;
-}
-
-.openerp .oe_msg_content td.oe_form_group_cell {
- vertical-align: bottom;
-}
-
-/* subject: change width */
-.openerp .oe_msg_content .oe_form .oe_form_field input[type='text'] {
- width: 472px;
-}
-
-/* body_html: cleditor */
-.openerp .oe_msg_content div.cleditorMain {
- border: 1px solid #cccccc;
-}
-
-/* destination_partner_ids */
-.openerp .oe_msg_content div.text-core {
- height: 22px !important;
- width: 472px;
-}
\ No newline at end of file
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 7501eabe104..9133e5de257 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -1,4 +1,4 @@
-openerp.mail = function(session) {
+openerp.mail = function (session) {
var _t = session.web._t,
_lt = session.web._lt;
@@ -16,15 +16,16 @@ openerp.mail = function(session) {
*/
session.web.FormView = session.web.FormView.extend({
- do_action: function(action) {
+ do_action: function (action) {
if (action.res_model == 'mail.compose.message') {
/* hack for stop context propagation of wrong value
* delete this hack when a global method to clean context is create
*/
var context_keys = ['default_template_id', 'default_composition_mode',
'default_use_template', 'default_partner_ids', 'default_model',
- 'default_res_id', 'default_content_subtype', 'active_id', 'lang',
- 'bin_raw', 'tz', 'active_model', 'edi_web_url_view', 'active_ids']
+ 'default_res_id', 'default_content_subtype', , 'default_subject',
+ 'default_body', 'active_id', 'lang', 'bin_raw', 'tz',
+ 'active_model', 'edi_web_url_view', 'active_ids']
for (var key in action.context) {
if (_.indexOf(context_keys, key) == -1) {
action.context[key] = null;
@@ -51,17 +52,18 @@ openerp.mail = function(session) {
mail.ChatterUtils = {
- /** Get an image in /web/binary/image?... */
- get_image: function(session, model, field, id) {
+ /* 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 the url of an attachment {'id': id} */
+ /* Get the url of an attachment {'id': id} */
get_attachment_url: function (session, attachment) {
return session.origin + '/web/binary/saveas?session_id=' + session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id'];
},
- /** Replaces some expressions
+ /**
+ * Replaces some expressions
* - :name - shortcut to an image
*/
do_replace_expressions: function (string) {
@@ -78,14 +80,36 @@ openerp.mail = function(session) {
return string;
},
- /* replace textarea text into html text
- * (add , )
- * TDE note : should not be here, but server-side I think ...
- */
- get_text2html: function(text){
+ /**
+ * Replaces textarea text into html text (add , )
+ * TDE note : should be done server-side, in Python -> use mail.compose.message ?
+ */
+ get_text2html: function (text) {
return text
.replace(/[\n\r]/g,' ')
.replace(/((?:https?|ftp):\/\/[\S]+)/g,' $1 ')
+ },
+
+ /* Returns the complete domain with "&"
+ * TDE note: please add some comments to explain how/why
+ */
+ expand_domain: function (domain) {
+ var new_domain = [];
+ var nb_and = -1;
+ // TDE note: smarted code maybe ?
+ for ( var k = domain.length-1; k >= 0 ; k-- ) {
+ if ( typeof domain[k] != 'array' && typeof domain[k] != 'object' ) {
+ nb_and -= 2;
+ continue;
+ }
+ nb_and += 1;
+ }
+
+ for (var k = 0; k < nb_and ; k++) {
+ domain.unshift('&');
+ }
+
+ return domain;
}
};
@@ -97,6 +121,8 @@ openerp.mail = function(session) {
*
* This widget handles the display of a form to compose a new message.
* This form is a mail.compose.message form_view.
+ * On first time : display a compact textarea that is not the compose form.
+ * When the user focuses the textarea, the compose message is instantiated.
*/
mail.ThreadComposeMessage = session.web.Widget.extend({
@@ -108,60 +134,50 @@ openerp.mail = function(session) {
* @param {Object} [context] context passed to the
* mail.compose.message DataSetSearch. Please refer to this model
* for more details about fields and default values.
- * @param {Boolean} [show_attachment_delete]
*/
- init: function (parent, options) {
+
+ init: function (parent, datasets, options) {
var self = this;
this._super(parent);
this.context = options.context || {};
+ this.options = options.options;
- this.datasets = {
- 'attachment_ids' : [],
- 'id': options.datasets.id,
- 'model': options.datasets.model,
- 'res_model': options.datasets.res_model,
- 'is_private': options.datasets.is_private || false,
- 'partner_ids': options.datasets.partner_ids || []
- };
- this.options={};
- this.options.thread={};
- this.options.thread.show_header_compose = options.options.thread.show_header_compose;
- this.options.thread.display_on_thread = options.options.thread.display_on_thread;
- this.options.thread.show_attachment_delete = true;
- this.options.thread.show_attachment_link = true;
+ 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(){
+ start: function () {
this.display_attachments();
this.bind_events();
-
- //load avatar img
- var user_avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
- this.$('img.oe_mail_icon').attr('src', user_avatar);
},
/* upload the file on the server, add in the attachments list and reload display
*/
- display_attachments: function(){
- var self = this;
- var render = $(session.web.qweb.render('mail.thread.message.attachments', {'widget': self}));
- if(!this.list_attachment){
- this.$('.oe_mail_compose_attachment_list').replaceWith( render );
- } else {
- this.list_attachment.replaceWith( render );
- }
- this.list_attachment = this.$("ul.oe_msg_attachments");
-
+ display_attachments: function () {
+ this.$(".oe_msg_attachment_list").html(
+ session.web.qweb.render('mail.thread.message.attachments', {'widget': this}) );
// event: delete an attachment
- this.$el.on('click', '.oe_mail_attachment_delete', self.on_attachment_delete);
+ 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) {
event.stopPropagation();
var self = this;
@@ -172,25 +188,24 @@ openerp.mail = function(session) {
// if the files exits for this answer, delete the file before upload
var attachments=[];
- for(var i in this.datasets.attachment_ids){
- if((this.datasets.attachment_ids[i].filename || this.datasets.attachment_ids[i].name) == filename){
- if(this.datasets.attachment_ids[i].upload){
+ for (var i in this.attachment_ids) {
+ if ((this.attachment_ids[i].filename || this.attachment_ids[i].name) == filename) {
+ if (this.attachment_ids[i].upload) {
return false;
}
- this.ds_attachment.unlink([this.datasets.attachment_ids[i].id]);
+ this.ds_attachment.unlink([this.attachment_ids[i].id]);
} else {
- attachments.push(this.datasets.attachment_ids[i]);
+ attachments.push(this.attachment_ids[i]);
}
}
- this.datasets.attachment_ids = attachments;
+ this.attachment_ids = attachments;
// submit file
- //session.web.blockUI();
- self.$('form.oe_form_binary_form').submit();
+ this.$('form.oe_form_binary_form').submit();
this.$(".oe_attachment_file").hide();
- this.datasets.attachment_ids.push({
+ this.attachment_ids.push({
'id': 0,
'name': filename,
'filename': filename,
@@ -201,11 +216,12 @@ openerp.mail = function(session) {
}
},
+ /* when the file is uploaded
+ */
on_attachment_loaded: function (event, result) {
- //session.web.unblockUI();
- for(var i in this.datasets.attachment_ids){
- if(this.datasets.attachment_ids[i].filename == result.filename && this.datasets.attachment_ids[i].upload) {
- this.datasets.attachment_ids[i]={
+ for (var i in this.attachment_ids) {
+ if (this.attachment_ids[i].filename == result.filename && this.attachment_ids[i].upload) {
+ this.attachment_ids[i]={
'id': result.id,
'name': result.name,
'filename': result.filename,
@@ -219,6 +235,7 @@ openerp.mail = function(session) {
$input.after($input.clone(true)).remove();
this.$(".oe_attachment_file").show();
},
+
/* unlink the file on the server and reload display
*/
on_attachment_delete: function (event) {
@@ -226,61 +243,58 @@ openerp.mail = function(session) {
var attachment_id=$(event.target).data("id");
if (attachment_id) {
var attachments=[];
- for(var i in this.datasets.attachment_ids){
- if(attachment_id!=this.datasets.attachment_ids[i].id){
- attachments.push(this.datasets.attachment_ids[i]);
+ for (var i in this.attachment_ids) {
+ if (attachment_id!=this.attachment_ids[i].id) {
+ attachments.push(this.attachment_ids[i]);
}
else {
this.ds_attachment.unlink([attachment_id]);
}
}
- this.datasets.attachment_ids = attachments;
+ this.attachment_ids = attachments;
this.display_attachments();
}
},
- /* to avoid having unsorted file on the server.
- we will show the users files of the first message post
- TDE note: unnecessary call to server I think
- */
- // set_free_attachments: function(){
- // var self=this;
- // this.parent_thread.ds_message.call('user_free_attachment').then(function(attachments){
- // this.attachment_ids=[];
- // for(var i in attachments){
- // self.attachment_ids[i]={
- // 'id': attachments[i].id,
- // 'name': attachments[i].name,
- // 'filename': attachments[i].filename,
- // 'url': mail.ChatterUtils.get_attachment_url(self.session, attachments[i])
- // };
- // }
- // self.display_attachments();
- // });
- // },
-
- bind_events: function() {
+ bind_events: function () {
var self = this;
+ 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', self.on_attachment_change );
- this.$el.on('click', 'a.oe_cancel', self.on_cancel );
- this.$el.on('click', 'button.oe_post', function(){self.on_message_post()} );
- this.$el.on('click', 'button.oe_full', function(){self.on_compose_fullmail()} );
+ this.$el.on('change', 'input.oe_form_binary_file', _.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') );
+
+ /* 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();
+
+ // auto close
+ this.$el.on('blur', 'textarea:not(.oe_compact):first', _.bind( this.on_compose_expandable, this));
},
- on_compose_fullmail: function(){
- var attachments=[];
- for(var i in this.datasets.attachment_ids){
- attachments.push(this.datasets.attachment_ids[i].id);
- }
- /* TDE note: I think this is not necessary, because
- * 1/ post on a document: followers added server-side in _notify
- * 2/ reply to a message: mail.compose.message should add the previous partners
- */
- var partner_ids=[];
- for(var i in this.datasets.partner_ids){
- partner_ids.push(this.datasets.partner_ids[i][0]);
+ on_compose_fullmail: function (default_composition_mode) {
+ if (default_composition_mode == 'reply') {
+ var context = {
+ '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() || '') : ''),
+ 'default_attachment_ids': this.attachment_ids,
+ };
+ } else {
+ var context = {
+ 'default_model': this.context.default_model,
+ 'default_res_id': this.context.default_res_id,
+ 'default_content_subtype': 'html',
+ '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() || '') : ''),
+ 'default_attachment_ids': this.attachment_ids,
+ };
}
var action = {
type: 'ir.actions.act_window',
@@ -290,70 +304,108 @@ openerp.mail = function(session) {
action_from: 'mail.ThreadComposeMessage',
views: [[false, 'form']],
target: 'new',
- context: {
- 'default_model': this.context.default_model,
- 'default_res_id': this.context.default_res_id,
- 'default_content_subtype': 'html',
- 'default_parent_id': this.context.default_parent_id,
- 'default_body': mail.ChatterUtils.get_text2html(this.$('textarea').val() || ''),
- 'default_attachment_ids': attachments,
- 'default_partner_ids': partner_ids
- },
+ context: context,
};
+
this.do_action(action);
+ this.on_cancel();
},
- on_cancel: function(event){
- if(event) event.stopPropagation();
- this.$('textarea').val("");
- this.$('input[data-id]').remove();
- //this.attachment_ids=[];
+ reinit: function() {
+ var $render = $( session.web.qweb.render('mail.compose_message', {'widget': this}) );
+
+ $render.insertAfter(this.$el.last());
+ this.$el.remove();
+ this.$el = $render;
+
this.display_attachments();
- if(!this.options.thread.show_header_compose || !this.options.thread.display_on_thread[0]){
- this.$el.hide();
- }
+ this.bind_events();
+ },
+
+ on_cancel: function (event) {
+ if (event) event.stopPropagation();
+ this.attachment_ids=[];
+ this.stay_open = false;
+ this.show_composer = false;
+ this.reinit();
},
/*post a message and fetch the message*/
- on_message_post: function (body) {
+ on_message_post: function (event) {
var self = this;
- if (! body) {
- var comment_node = this.$('textarea');
- var body = comment_node.val();
- comment_node.val('');
- }
+ var comment_node = this.$('textarea');
+ var body = comment_node.val();
+ comment_node.val('');
var attachments=[];
- for(var i in this.datasets.attachment_ids){
- if(this.datasets.attachment_ids[i].upload){
+ for (var i in this.attachment_ids) {
+ if (this.attachment_ids[i].upload) {
session.web.dialog($('
' + session.web.qweb.render('CrashManager.warning', {message: 'Please, wait while the file is uploading.'}) + '
'));
return false;
}
- attachments.push(this.datasets.attachment_ids[i].id);
+ attachments.push(this.attachment_ids[i].id);
}
- if(body.match(/\S+/)) {
+ if (body.match(/\S+/)) {
+ //session.web.blockUI();
this.parent_thread.ds_thread.call('message_post_api', [
this.context.default_res_id,
mail.ChatterUtils.get_text2html(body),
false,
- 'comment',
- 'mail.mt_comment',
this.context.default_parent_id,
attachments,
this.parent_thread.context
- ]).then(function(records){
- self.parent_thread.switch_new_message(records);
- self.datasets.attachment_ids=[];
- self.on_cancel();
+ ]).then(function (record) {
+ var thread = self.parent_thread;
+ // create object and attach to the thread object
+ thread.message_fetch(false, false, [record], function (arg, data) {
+ var message = thread.create_message_object( data[0] );
+ // insert the message on dom
+ thread.insert_message( message, self.$el );
+ if (thread.parent_message) {
+ self.$el.remove();
+ self.parent_thread.compose_message = null;
+ } else {
+ self.on_cancel();
+ }
+ });
+ //session.web.unblockUI();
});
return true;
}
},
+
+ /* convert the compact mode into the compose message
+ */
+ on_compose_expandable: function (event) {
+
+ if (!this.stay_open && (!this.show_composer || !this.$('textarea:not(.oe_compact)').val().match(/\S+/))) {
+ this.show_composer = !this.show_composer || this.stay_open;
+ this.reinit();
+ }
+ if (!this.stay_open && this.show_composer) {
+ this.$('textarea:not(.oe_compact):first').focus();
+ }
+ return true;
+ },
+
+ do_hide_compact: function () {
+ this.show_compact_message = false;
+ if (!this.show_composer) {
+ this.reinit();
+ }
+ },
+
+ do_show_compact: function () {
+ this.show_compact_message = true;
+ if (!this.show_composer) {
+ this.reinit();
+ }
+ }
});
- /**
+ /**
* ------------------------------------------------------------
* Thread Message Expandable Widget
* ------------------------------------------------------------
@@ -369,71 +421,81 @@ openerp.mail = function(session) {
mail.ThreadExpandable = session.web.Widget.extend({
template: 'mail.thread.expandable',
- init: function(parent, options) {
+ init: function (parent, datasets, context) {
this._super(parent);
- this.domain = options.domain || [];
+ this.domain = datasets.domain || [];
+ this.options = datasets.options;
this.context = _.extend({
default_model: 'mail.thread',
default_res_id: 0,
- default_parent_id: false }, options.context || {});
+ default_parent_id: false }, context || {});
- this.datasets = {
- 'id' : options.datasets.id || -1,
- 'model' : options.datasets.model || false,
- 'parent_id' : options.datasets.parent_id || false,
- 'nb_messages' : options.datasets.nb_messages || 0,
- 'type' : 'expandable',
- 'max_limit' : options.datasets.max_limit || false,
- 'flag_used' : false,
- };
-
- // record options and data
- this.parent_thread= parent.messages!= undefined ? parent : options.options.thread._parents[0] ;
+ // 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];
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
this.bind_events();
},
+ reinit: function () {
+ var $render = $(session.web.qweb.render('mail.thread.expandable', {'widget': this}));
+ this.$el.replaceWith( $render );
+ this.$el = $render;
+ this.bind_events();
+ },
+
/**
* Bind events in the widget. Each event is slightly described
* in the function. */
- bind_events: function() {
- var self = this;
- this.$el.on('click', 'a.oe_mail_fetch_more', self.on_expandable);
+ bind_events: function () {
+ this.$el.on('click', 'a.oe_msg_fetch_more', this.on_expandable);
},
- animated_destroy: function(options) {
+ animated_destroy: function (fadeTime) {
var self=this;
- //graphic effects
- if(options && options.fadeTime) {
- self.$el.fadeOut(options.fadeTime, function(){
- self.destroy();
- });
- } else {
+ this.$el.fadeOut(fadeTime, function () {
self.destroy();
- }
+ });
},
/*The selected thread and all childs (messages/thread) became read
* @param {object} mouse envent
*/
on_expandable: function (event) {
- if(event)event.stopPropagation();
- if(this.datasets.flag_used) {
+ if (event)event.stopPropagation();
+ if (this.flag_used) {
return false
}
- this.datasets.flag_used = true;
+ this.flag_used = true;
- this.animated_destroy({'fadeTime':300});
- this.parent_thread.message_fetch(false, this.domain, this.context);
+ this.animated_destroy(200);
+ this.parent_thread.message_fetch(this.domain, this.context);
return false;
},
+
+ /**
+ * call on_message_delete on his parent thread
+ */
+ destroy: function () {
+
+ this._super();
+ this.parent_thread.on_message_detroy(this);
+
+ }
});
- /**
+ /**
* ------------------------------------------------------------
* Thread Message Widget
* ------------------------------------------------------------
@@ -463,80 +525,56 @@ openerp.mail = function(session) {
* @param {Object} [options]
* @param {Object} [thread] read obout mail.Thread object
* @param {Object} [message]
- * @param {Number} [message_ids=null] ids for message_fetch
- * @param {Number} [message_data=null] already formatted message data,
- * for subthreads getting data from their parent
* @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 {Boolean} [show_dd_delete]
- * @param {Array [A,B]} [show_reply] display the reply button on the
- * message for thread level between A and B. -1 for no begin or no end.
- * @param {Array [A,B]} [show_read_unread] display the read/unread button on the
- * message for thread level between A and B. -1 for no begin or no end.
+ *... @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, options) {
+ init: function (parent, datasets, context) {
this._super(parent);
- // record datasets
- var param = options.datasets;
- this.datasets = _.extend({
- 'id' : -1,
- 'model' : false,
- 'parent_id': false,
- 'res_id' : false,
- 'type' : false,
- 'is_author' : false,
- 'is_private' : false,
- 'subject' : false,
- 'name' : false,
- 'record_name' : false,
- 'body' : false,
- 'vote_user_ids' :[],
- 'has_voted' : false,
- 'is_favorite' : false,
- 'thread_level' : 0,
- 'to_read' : true,
- 'author_id' : [],
- 'attachment_ids' : [],
- }, param || {});
- this.datasets._date = param.date;
-
// record domain and context
- this.domain = options.domain || [];
+ this.domain = datasets.domain || [];
this.context = _.extend({
default_model: 'mail.thread',
default_res_id: 0,
- default_parent_id: false }, options.context || {});
+ default_parent_id: false }, context || {});
// record options
- this.options={
- 'thread' : options.options.thread,
- 'message' : {
- 'message_ids': options.options.message.message_ids || null,
- 'message_data': options.options.message.message_data || null,
- 'show_record_name': options.options.message.show_record_name != undefined ? options.options.message.show_record_name: true,
- 'show_dd_delete': options.options.message.show_dd_delete || false,
- 'truncate_limit': options.options.message.truncate_limit || 250,
- 'show_reply': options.options.message.show_reply || [0,-1],
- 'show_read_unread': options.options.message.show_read_unread || [0,-1],
- }
- };
+ this.options = datasets.options;
- this.datasets.show_reply = this.options.message.show_reply[0]>=0 &&
- this.options.message.show_reply[0]<=this.datasets.thread_level &&
- (this.options.message.show_reply[1]<0 || this.options.message.show_reply[1]>=this.datasets.thread_level);
+ // 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.datasets.show_read_unread = this.options.message.show_read_unread[0]>=0 &&
- this.options.message.show_read_unread[0]<=this.datasets.thread_level &&
- (this.options.message.show_read_unread[1]<0 || this.options.message.show_read_unread[1]>=this.datasets.thread_level);
+
+ 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 : options.options.thread._parents[0];
+ this.parent_thread= parent.messages!= undefined ? parent : this.options._parents[0];
this.thread = false;
- if( this.datasets.id > 0 ) {
+ if ( this.id > 0 ) {
this.formating_data();
}
@@ -545,63 +583,93 @@ openerp.mail = function(session) {
this.ds_follow = new session.web.DataSetSearch(this, 'mail.followers');
},
- formating_data: function(){
+ /* Convert date, timerelative and avatar in displayable data. */
+ formating_data: function () {
//formating and add some fields for render
- this.datasets.date = session.web.format_value(this.datasets._date, {type:"datetime"});
- this.datasets.timerelative = $.timeago(this.datasets.date);
- if (this.datasets.type == 'email') {
- this.datasets.avatar = ('/mail/static/src/img/email_icon.png');
+ 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.datasets.avatar = mail.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', this.datasets.author_id[0]);
+ this.avatar = mail.ChatterUtils.get_image(this.session, 'res.partner', 'image_small', this.author_id[0]);
}
- for (var l in this.datasets.attachment_ids) {
- var attach = this.datasets.attachment_ids[l];
+ 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() {
+ start: function () {
this._super.apply(this, arguments);
this.expender();
- this.$el.hide().fadeIn(750);
+ this.$el.hide().fadeIn(750, function () {$(this).css('display', '');});
+ this.resize_img();
this.bind_events();
- this.create_thread();
+ 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);
},
/**
* Bind events in the widget. Each event is slightly described
* in the function. */
- bind_events: function() {
+ bind_events: function () {
var self = this;
// event: click on 'Attachment(s)' in msg
- this.$('a.oe_msg_view_attachments:first').on('click', function (event) {
- self.$('.oe_msg_attachments:first').toggle();
+ 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', 'a.oe_read', this.on_message_read_unread);
+ this.$el.on('click', '.oe_read', this.on_message_read_unread);
// event: click on icone 'UnRead' in header
- this.$el.on('click', 'a.oe_unread', this.on_message_read_unread);
+ this.$el.on('click', '.oe_unread', this.on_message_read_unread);
// event: click on 'Delete' in msg side menu
- this.$el.on('click', 'a.oe_msg_delete', this.on_message_delete);
+ this.$el.on('click', '.oe_msg_delete', this.on_message_delete);
// event: click on 'Reply' in msg
- this.$el.on('click', 'a.oe_reply', this.on_message_reply);
+ this.$el.on('click', '.oe_reply', this.on_message_reply);
// event: click on 'Vote' button
- this.$el.on('click', 'button.oe_msg_vote', this.on_vote);
- // event: click on 'Star' button
- this.$el.on('click', 'button.oe_mail_starbox', this.on_star);
+ 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);
},
- on_message_reply:function(event){
+ /* Call the on_compose_message on the thread of this message. */
+ on_message_reply:function (event) {
event.stopPropagation();
+ this.create_thread();
this.thread.on_compose_message();
return false;
},
- expender: function(){
- this.$('div.oe_msg_body:first').expander({
+ expender: function () {
+ this.$('.oe_msg_body:first').expander({
slicePoint: this.options.truncate_limit,
expandText: 'read more',
userCollapseText: '[^]',
@@ -611,52 +679,55 @@ openerp.mail = function(session) {
});
},
- create_thread: function(){
- var self=this;
- if(this.thread){
+ /**
+ * Instantiate the thread object of this message.
+ * Each message have only one thread.
+ */
+ create_thread: function () {
+ if (this.thread) {
return false;
}
-
/*create thread*/
- self.thread = new mail.Thread(self, {
- 'domain': self.domain,
+ this.thread = new mail.Thread(this, this, {
+ 'domain': this.domain,
'context':{
- 'default_model': self.datasets.model,
- 'default_res_id': self.datasets.res_id,
- 'default_parent_id': self.datasets.id
+ 'default_model': this.model,
+ 'default_res_id': this.res_id,
+ 'default_parent_id': this.id
},
- 'options': {
- 'thread' : self.options.thread,
- 'message' : self.options.message
- },
- 'datasets': self.datasets
+ 'options': this.options
}
);
/*insert thread in parent message*/
- self.thread.appendTo(self.$el.find('div.oe_thread_placeholder'));
+ this.thread.insertAfter(this.$el);
},
- animated_destroy: function(options) {
+ /**
+ * Fade out the message and his child thread.
+ * Then this object is destroyed.
+ */
+ animated_destroy: function (fadeTime) {
var self=this;
- //graphic effects
- if(options && options.fadeTime) {
- self.$el.fadeOut(options.fadeTime, function(){
- self.destroy();
- });
- } else {
- self.destroy();
+ this.$el.fadeOut(fadeTime, function () {
+ self.parent_thread.message_to_expandable(self);
+ });
+ if (this.thread) {
+ this.thread.$el.fadeOut(fadeTime);
}
},
+ /**
+ * Wait a confirmation for delete the message on the DB.
+ * Make an animate destroy
+ */
on_message_delete: function (event) {
event.stopPropagation();
if (! confirm(_t("Do you really want to delete this message?"))) { return false; }
- this.animated_destroy({fadeTime:250});
+ this.animated_destroy(150);
// delete this message and his childs
- var ids = [this.datasets.id].concat( this.get_child_ids() );
+ var ids = [this.id].concat( this.get_child_ids() );
this.ds_message.unlink(ids);
- this.animated_destroy();
return false;
},
@@ -664,117 +735,125 @@ openerp.mail = function(session) {
* @param {object} mouse envent
*/
on_message_read_unread: function (event) {
- // TDE note: code here seems complicated... just check that current message is read (value coming from server)
- // and send its opposite to set_message_read
event.stopPropagation();
- // if this message is read, all childs message display is read
- var ids = [this.datasets.id].concat( this.get_child_ids() );
- var read = $(event.srcElement).hasClass("oe_read");
- this.$el.removeClass("oe_mail_" + (read?"un":"") + "read").addClass("oe_mail_" + (read?"":"un") + "read");
+ var self=this;
- if( (read && this.options.thread.typeof_thread == 'inbox') ||
- (!read && this.options.thread.typeof_thread == 'archives')) {
- this.animated_destroy({fadeTime:250});
+ if ( (this.to_read && this.options.typeof_thread == 'inbox') ||
+ (!this.to_read && this.options.typeof_thread == 'archives')) {
+ this.animated_destroy(150);
}
- // TDE note: should have a context here
- this.ds_notification.call('set_message_read', [ids, read]);
+
+ // 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;
},
- /** browse message
+ /**
+ * 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){
+ browse_message: function (options) {
// goto the wall thread for launch browse
- if(!options._go_thread_wall) {
+ if (!options._go_thread_wall) {
options._go_thread_wall = true;
- for(var i in this.options.thread._parents[0].messages){
- var res=this.options.thread._parents[0].messages[i].browse_message(options);
- if(res) return res;
+ 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.datasets.id==options.id)
+ if (this.id==options.id)
return this;
- for(var i in this.thread.messages){
- if(this.thread.messages[i].thread){
+ 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;
+ if (res) return res;
}
}
return false;
},
- /* get all child message/thread id linked
+ /* get all child message id linked.
+ * @return array of id
*/
- get_child_ids: function(){
+ get_child_ids: function () {
var res=[]
- if(arguments[0]) res.push(this.datasets.id);
- if(this.thread){
+ 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.datasets.id]]).pipe(function(vote){
- // TDE note: to update, because vote_user_ids is about to disappear to be replaced by vote_nb (number of votes)
- self.datasets.has_voted=vote;
- if (!self.datasets.has_voted) {
- var votes=[];
- for(var i in self.datasets.vote_user_ids){
- if(self.datasets.vote_user_ids[i][0]!=self.datasets.session.uid)
- vote.push(self.datasets.vote_user_ids[i]);
- }
- self.datasets.vote_user_ids=votes;
- }
- else {
- self.datasets.vote_user_ids.push([self.session.uid, 'You']);
- }
+ 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();
});
return false;
},
- // Render vote Display template.
+ /**
+ * 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.$(".placeholder-mail-vote:first").empty();
- self.$(".placeholder-mail-vote:first").html(vote_element);
+ self.$(".oe_msg_vote:first").remove();
+ self.$(".oe_mail_vote_count:first").replaceWith(vote_element);
},
- // Stared/unstared + Render star.
+ /**
+ * add or remove a favorite (or starred) for a message and change class on the DOM
+ */
on_star: function (event) {
event.stopPropagation();
var self=this;
- var button = self.$('button.oe_mail_starbox:first');
- return this.ds_message.call('favorite_toggle', [[self.datasets.id]]).pipe(function(star){
- self.datasets.is_favorite=star;
- if(self.datasets.is_favorite){
- button.addClass('oe_stared');
+ 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_stared');
- if( self.options.thread.typeof_thread == 'stared' ) {
- self.animated_destroy({fadeTime:250});
+ button.removeClass('oe_starred');
+ if ( self.options.typeof_thread == 'stared' ) {
+ self.animated_destroy(150);
}
}
});
return false;
},
+ /**
+ * call on_message_delete on his parent thread
+ */
+ destroy: function () {
+
+ this._super();
+ this.parent_thread.on_message_detroy(this);
+
+ }
+
});
- /**
- * ------------------------------------------------------------
+ /**
+ * ------------------------------------------------------------
* Thread Widget
* ------------------------------------------------------------
*
@@ -799,23 +878,15 @@ openerp.mail = function(session) {
* @param {Object} [options]
* @param {Object} [message] read about mail.ThreadMessage object
* @param {Object} [thread]
- * @param {Boolean} [use_composer] use the advanced composer, or
- * the default basic textarea if not set
- * @param {Number} [expandable_number=5] number message show
- * for each click on "show more message"
- * @param {Number} [expandable_default_number=5] number message show
- * on begin before the first click on "show more message"
- * @param {Array [A,B]} [display_on_thread] display the threads (hierarchy)
- * for the thread level between A and B. -1 for no begin or no end.
- * All thread before A are insert in the root thread.
- * All thread after B are insert in parent thread on B level.
+ * @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, options) {
+ init: function (parent, datasets, options) {
this._super(parent);
this.domain = options.domain || [];
this.context = _.extend({
@@ -823,154 +894,132 @@ openerp.mail = function(session) {
default_res_id: 0,
default_parent_id: false }, options.context || {});
- // options
- this.options={
- 'thread' : {
- 'show_header_compose': (options.options.thread.show_header_compose != undefined ? options.options.thread.show_header_compose: false),
- 'use_composer': options.options.thread.use_composer || false,
- 'expandable_number': options.options.thread.expandable_number || 5,
- 'expandable_default_number': options.options.thread.expandable_default_number || 5,
- '_expandable_max': options.options.thread.expandable_default_number || 5,
- 'display_on_thread': options.options.thread.display_on_thread || [0,-1],
- 'typeof_thread': options.options.thread.typeof_thread || 'inbox',
- '_parents': (options.options.thread._parents != undefined ? options.options.thread._parents : []).concat( [this] )
- },
- 'message' : options.options.message
- };
+ this.options = options.options;
+ this.options._parents = (options.options._parents != undefined ? options.options._parents : []).concat( [this] );
// record options and data
this.parent_message= parent.thread!= undefined ? parent : false ;
- var param = options.datasets
- // datasets and internal vars
- this.datasets = {
- 'id' : param.id || false,
- 'model' : param.model || false,
- 'parent_id' : param.parent_id || false,
- 'is_private' : param.is_private || false,
- 'author_id' : param.author_id || false,
- 'thread_level' : (param.thread_level+1) || 0,
- 'partner_ids' : []
- };
-
- for(var i in param.partner_ids){
- if(param.partner_ids[i][0]!=(param.author_id ? param.author_id[0] : -1)){
- this.datasets.partner_ids.push(param.partner_ids[i]);
- }
- }
-
+ // data of this thread
+ this.id = datasets.id || false,
+ this.model = datasets.model || 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.ComposeMessage = false;
+ this.show_compose_message = this.options.show_compose_message && (this.options.show_reply_button > this.thread_level || !this.thread_level);
+
+ // object compose message
+ this.compose_message = false;
this.ds_thread = new session.web.DataSetSearch(this, this.context.default_model || 'mail.thread');
this.ds_message = new session.web.DataSetSearch(this, 'mail.message');
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
-
- this.list_ul = this.$('ul.oe_mail_thread_display:first');
- this.more_msg = this.$(">.oe_msg_more_message:first");
-
- this.display_user_avatar();
- var display_done = compose_done = false;
-
this.bind_events();
-
- if(this.options.thread._parents[0]==this){
- this.on_root_thread();
- }
-
- return display_done && compose_done;
},
- instantiate_ComposeMessage: function() {
- // add message composition form view
- this.ComposeMessage = new mail.ThreadComposeMessage(this,{
- 'context': this.context,
- 'datasets': this.datasets,
- 'options': this.options,
- 'show_attachment_delete': true,
- });
- this.ComposeMessage.appendTo(this.$(".oe_mail_thread_action:first"));
- },
-
- /* this method is runing for first parent thread
+ /* instantiate the compose message object and insert this on the DOM.
+ * The compose message is display in compact form.
*/
- on_root_thread: function(){
- var self=this;
- // fetch and display message, using message_ids if set
- this.message_fetch();
-
- $(document).scroll( self.on_scroll );
- $(window).resize( self.on_scroll );
- window.setTimeout( self.on_scroll, 500 );
-
- $(session.web.qweb.render('mail.wall_no_message', {})).appendTo(this.$('ul.oe_mail_thread_display'));
-
- this.instantiate_ComposeMessage();
- this.ComposeMessage.datasets.is_private=true;
-
- if(this.options.thread.show_header_compose){
- this.ComposeMessage.$el.show();
- //this.ComposeMessage.set_free_attachments();
+ instantiate_compose_message: function () {
+ // add message composition form view
+ if (!this.compose_message) {
+ this.compose_message = new mail.ThreadComposeMessage(this, this, {
+ 'context': this.context,
+ 'options': this.options,
+ });
+ if (!this.thread_level) {
+ // root view
+ 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.$el.addClass("oe_mail_root_thread");
},
/* 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();
- var message = this.messages[0];
- if(message && message.datasets.type=="expandable" && message.datasets.max_limit){
+ 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();
- if(pos.top){
+ if (pos.top) {
/* bottom of the screen */
var bottom = $(window).scrollTop()+$(window).height()+200;
- if(bottom - pos.top > 0){
+ if (bottom > pos.top) {
message.on_expandable();
}
}
-
}
},
/**
* Bind events in the widget. Each event is slightly described
* in the function. */
- bind_events: function() {
+ bind_events: function () {
var self = this;
- self.$('.oe_mail_compose_textarea .oe_more').click(function () { var p=$(this).parent(); p.find('.oe_more_hidden, .oe_hidden').show(); p.find('.oe_more').hide(); });
- self.$('.oe_mail_compose_textarea .oe_more_hidden').click(function () { var p=$(this).parent(); p.find('.oe_more_hidden, .oe_hidden').hide(); p.find('.oe_more').show(); });
+ 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);
},
- /* get all child message/thread id linked
+ /**
+ *show all the partner list of this parent message
*/
- get_child_ids: function(){
+ on_show_recipients: function () {
+ var p=$(this).parent();
+ p.find('.oe_more_hidden, .oe_hidden').show();
+ p.find('.oe_more').hide();
+ },
+
+ /**
+ *hide a part of the partner list of this parent message
+ */
+ on_hide_recipients: function () {
+ var p=$(this).parent();
+ p.find('.oe_more_hidden, .oe_hidden').hide();
+ p.find('.oe_more').show();
+ },
+
+ /* 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.datasets.id); });
+ _(this.get_childs()).each(function (val, key) { res.push(val.id); });
return res;
},
- /* get all child message/thread linked
+ /* get all child message/thread linked.
+ * @param {int} nb_thread_level, number of traversed thread level for this search
+ * @return array of thread object
*/
- get_childs: function(nb_thread_level){
+ get_childs: function (nb_thread_level) {
var res=[];
- if(arguments[1]) res.push(this);
- if(isNaN(nb_thread_level) || nb_thread_level>0){
+ if (arguments[1]) res.push(this);
+ if (isNaN(nb_thread_level) || nb_thread_level>0) {
_(this.messages).each(function (val, key) {
- if(val.thread){
- res = res.concat( val.thread.get_childs((isNaN(nb_thread_level) ? null : nb_thread_level-1), true) )
+ if (val.thread) {
+ res = res.concat( val.thread.get_childs((isNaN(nb_thread_level) ? undefined : nb_thread_level-1), true) );
}
});
}
return res;
},
- /** browse thread
+ /**
+ *search a thread in all thread and child thread.
+ * This method return an object thread.
* @param {object}{int} option.id
* @param {object}{string} option.model
* @param {object}{boolean} option._go_thread_wall
@@ -979,252 +1028,469 @@ openerp.mail = function(session) {
* return the top thread (wall) if no thread found
* @return thread object
*/
- browse_thread: function(options){
+ browse_thread: function (options) {
// goto the wall thread for launch browse
- if(!options._go_thread_wall) {
+ if (!options._go_thread_wall) {
options._go_thread_wall = true;
- return this.options.thread._parents[0].browse_thread(options);
+ return this.options._parents[0].browse_thread(options);
}
- if(this.datasets.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});
- if(res) return res;
- }
- }
-
- //if option default_return_top_thread, return the top if no found thread
- if(options.default_return_top_thread){
- return this;
- }
-
- return false;
- },
-
- /** browse 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){
- if(this.options.thread._parents[0].messages[0])
- return this.options.thread._parents[0].messages[0].browse_message(options);
- },
-
- /* this function is launch when a user click on "Reply" button
- */
- on_compose_message: function(){
- if(!this.ComposeMessage){
- this.instantiate_ComposeMessage();
- }
- this.ComposeMessage.$el.toggle();
- return false;
- },
-
- /** Fetch messages
- * @param {Bool} initial_mode: initial mode: try to use message_data or
- * message_ids, if nothing available perform a message_read; otherwise
- * directly perform a message_read
- * @param {Array} replace_domain: added to this.domain
- * @param {Object} replace_context: added to this.context
- */
- message_fetch: function (initial_mode, replace_domain, replace_context, ids, callback) {
- var self = this;
-
- // initial mode: try to use message_data or message_ids
- if (initial_mode && this.options.thread.message_data) {
- return this.create_message_object(this.options.message_data);
- }
- // domain and context: options + additional
- fetch_domain = replace_domain ? replace_domain : this.domain;
- fetch_context = replace_context ? replace_context : this.context;
- var message_loaded = [this.datasets.id||0].concat( self.options.thread._parents[0].get_child_ids() );
-
- return this.ds_message.call('message_read', [ids, fetch_domain, message_loaded, fetch_context, this.context.default_parent_id || undefined]
- ).then(this.proxy('switch_new_message'));
- },
-
- /* create record object and linked him
- */
- create_message_object: function (data) {
- var self = this;
-
- if(data.type=='expandable'){
- var message = new mail.ThreadExpandable(self, {
- 'domain': data.domain,
- 'context': {
- 'default_model': data.model || self.context.default_model,
- 'default_res_id': data.res_id || self.context.default_res_id,
- 'default_parent_id': self.datasets.id },
- 'datasets': data
- });
- } else {
- var message = new mail.ThreadMessage(self, {
- 'domain': data.domain,
- 'context': {
- 'default_model': data.model,
- 'default_res_id': data.res_id,
- 'default_parent_id': data.id },
- 'options':{
- 'thread': self.options.thread,
- 'message': self.options.message
- },
- 'datasets': _.extend(data, {'thread_level': self.datasets.thread_level})
- });
- var data = _.extend(data, {'thread_level': self.datasets.thread_level});
- }
-
- // check if the message is already create
- for(var i in self.messages){
- if(self.messages[i].datasets.id==message.datasets.id){
- self.messages[i].destroy();
- self.messages[i]=self.insert_message(message);
- return true;
- }
- }
- self.messages.push( self.insert_message(message) );
- },
-
- /** Displays a message or an expandable message */
- insert_message: function (message) {
- var self=this;
-
- this.$("li.oe_wall_no_message").remove();
-
- // insert on hierarchy display => insert in self child
- var thread_messages = self.messages;
- var thread = self;
- var flat = false;
- var hierarchy = self.options.thread.display_on_thread;
- if( hierarchy[0] < 0 ||
- hierarchy[0] > self.datasets.thread_level ||
- (hierarchy[1]>0 && hierarchy[1] < self.datasets.thread_level) ) {
-
- var flat = true;
-
- if(hierarchy[0]<0){
-
- // all is in flat mode
- thread = self.options.thread._parents[0];
- var nb_thread_level = null;
-
- } else if(hierarchy[0] > self.datasets.thread_level) {
-
- // list all childs messages for flat display before the hierarchy
- thread = self.options.thread._parents[0];
- var nb_thread_level = hierarchy[0];
-
- } else if(hierarchy[1] < self.datasets.thread_level) {
-
- // list all childs messages for flat display after the hierarchy
- thread = self.options.thread._parents[hierarchy[1]];
- var nb_thread_level = hierarchy[1]>0 ? hierarchy[1]-hierarchy[0] : null;
- } else {
-
- thread = self.options.thread._parents[0];
- var nb_thread_level = null;
- }
-
- var thread_messages = [];
- _(thread.get_childs( nb_thread_level )).each(function (val, key) { thread_messages.push(val.parent_message); });
- }
-
-
- // check older and newer message for insert
- var parent_newer = false;
- var parent_older = false;
- if ( message.datasets.id > 0 ){
- for(var i in thread_messages){
- if(thread_messages[i].datasets.id > message.datasets.id){
- if(!parent_newer || parent_newer.datasets.id>=thread_messages[i].datasets.id)
- parent_newer = thread_messages[i];
- } else if(thread_messages[i].datasets.id>0 && thread_messages[i].datasets.id < message.datasets.id) {
- if(!parent_older || parent_older.id=1);
+ //if option default_return_top_thread, return the top if no found thread
+ if (options.default_return_top_thread) {
+ return this;
+ }
- if(parent_older){
- if(sort){
- message.insertBefore(parent_older.$el);
- } else {
- message.insertAfter(parent_older.$el);
+ 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 message object
+ */
+ browse_message: function (options) {
+ if (this.options._parents[0].messages[0])
+ return this.options._parents[0].messages[0].browse_message(options);
+ },
+
+ /**
+ *If compose_message doesn't exist, instantiate the compose message.
+ * Call the on_compose_expandable method to allow the user to write his message.
+ * (Is call when a user click on "Reply" button)
+ */
+ on_compose_message: function () {
+ this.instantiate_compose_message();
+ this.compose_message.on_compose_expandable();
+ },
+
+ /**
+ *display the message "there are no message" on the thread
+ */
+ 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);
+ }
+ no_message.appendTo(this.$el);
+ },
+
+ /**
+ *make a request to read the message (calling RPC to "message_read").
+ * The result of this method is send to the switch message for sending ach message to
+ * his parented object thread.
+ * @param {Array} replace_domain: added to this.domain
+ * @param {Object} replace_context: added to this.context
+ * @param {Array} ids read (if the are some ids, the method don't use the domain)
+ */
+ message_fetch: function (replace_domain, replace_context, ids, callback) {
+ 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'));
+ },
+
+ /**
+ *create the message object and attached on this thread.
+ * When the message object is create, this method call insert_message for,
+ * displaying this message on the DOM.
+ * @param : {object} data from calling RPC to "message_read"
+ */
+ create_message_object: function (data) {
+ var self = this;
+
+ var data = _.extend(data, {'thread_level': data.thread_level ? data.thread_level : self.thread_level});
+ data.options = _.extend(self.options, data.options);
+
+ if (data.type=='expandable') {
+ var message = new mail.ThreadExpandable(self, data, {
+ '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, {
+ '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) {
+ self.messages[i].destroy();
}
- } else if(parent_newer){
- if(sort){
- message.insertAfter(parent_newer.$el);
+ }
+ self.messages.push( message );
+
+ return message;
+ },
+
+ /**
+ *insert the message on the DOM.
+ * All message (and expandable message) are sorted. The method get the
+ * older and newer message to insert the message (before, after).
+ * If there are no older or newer, the message is prepend or append to
+ * the thread (parent object or on root thread for flat view).
+ * The sort is define by the thread_level (O for newer on top).
+ * @param : {object} ThreadMessage object
+ */
+ insert_message: function (message, dom_insert_after) {
+ var self=this;
+
+ if (this.show_compose_message && this.options.show_compact_message) {
+ this.instantiate_compose_message();
+ this.compose_message.do_show_compact();
+ }
+
+ this.$('.oe_wall_no_message').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.insertBefore(parent_newer.$el);
+
+ message.prependTo(self.$el);
}
} else {
- if(sort && message.id > 0){
- message.prependTo(thread.list_ul);
+ 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(thread.list_ul);
+
+ message.appendTo(self.$el);
+
}
}
return message
},
-
- display_user_avatar: function () {
- var avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
- return this.$('img.oe_mail_icon').attr('src', avatar);
- },
- /* Send the records to his parent thread */
- switch_new_message: function(records) {
+ /**
+ *get the parent thread of the messages.
+ * Each message is send to his parent object (or parent thread flat mode) for creating the object message.
+ * @param : {Array} datas from calling RPC to "message_read"
+ */
+ switch_new_message: function (records) {
var self=this;
- _(records).each(function(record){
- self.browse_thread({
+ _(records).each(function (record) {
+ var thread = self.browse_thread({
'id': record.parent_id,
'default_return_top_thread':true
- }).create_message_object( record );
+ });
+ // create object and attach to the thread object
+ var message = thread.create_message_object( record );
+ // insert the message on dom
+ thread.insert_message( message );
});
},
+
+ /**
+ * this method is call when the widget of a message or an expandable message is destroy
+ * in this thread. The this.messages array is filter to remove this message
+ */
+ on_message_detroy: function (message) {
+
+ this.messages = _.filter(this.messages, function (val) { return !val.isDestroyed(); });
+
+ },
+
+ /**
+ * Convert a destroyed message into a expandable message
+ */
+ message_to_expandable: function (message) {
+
+ if (!this.thread_level || message.isDestroyed()) {
+ message.destroy();
+ return false;
+ }
+
+ var messages = _.sortBy( this.messages, function (val) { return val.id; });
+ var it = _.indexOf( messages, message );
+
+ var msg_up = messages[it-1];
+ var msg_down = messages[it+1];
+
+ var message_dom = [ ["id", "=", message.id] ];
+
+ if ( msg_up && msg_up.type == "expandable" && msg_down && msg_down.type == "expandable") {
+ // concat two expandable message and add this message to this dom
+ msg_up.domain = mail.ChatterUtils.expand_domain( msg_up.domain );
+ msg_down.domain = mail.ChatterUtils.expand_domain( msg_down.domain );
+
+ msg_down.domain = ['|','|'].concat( msg_up.domain ).concat( message_dom ).concat( msg_down.domain );
+
+ if ( !msg_down.max_limit ) {
+ msg_down.nb_messages += 1 + msg_up.nb_messages;
+ }
+
+ msg_up.$el.remove();
+ msg_up.destroy();
+
+ msg_down.reinit();
+
+ } else if ( msg_up && msg_up.type == "expandable") {
+ // concat preview expandable message and this message to this dom
+ msg_up.domain = mail.ChatterUtils.expand_domain( msg_up.domain );
+ msg_up.domain = ['|'].concat( msg_up.domain ).concat( message_dom );
+
+ msg_up.nb_messages++;
+
+ msg_up.reinit();
+
+ } else if ( msg_down && msg_down.type == "expandable") {
+ // concat next expandable message and this message to this dom
+ msg_down.domain = mail.ChatterUtils.expand_domain( msg_down.domain );
+ msg_down.domain = ['|'].concat( msg_down.domain ).concat( message_dom );
+
+ // it's maybe a message expandable for the max limit read message
+ if ( !msg_down.max_limit ) {
+ msg_down.nb_messages++;
+ }
+
+ msg_down.reinit();
+
+ } 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,
+ 'thread_level': message.thread_level,
+ 'parent_id': message.parent_id,
+ '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,
+ });
+
+ // add object on array and DOM
+ this.messages.push(expandable);
+ expandable.insertAfter(message.$el);
+ }
+
+ // destroy message
+ message.destroy();
+
+ return true;
+ },
});
-
- /**
+ /**
+ * ------------------------------------------------------------
+ * mail : root Widget
* ------------------------------------------------------------
+ *
+ * This widget handles the display of messages with thread options. Its main
+ * use is to receive a context and a domain, and to delegate the message
+ * fetching and displaying to the Thread widget.
+ */
+ session.web.client_actions.add('mail.Widget', 'session.mail.Widget');
+ mail.Widget = session.web.Widget.extend({
+ template: 'mail.Widget',
+
+ /**
+ * @param {Object} parent parent
+ * @param {Array} [domain]
+ * @param {Object} [context] context of the thread. It should
+ * contain at least default_model, default_res_id. Please refer to
+ * the compose_message widget for more information about it.
+ * ... @param {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 {int} [display_indented_thread] number thread level to indented threads.
+ * other are on flat mode
+ *... @param {Boolean} [show_compose_message] allow to display the composer
+ *... @param {Boolean} [show_compact_message] display the compact message on the thread
+ * when the user clic on this compact mode, the composer is open
+ *... @param {Array} [message_ids] List of ids to fetch by the root thread.
+ * When you use this option, the domain is not used for the fetch root.
+ * @param {String} [no_message] Message to display when there are no message
+ */
+ init: function (parent, options) {
+ this._super(parent);
+ this.domain = options.domain || [];
+ this.context = options.context || {};
+ this.search_results = {'domain': [], 'context': {}, 'groupby': {}};
+
+ this.options = _.extend({
+ 'typeof_thread' : 'inbox',
+ 'display_indented_thread' : -1,
+ 'show_reply_button' : -1,
+ 'show_read_unread_button' : -1,
+ 'truncate_limit' : 250,
+ 'show_record_name' : false,
+ 'show_compose_message' : false,
+ 'show_compact_message' : false,
+ 'message_ids': undefined,
+ 'no_message': false
+ }, options);
+
+ 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;
+ }
+
+ },
+
+ start: function (options) {
+ this._super.apply(this, arguments);
+ this.message_render();
+ this.bind_events();
+ },
+
+
+ /**
+ *Create the root thread and display this object in the DOM.
+ * Call the no_message method then c all the message_fetch method
+ * of this root thread to display the messages.
+ */
+ message_render: function (search) {
+
+ this.thread = new mail.Thread(this, {}, {
+ 'domain' : this.domain,
+ 'context' : this.context,
+ 'options': this.options,
+ });
+
+ 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) {
+ 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();
+ }
+ }
+ },
+
+ 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 );
+ }
+ }
+ });
+
+
+ /**
+ * ------------------------------------------------------------
* mail_thread Widget
* ------------------------------------------------------------
*
* This widget handles the display of messages on a document. Its main
* use is to receive a context and a domain, and to delegate the message
* fetching and displaying to the Thread widget.
+ * Use Help on the field to display a custom "no message loaded"
*/
session.web.form.widgets.add('mail_thread', 'openerp.mail.RecordThread');
mail.RecordThread = session.web.form.AbstractField.extend({
template: 'mail.record_thread',
- init: function() {
+ init: function () {
this._super.apply(this, arguments);
this.options.domain = this.options.domain || [];
this.options.context = {'default_model': 'mail.thread', 'default_res_id': false};
},
- start: function() {
+ start: function () {
this._super.apply(this, arguments);
// NB: check the actual_mode property on view to know if the view is in create mode anymore
this.view.on("change:actual_mode", this, this._check_visibility);
this._check_visibility();
},
- _check_visibility: function() {
+ _check_visibility: function () {
this.$el.toggle(this.view.get("actual_mode") !== "create");
},
- render_value: function() {
+ 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();
@@ -1237,39 +1503,37 @@ openerp.mail = function(session) {
default_is_private: false });
// update domain
var domain = this.options.domain.concat([['model', '=', this.view.model], ['res_id', '=', this.view.datarecord.id]]);
- // create and render Thread widget
- // TDE note: replace message_is_follower by a check in message_follower_ids, as message_is_follower is not used in views anymore
- var show_header_compose = this.view.is_action_enabled('edit') ||
+
+ var show_compose_message = this.view.is_action_enabled('edit') ||
(this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value());
- if(this.thread){
- this.thread.destroy();
+ var message_ids = this.getParent().fields.message_ids && this.getParent().fields.message_ids.get_value();
+
+ if (this.root) {
+ this.root.destroy();
}
- this.thread = new mail.Thread(self, {
- 'domain': domain,
- 'context': this.options.context,
- 'options':{
- 'thread':{
- 'show_header_compose': show_header_compose,
- 'use_composer': show_header_compose,
- 'display_on_thread':[-1,-1]
- },
- 'message':{
- 'show_reply': [-1,-1],
- 'show_read_unread': [-1,-1],
- 'show_dd_delete': false
- }
- },
- 'datasets': {},
+ // create and render Thread widget
+ this.root = new mail.Widget(this, {
+ '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
}
);
- return this.thread.appendTo( this.$('.oe_mail_wall_threads:first') );
+
+ return this.root.replace(this.$('.oe_mail-placeholder'));
},
});
- /**
- * ------------------------------------------------------------
+ /**
+ * ------------------------------------------------------------
* Wall Widget
* ------------------------------------------------------------
*
@@ -1287,8 +1551,6 @@ openerp.mail = function(session) {
* @param {Array} [options.domain] domain on the Wall
* @param {Object} [options.context] context, is an object. It should
* contain default_model, default_res_id, to give it to the threads.
- * @param {Number} [options.thread_level] number of thread levels to display
- * 0 being flat.
*/
init: function (parent, options) {
this._super(parent);
@@ -1305,7 +1567,7 @@ openerp.mail = function(session) {
var thread_displayed = this.message_render();
this.options.domain = this.options.domain.concat(this.search_results['domain']);
this.bind_events();
- return (searchview_ready && thread_displayed);
+ return $.when(searchview_ready, thread_displayed);
},
/**
@@ -1328,7 +1590,7 @@ openerp.mail = function(session) {
* @param {Array} contexts
* @param {Array} groupbys
*/
- do_searchview_search: function(domains, contexts, groupbys) {
+ do_searchview_search: function (domains, contexts, groupbys) {
var self = this;
this.rpc('/web/session/eval_domain_and_context', {
domains: domains || [],
@@ -1337,51 +1599,43 @@ openerp.mail = function(session) {
}).then(function (results) {
self.search_results['context'] = results.context;
self.search_results['domain'] = results.domain;
- self.thread.destroy();
+ self.root.destroy();
return self.message_render();
});
},
/**
- * Display the threads
+ *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.thread = new mail.Thread(this, {
- 'domain' : domain,
- 'context' : context,
- 'options': {
- 'thread' :{
- 'use_composer': true,
- 'show_header_compose': false,
- 'typeof_thread': context.typeof_thread || 'inbox',
- 'display_on_thread': [0,1]
- },
- 'message': {
- 'show_reply': [0,0],
- 'show_read_unread': [0,-1],
- 'show_dd_delete': false,
- },
- },
- 'datasets': {},
+ this.root = new mail.Widget(this, {
+ '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.thread.appendTo( this.$('.oe_mail_wall_threads:first') );
+ return this.root.replace(this.$('.oe_mail-placeholder'));
},
- bind_events: function(){
+ bind_events: function () {
var self=this;
- this.$("button.oe_write_full:first").click(function(){ self.thread.ComposeMessage.on_compose_fullmail(); });
- this.$("button.oe_write_onwall:first").click(function(){ self.thread.ComposeMessage.$el.toggle(); });
+ this.$(".oe_write_full").click(function(){ self.root.thread.compose_message.on_compose_fullmail(); });
+ this.$(".oe_write_onwall").click(function(){ self.root.thread.on_compose_message(); });
}
});
/**
- * ------------------------------------------------------------
+ * ------------------------------------------------------------
* UserMenu
* ------------------------------------------------------------
*
@@ -1401,13 +1655,13 @@ openerp.mail = function(session) {
};
},
- start: function(parent, params) {
+ start: function (parent, params) {
var self = this;
this.$el.on('click', 'button', self.on_compose_message );
this._super(parent, params);
},
- on_compose_message: function(event){
+ on_compose_message: function (event) {
event.stopPropagation();
var action = {
type: 'ir.actions.act_window',
@@ -1417,7 +1671,11 @@ openerp.mail = function(session) {
action_from: 'mail.ThreadComposeMessage',
views: [[false, 'form']],
target: 'new',
- context: this.options.context,
+ context: _.extend(this.options.context, {
+ 'default_model': this.context.default_model,
+ 'default_res_id': this.context.default_res_id,
+ 'default_content_subtype': 'html',
+ }),
};
session.client.action_manager.do_action(action);
},
@@ -1425,7 +1683,7 @@ openerp.mail = function(session) {
});
session.web.UserMenu = session.web.UserMenu.extend({
- start: function(parent, params) {
+ start: function (parent, params) {
var render = new session.web.ComposeMessageTopButton();
render.insertAfter(this.$el);
this._super(parent, params);
diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js
index 806bf28e7b2..a13d2048bf3 100644
--- a/addons/mail/static/src/js/mail_followers.js
+++ b/addons/mail/static/src/js/mail_followers.js
@@ -24,10 +24,9 @@ openerp_mail_followers = function(session, mail) {
init: function() {
this._super.apply(this, arguments);
- this.options.image = this.node.attrs.image || 'image_small';
- this.options.title = this.node.attrs.title || 'Followers';
- this.options.comment = this.node.attrs.help || false;
- this.options.displayed_nb = this.node.attrs.displayed_nb || 10;
+ this.image = this.node.attrs.image || 'image_small';
+ this.comment = this.node.attrs.help || false;
+ this.displayed_nb = this.node.attrs.displayed_nb || 10;
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');
@@ -61,7 +60,7 @@ openerp_mail_followers = function(session, mail) {
self.do_unfollow();
});
// event: click on a subtype, that (un)subscribe for this subtype
- this.$el.on('click', 'ul.oe_subtypes input', self.do_update_subscription);
+ this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
// event: click on 'invite' button, that opens the invite wizard
this.$('.oe_invite').on('click', function (event) {
action = {
@@ -115,20 +114,24 @@ openerp_mail_followers = function(session, mail) {
self.message_is_follower = (_.indexOf(self.get('value'), pid) != -1);
}).pipe(self.proxy('display_generic'));
},
-
+ _format_followers: function(count){
+ // TDE note: why redefining _t ?
+ function _t(str) { return str; }
+ var str = '';
+ if(count <= 0){
+ str = _t('No followers');
+ }else if(count === 1){
+ str = _t('One follower');
+ }else{
+ str = ''+count+' '+_t('followers');
+ }
+ return str;
+ },
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
var self = this;
- var node_user_list = this.$('ul.oe_mail_followers_display').empty();
- // format content: Followers (You and 0 other) // Followers (3)
- var content = this.options.title;
- if (this.message_is_follower) {
- content += ' (You and ' + (this.get('value').length-1) + ' other)';
- }
- else {
- content += ' (' + this.get('value').length + ')'
- }
- this.$('div.oe_mail_recthread_followers h4').html(content);
+ var node_user_list = this.$('.oe_follower_list').empty();
+ this.$('.oe_follower_title').html(this._format_followers(this.get('value').length));
},
/** Display the followers */
@@ -137,16 +140,17 @@ openerp_mail_followers = function(session, mail) {
records = records || [];
this.message_is_follower = this.set_is_follower(records);
// clean and display title
- var node_user_list = this.$('ul.oe_mail_followers_display').empty();
- this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
+ var node_user_list = this.$('.oe_follower_list').empty();
+ this.$('.oe_follower_title').html(this._format_followers(records.length));
// truncate number of displayed followers
- truncated = records.splice(0, this.options.displayed_nb);
+ truncated = records.splice(0, this.displayed_nb);
_(truncated).each(function (record) {
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
});
+ // FVA note: be sure it is correctly translated
if (truncated.length < records.length) {
- $('And ' + (records.length - truncated.length) + ' more. ').appendTo(node_user_list);
+ $('And ' + (records.length - truncated.length) + ' more.
').appendTo(node_user_list);
}
},
@@ -172,7 +176,7 @@ openerp_mail_followers = function(session, mail) {
/** Fetch subtypes, only if current user is follower */
fetch_subtypes: function () {
- var subtype_list_ul = this.$('.oe_subtypes').empty();
+ 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'));
@@ -181,13 +185,12 @@ openerp_mail_followers = function(session, mail) {
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data) {
var self = this;
- var subtype_list_ul = this.$('.oe_subtypes');
- var records = data[this.view.datarecord.id].message_subtype_data;
-
+ var subtype_list_ul = this.$('.oe_subtype_list');
+ var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
- $(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('ul.oe_subtypes') );
+ $(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
});
},
@@ -210,7 +213,7 @@ openerp_mail_followers = function(session, mail) {
var self = this;
var checklist = new Array();
- _(this.$('.oe_mail_recthread_actions input[type="checkbox"]')).each(function (record) {
+ _(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
}
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index fbb64cc6772..caa85cd3deb 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -1,33 +1,46 @@
+
+
+
+
+
+
-
-
-
-
-
-