summaryrefslogtreecommitdiff
path: root/addons/mail
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-02-12 18:34:43 +0100
committerChristophe Simonis <chs@odoo.com>2018-02-12 18:34:43 +0100
commitadeea2a88db4443b2b426877a28bb1471c2f5db1 (patch)
tree2f43fecc9649947d3061e8c482219fa167b3385a /addons/mail
parent317f5f11112266128ac2292ecfafcd92cda74fd0 (diff)
parent0f02dcb63918228d48df50f2d5805b7fda25c14a (diff)
[MERGE] forward port branch saas-11.2 up to 0f02dcb6391
Diffstat (limited to 'addons/mail')
-rw-r--r--addons/mail/data/mail_data.xml12
-rw-r--r--addons/mail/i18n/de.po28
-rw-r--r--addons/mail/i18n/zh_CN.po2
-rw-r--r--addons/mail/models/mail_message.py5
-rw-r--r--addons/mail/models/mail_template.py39
-rw-r--r--addons/mail/models/mail_thread.py35
-rw-r--r--addons/mail/models/res_partner.py7
-rw-r--r--addons/mail/static/src/js/composer.js6
-rw-r--r--addons/mail/static/src/js/discuss.js35
-rw-r--r--addons/mail/static/src/js/extended_chat_window.js3
-rw-r--r--addons/mail/static/src/js/services/chat_manager.js62
-rw-r--r--addons/mail/static/src/js/services/chat_window_manager.js26
-rw-r--r--addons/mail/static/src/js/systray.js51
-rw-r--r--addons/mail/static/src/js/thread_field.js5
-rw-r--r--addons/mail/static/tests/chat_window_tests.js12
-rw-r--r--addons/mail/static/tests/chatter_tests.js28
-rw-r--r--addons/mail/static/tests/discuss_mobile_tests.js8
-rw-r--r--addons/mail/static/tests/helpers/test_utils.js14
-rw-r--r--addons/mail/static/tests/systray_tests.js6
19 files changed, 196 insertions, 188 deletions
diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml
index a6c9b62e019..fa48b112aa8 100644
--- a/addons/mail/data/mail_data.xml
+++ b/addons/mail/data/mail_data.xml
@@ -68,12 +68,12 @@
<div>
<div t-if="has_button_access" itemscope="itemscope" itemtype="http://schema.org/EmailMessage">
<div itemprop="potentialAction" itemscope="itemscope" itemtype="http://schema.org/ViewAction">
- <link itemprop="target" href="${ctx['button_access']['url']}"/>
- <link itemprop="url" href="${ctx['button_access']['url']}"/>
- <meta itemprop="name" content="View ${ctx['model_name']}"/>
+ <link itemprop="target" t-att-href="button_access['url']"/>
+ <link itemprop="url" t-att-href="button_access['url']"/>
+ <meta itemprop="name" t-att-content="button_access['title']"/>
</div>
</div>
-<div t-if="has_button_access or len(actions) &gt; 0 or is_notification"
+<div t-if="has_button_access or len(actions) &gt; 0 or not is_discussion"
summary="o_mail_notification" style="padding: 0px; width:600px;">
<table cellspacing="0" cellpadding="0" border="0" style="width: 600px; margin-top: 5px;">
<tbody><tr>
@@ -116,9 +116,9 @@
<p style="color: #555555;">
Sent by
<a t-if="website_url" t-att-href="website_url" style="text-decoration:none; color: #875A7B;">
- <span t-esc="company_name"/>
+ <span t-esc="company.name"/>
</a>
- <span t-if="not website_url" t-esc="company_name"/>
+ <span t-if="not website_url" t-esc="company.name"/>
using
<a target="_blank" href="https://www.odoo.com" style="text-decoration:none; color: #875A7B;">Odoo</a>.
</p>
diff --git a/addons/mail/i18n/de.po b/addons/mail/i18n/de.po
index 6a7b1f181ec..065a45f4099 100644
--- a/addons/mail/i18n/de.po
+++ b/addons/mail/i18n/de.po
@@ -4,14 +4,14 @@
#
# Translators:
# Ermin Trevisan <trevi@twanda.com>, 2017
-# Martin Trigaux <mat@odoo.com>, 2017
+# Martin Trigaux, 2017
# JackTheHunter <Maximilian.N98@gmail.com>, 2017
# Markus Menner <markus.menner@axxelia.com>, 2017
-# Wolfgang Taferner <wolfgang.transifex@service.wt-io-it.at>, 2017
+# Wolfgang Taferner, 2017
# darenkster <inactive+darenkster@transifex.com>, 2017
# Walter Müller <inactive+WILL.I.AM@transifex.com>, 2017
# Martin K <martinkuenz@gmail.com>, 2017
-# Renzo Meister <info@jamotion.ch>, 2017
+# Renzo Meister, 2017
# Ralf Hilgenstock <rh@dialoge.info>, 2017
# Anja Funk <anja.funk@afimage.de>, 2017
# Leon Fischer <leon.fischer621@gmail.com>, 2017
@@ -19,28 +19,29 @@
# key six <mobile.umts@gmail.com>, 2017
# Tina Rittmüller <tr@ife.de>, 2017
# Marvin Elia Hoppe <marvin_elia@web.de>, 2017
-# Niki Waibel <niki.waibel@gmail.com>, 2017
+# Niki Waibel, 2017
# Mathias Neef <mn@copado.de>, 2017
# Bülent Tiknas <btiknas@googlemail.com>, 2017
# Thomas Neu <t.neu@runbox.com>, 2017
-# Tom <github@strohmeier.email>, 2017
+# Tom, 2017
# Henry Mineehen <info@mineehen.de>, 2017
# Christian Körner <fipes.de@gmail.com>, 2017
-# Andi <flimflam@salasar.de>, 2017
+# Andi, 2017
# Katharina Moritz <kmo@e2f.com>, 2017
# AppleMentalist <felix.schnatbaum@gmail.com>, 2017
# Constantin Ehrenstein <transifex@skiller.eu>, 2017
# DE R1 <e2fes1@hotmail.com>, 2017
# DE T1 <e2f8846@yahoo.com>, 2017
# Malte Jansen <work@maltejansen.de>, 2017
-# Dejan Dinic <dejan.dinic@bluewin.ch>, 2017
+# Dejan Dinic, 2017
+# e2f <projects@e2f.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-13 14:36+0000\n"
"PO-Revision-Date: 2017-12-13 14:36+0000\n"
-"Last-Translator: Dejan Dinic <dejan.dinic@bluewin.ch>, 2017\n"
+"Last-Translator: e2f <projects@e2f.com>, 2018\n"
"Language-Team: German (https://www.transifex.com/odoo/teams/41243/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -311,7 +312,7 @@ msgstr ""
#: code:addons/mail/static/src/xml/activity.xml:49
#, python-format
msgid ", due on"
-msgstr ""
+msgstr ", fällig am"
#. module: mail
#. openerp-web
@@ -2962,7 +2963,7 @@ msgstr "Zuhörer"
#. module: mail
#: model:ir.model,name:mail.model_mail_channel_partner
msgid "Listeners of a Channel"
-msgstr ""
+msgstr "Hörer eines Kanals"
#. module: mail
#: selection:mail.channel,channel_type:0
@@ -4888,6 +4889,9 @@ msgid ""
"Specify a model if the activity should be specific to a model and not "
"available when managing activities for other models."
msgstr ""
+"Spezifizieren Sie ein Modell, wenn die Aktivität für ein Modell spezifisch "
+"sein und beim Verwalten von Tätigkeiten für andere Modelle nicht verfügbar "
+"sein soll."
#. module: mail
#. openerp-web
@@ -5593,7 +5597,7 @@ msgstr "Ansicht"
#: code:addons/mail/models/mail_thread.py:668
#, python-format
msgid "View %s"
-msgstr ""
+msgstr "%s ansehen"
#. module: mail
#. openerp-web
@@ -5877,7 +5881,7 @@ msgstr "Nicht richtig konfigurierter alias "
#: code:addons/mail/models/mail_alias.py:278
#, python-format
msgid "incorrectly configured alias (unknown reference record)"
-msgstr ""
+msgstr "Nicht richtig konfigurierter Alias (unbekannter Referenzdatensatz)"
#. module: mail
#: model:ir.model,name:mail.model_ir_actions_server
diff --git a/addons/mail/i18n/zh_CN.po b/addons/mail/i18n/zh_CN.po
index 2836fe7d31d..8e88042d498 100644
--- a/addons/mail/i18n/zh_CN.po
+++ b/addons/mail/i18n/zh_CN.po
@@ -5543,7 +5543,7 @@ msgstr "关注者"
#: code:addons/mail/static/src/xml/activity.xml:30
#, python-format
msgid "for"
-msgstr ""
+msgstr "由于"
#. module: mail
#: model:ir.ui.view,arch_db:mail.message_origin_link
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 0d855dd15de..724f78ed827 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -749,9 +749,10 @@ class Message(models.Model):
'datas_fname': name,
'res_model': 'mail.message',
})
+ attachment.generate_access_token()
values['attachment_ids'].append((4, attachment.id))
- data_to_url[key] = '/web/image/%s' % attachment.id
- return '%s%s alt="%s"' % (data_to_url[key], match.group(3), name)
+ data_to_url[key] = ['/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token), name]
+ return '%s%s alt="%s"' % (data_to_url[key][0], match.group(3), data_to_url[key][1])
values['body'] = _image_dataurl.sub(base64_to_boundary, tools.ustr(values['body']))
# delegate creation of tracking after the create as sudo to avoid access rights issues
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index 2407e58fdbd..17cea52deac 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -315,45 +315,8 @@ class MailTemplate(models.Model):
# ----------------------------------------
@api.model
- def _replace_local_links(self, html):
- """ Post-processing of html content to replace local links to absolute
- links, using web.base.url as base url. """
- if not html:
- return html
-
- # form a tree
- root = lxml.html.fromstring(html)
- if not len(root) and root.text is None and root.tail is None:
- html = u'<div>%s</div>' % html
- root = lxml.html.fromstring(html, encoding='unicode')
-
- base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
- base = urls.url_parse(base_url)
-
- def _process_link(url):
- new_url = urls.url_parse(url)
- if new_url.scheme and (new_url.netloc or new_url.scheme == 'mailto'):
- return url
- return new_url.replace(scheme=base.scheme, netloc=base.netloc).to_url()
-
- # check all nodes, replace :
- # - img src -> check URL
- # - a href -> check URL
- for node in root.iter():
- if node.tag == 'a' and node.get('href'):
- node.set('href', _process_link(node.get('href')))
- elif node.tag == 'img' and not node.get('src', 'data').startswith(u'data'):
- node.set('src', _process_link(node.get('src')))
-
- html = lxml.html.tostring(root, pretty_print=False, method='html', encoding='unicode')
- # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
- if html.startswith(u'<div>') and html.endswith(u'</div>'):
- html = html[5:-6]
- return html
-
- @api.model
def render_post_process(self, html):
- html = self._replace_local_links(html)
+ html = self.env['mail.thread']._replace_local_links(html)
return html
@api.model
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 4ec3af5159e..32639ae75a9 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -23,9 +23,10 @@ from email.message import Message
from email.utils import formataddr
from lxml import etree
from werkzeug import url_encode
+from werkzeug import urls
from odoo import _, api, exceptions, fields, models, tools
-from odoo.tools import pycompat
+from odoo.tools import pycompat, ustr
from odoo.tools.safe_eval import safe_eval
@@ -374,6 +375,38 @@ class MailThread(models.AbstractModel):
# Technical methods / wrappers / tools
# ------------------------------------------------------
+ def _replace_local_links(self, html, base_url=None):
+ """ Replace local links by absolute links. It is required in various
+ cases, for example when sending emails on chatter or sending mass
+ mailings. It replaces
+
+ * href of links (mailto will not match the regex)
+ * src of images (base64 hardcoded data will not match the regex)
+ * styling using url like background-image: url
+
+ It is done using regex because it is shorten than using an html parser
+ to create a potentially complex soupe and hope to have a result that
+ has not been harmed.
+ """
+ if not html:
+ return html
+
+ html = ustr(html)
+
+ def _sub_relative2absolute(match):
+ # compute here to do it only if really necessary + cache will ensure it is done only once
+ # if not base_url
+ if not _sub_relative2absolute.base_url:
+ _sub_relative2absolute.base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
+ return match.group(1) + urls.url_join(_sub_relative2absolute.base_url, match.group(2))
+
+ _sub_relative2absolute.base_url = base_url
+ html = re.sub(r"""(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)""", _sub_relative2absolute, html)
+ html = re.sub(r"""(<a(?=\s)[^>]*\shref=")(/[^/][^"]+)""", _sub_relative2absolute, html)
+ html = re.sub(r"""(<[^>]+\bstyle="[^"]+\burl\('?)(/[^/'][^'")]+)""", _sub_relative2absolute, html)
+
+ return html
+
@api.model
def _garbage_collect_attachments(self):
""" Garbage collect lost mail attachments. Those are attachments
diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py
index 6999acc015b..4cee80af16a 100644
--- a/addons/mail/models/res_partner.py
+++ b/addons/mail/models/res_partner.py
@@ -73,17 +73,15 @@ class Partner(models.Model):
if message.res_id and message.model in self.env:
record = self.env[message.model].browse(message.res_id)
- company = user.company_id;
+ company = user.company_id
if record and hasattr(record, 'company_id'):
- company = record.company_id;
- company_name = company.name;
+ company = record.company_id
return {
'message': message,
'signature': signature,
'website_url': website_url,
'company': company,
- 'company_name': company_name,
'model_name': model_name,
'record': record,
'record_name': record_name,
@@ -187,6 +185,7 @@ class Partner(models.Model):
'subject': message.subject or (message.record_name and 'Re: %s' % message.record_name),
'body': base_template.render(template_ctx, engine='ir.qweb'),
}
+ fol_values['body'] = self.env['mail.thread']._replace_local_links(fol_values['body'])
# send email
new_emails, new_recipients_nbr = self._notify_send(fol_values['body'], fol_values['subject'], recipient_template_values['recipients'], **base_mail_values)
# update notifications
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index 06675ec3902..c4ad1b53306 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -790,11 +790,11 @@ var BasicComposer = Widget.extend({
var def = $.Deferred();
clearTimeout(this.canned_timeout);
this.canned_timeout = setTimeout(function () {
- var canned_responses = self.call('chat_manager', 'getCannedResponses');
- var matches = fuzzy.filter(utils.unaccent(search), _.pluck(canned_responses, 'source'));
+ var cannedResponses = self.call('chat_manager', 'getCannedResponses');
+ var matches = fuzzy.filter(utils.unaccent(search), _.pluck(cannedResponses, 'source'));
var indexes = _.pluck(matches.slice(0, self.options.mention_fetch_limit), 'index');
def.resolve(_.map(indexes, function (i) {
- return canned_responses[i];
+ return cannedResponses[i];
}));
}, 500);
return def;
diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js
index 52bf2b75822..3b405761169 100644
--- a/addons/mail/static/src/js/discuss.js
+++ b/addons/mail/static/src/js/discuss.js
@@ -63,13 +63,14 @@ var PartnerInviteDialog = Dialog.extend({
return $('<span>').text(item.text).prepend(status);
},
query: function (query) {
- self.call('chat_manager', 'searchPartner', query.term, 20).then(function (partners) {
- query.callback({
- results: _.map(partners, function (partner) {
- return _.extend(partner, { text: partner.label });
- }),
+ self.call('chat_manager', 'searchPartner', query.term, 20)
+ .then(function (partners) {
+ query.callback({
+ results: _.map(partners, function (partner) {
+ return _.extend(partner, { text: partner.label });
+ }),
+ });
});
- });
}
});
return this._super.apply(this, arguments);
@@ -152,15 +153,13 @@ var Discuss = Widget.extend(ControlPanelMixin, {
.then(function (fields_view) {
self.fields_view = fields_view;
});
- var chatReady = this.call('chat_manager', 'isReady');
- return $.when(this._super(), chatReady, def);
+ return $.when(this._super(), this.call('chat_manager', 'isReady'), def);
},
/**
* @override
*/
start: function () {
var self = this;
-
var defaultChannel = this.call('chat_manager', 'getChannel', this.defaultChannelID) ||
this.call('chat_manager', 'getChannel', 'channel_inbox');
@@ -214,8 +213,7 @@ var Discuss = Widget.extend(ControlPanelMixin, {
* @override
*/
on_attach_callback: function () {
- var chatBus = this.call('chat_manager', 'getChatBus');
- chatBus.trigger('discuss_open', true);
+ this.call('chat_manager', 'getChatBus').trigger('discuss_open', true);
if (this.channel) {
this.thread.scroll_to({offset: this.channelsScrolltop[this.channel.id]});
}
@@ -224,8 +222,7 @@ var Discuss = Widget.extend(ControlPanelMixin, {
* @override
*/
on_detach_callback: function () {
- var chatBus = this.call('chat_manager', 'getChatBus');
- chatBus.trigger('discuss_open', false);
+ this.call('chat_manager', 'getChatBus').trigger('discuss_open', false);
this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop();
},
@@ -582,7 +579,7 @@ var Discuss = Widget.extend(ControlPanelMixin, {
chatBus.on('new_channel', this, this._onNewChannel);
chatBus.on('anyone_listening', this, function (channel, query) {
query.is_displayed = query.is_displayed ||
- (channel.id === this.channel.id && this.thread.is_at_bottom());
+ (channel.id === this.channel.id && this.thread.is_at_bottom());
});
chatBus.on('unsubscribe_from_channel', this, this._onChannelLeft);
chatBus.on('update_needaction', this, this.throttledUpdateChannels);
@@ -642,7 +639,7 @@ var Discuss = Widget.extend(ControlPanelMixin, {
needaction_counter: this.call('chat_manager', 'getNeedactionCounter'),
starred_counter: this.call('chat_manager', 'getStarredCounter'),
});
- self.$(".o_mail_chat_sidebar").html($sidebar.contents());
+ this.$(".o_mail_chat_sidebar").html($sidebar.contents());
_.each(['dm', 'public', 'private'], function (type) {
var $input = self.$('.o_mail_add_channel[data-type=' + type + '] input');
self._prepareAddChannelInput($input, type);
@@ -791,10 +788,10 @@ var Discuss = Widget.extend(ControlPanelMixin, {
domain: this.domain
}).then(function (messages) {
var options = self._getThreadRenderingOptions(messages);
- self.thread.remove_message_and_render(message.id, messages, options).then(function () {
- self._updateButtonStatus(messages.length === 0, type);
-
- });
+ self.thread.remove_message_and_render(message.id, messages, options)
+ .then(function () {
+ self._updateButtonStatus(messages.length === 0, type);
+ });
});
} else if (_.contains(message.channel_ids, currentChannelID)) {
this._fetchAndRenderThread();
diff --git a/addons/mail/static/src/js/extended_chat_window.js b/addons/mail/static/src/js/extended_chat_window.js
index a8aa5673b81..a85740ac5e1 100644
--- a/addons/mail/static/src/js/extended_chat_window.js
+++ b/addons/mail/static/src/js/extended_chat_window.js
@@ -48,8 +48,7 @@ return ChatWindow.extend({
event.stopPropagation();
},
on_reverse_breadcrumb: function () {
- var chatBus = this.call('chat_manager', 'getChatBus');
- chatBus.trigger('discuss_open', false);
+ this.call('chat_manager', 'getChatBus').trigger('discuss_open', false);
},
on_click_expand: _.debounce(function (event) {
event.preventDefault();
diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js
index b7c46f2d4c1..fb4b6672bc6 100644
--- a/addons/mail/static/src/js/services/chat_manager.js
+++ b/addons/mail/static/src/js/services/chat_manager.js
@@ -16,11 +16,11 @@ var _lt = core._lt;
var LIMIT = 30; // max number of fetched messages from the server
var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers
-var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages
+var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages
/**
* This service handles everything about chat channels and messages.
- *
+ *
* There are basically two points of entry:
*
* 1. Calling a public method by means of 'this.call'
@@ -54,13 +54,13 @@ var ChatManager = AbstractService.extend({
this.pinnedDmPartners = []; // partner_ids we have a pinned DM with
this.discussOpen = false;
- this.chatBus = new Bus();
+ this.chatBus = new Bus(this);
this.busBus = this.call('bus_service', 'getBus');
this.chatBus.on('discuss_open', null, function (open) {
self.discussOpen = open;
});
-
+
this.busBus.on('notification', this, this._onNotification);
this.busBus.on('window_focus', this, this._onWindowFocus);
@@ -77,12 +77,12 @@ var ChatManager = AbstractService.extend({
this._isReady = this._initMessaging();
+ // Add static channels
this._addChannel({
id: "channel_inbox",
name: _lt("Inbox"),
type: "static",
}, { displayNeedactions: true });
-
this._addChannel({
id: "channel_starred",
name: _lt("Starred"),
@@ -181,7 +181,7 @@ var ChatManager = AbstractService.extend({
* Returns a channel corresponding to the given id.
*
* @param {string|integer} id e.g. 'channel_inbox', 'channel_starred'
- * @return {Object|undefined} channel
+ * @return {Object|undefined} the channel, if it exists
*/
getChannel: function (id) {
return _.findWhere(this.channels, {id: id});
@@ -189,7 +189,7 @@ var ChatManager = AbstractService.extend({
/**
* Returns a list of channels
*
- * @return {Object[]}
+ * @return {Object[]} list of channels
*/
getChannels: function () {
return _.clone(this.channels);
@@ -282,12 +282,13 @@ var ChatManager = AbstractService.extend({
* Show the list of available commands next to a message (e.g. star)
*
* @param {Object} channel
- * @return {Array}
+ * @return {Array} list of commands
*/
getCommands: function (channel) {
- return _.filter(this.commands, function (command) {
+ var commands = _.filter(this.commands, function (command) {
return !command.channel_types || _.contains(command.channel_types, channel.server_type);
});
+ return commands;
},
/**
* Returns the record id of ir.ui.menu for Discuss
@@ -309,7 +310,7 @@ var ChatManager = AbstractService.extend({
/**
* Returns list of emojis Objects
*
- * @return {Object[]}
+ * @return {Object[]} list of emojis
* ['id', 'source', 'unicode_source', 'substitution', 'description']
*/
getEmojis: function () {
@@ -319,9 +320,10 @@ var ChatManager = AbstractService.extend({
* Get the last seen message for a given channel
*
* @param {Object} channel
- * @return {Object} Message Object
+ * @return {Object|undefined} last seen Message Object (if any)
*/
getLastSeenMessage: function (channel) {
+ var result;
if (channel.last_seen_message_id) {
var messages = channel.cache['[]'].messages;
var msg = _.findWhere(messages, {id: channel.last_seen_message_id});
@@ -332,9 +334,10 @@ var ChatManager = AbstractService.extend({
msg = messages[i];
i++;
}
- return msg;
+ result = msg;
}
}
+ return result;
},
/**
* Get all listeners of a channel.
@@ -378,22 +381,22 @@ var ChatManager = AbstractService.extend({
* Gets message from its id
*
* @param {integer} msgID
- * @return {Object|undefined} Message Object
+ * @return {Object|undefined} Message Object (if any)
*/
getMessage: function (msgID) {
return _.findWhere(this.messages, {id: msgID});
},
/**
* Gets messages from channel or ids or record (model and res_id):
- *
+ *
* 1. From channel if we have 'channelID' in options
* 2. From ids if we have 'ids' in options
* 3. From model if we have 'model' and 'res_id' in options
- *
+ *
* Rule of precedence:
- *
+ *
* 'channelID' < 'ids' < 'model' and 'res_id'
- *
+ *
* If we have none of the cases above, we return an empty list.
*
* @param {Object} [options]
@@ -451,7 +454,7 @@ var ChatManager = AbstractService.extend({
* Returns the number of messages received from followed channels
* + all messages where the current user is notified.
*
- * @return {integer}
+ * @return {integer} needaction counter
*/
getNeedactionCounter: function () {
return this.needactionCounter;
@@ -459,7 +462,7 @@ var ChatManager = AbstractService.extend({
/**
* Gets the number of starred message
*
- * @return {integer}
+ * @return {integer} starred counter
*/
getStarredCounter: function () {
return this.starredCounter;
@@ -467,7 +470,7 @@ var ChatManager = AbstractService.extend({
/**
* Gets the number of conversation which contains unread messages
*
- * @return {integer}
+ * @return {integer} unread conversation counter
*/
getUnreadConversationCounter: function () {
return this.unreadConversationCounter;
@@ -491,7 +494,7 @@ var ChatManager = AbstractService.extend({
/**
* join an existing channel
* See @createChannel to join a new channel
- *
+ *
* @param {integer} channelID
* @param {Object} [options]
* @return {$.Promise<Object>} resolved with channel object
@@ -593,7 +596,7 @@ var ChatManager = AbstractService.extend({
},
/**
* Open the channel:
- *
+ *
* 1. If discuss is opened, asks discuss to open the channel
* 2. Otherwise, asks the chat_window_manager to detach the channel
*
@@ -604,14 +607,14 @@ var ChatManager = AbstractService.extend({
},
/**
* Prepares and sends a message to the server:
- *
+ *
* 1. Either the message is posted on a channel
* 2. Or the message is posted in a model's record (chatter)
*
* Rule of precedence:
- *
+ *
* 'channelID' < 'model' & 'res_id'
- *
+ *
* If options as none of these parameters, do nothing and return
* a promise no resolved item.
*
@@ -856,7 +859,7 @@ var ChatManager = AbstractService.extend({
* @param {string} [data.state] e.g. 'open', 'folded'
* @param {Object|integer} [options=undefined]
* @param {boolean} [options.silent]
- * @return {Object} the newly or already exisiting channel
+ * @return {Object} the newly or already existing channel
*/
_addChannel: function (data, options) {
options = typeof options === "object" ? options : {};
@@ -909,7 +912,7 @@ var ChatManager = AbstractService.extend({
* @param {Object} data message data
* @param {integer} data.id
* @param {Object} [options]
- * @param {Array} [options.domain]
+ * @param {Array} [options.domain]
* @param {boolean} [options.increment_unread] whether we should increment
* the unread_counter of channel.
* @param {boolean} [options.silent] whether it should inform in the chatBus
@@ -1134,6 +1137,7 @@ var ChatManager = AbstractService.extend({
}
}
});
+
self.busBus.start_polling();
});
},
@@ -1466,7 +1470,7 @@ var ChatManager = AbstractService.extend({
}
},
/**
- * Updates channel_inbox when a message has marked as read.
+ * Updates channel_inbox when a message has marked as read.
*
* @private
* @param {Object} data
@@ -1746,6 +1750,8 @@ var ChatManager = AbstractService.extend({
});
+core.serviceRegistry.add('chat_manager', ChatManager);
+
return ChatManager;
});
diff --git a/addons/mail/static/src/js/services/chat_window_manager.js b/addons/mail/static/src/js/services/chat_window_manager.js
index f82da369ecf..bab6fbf7729 100644
--- a/addons/mail/static/src/js/services/chat_window_manager.js
+++ b/addons/mail/static/src/js/services/chat_window_manager.js
@@ -40,8 +40,7 @@ var ChatWindowManager = AbstractService.extend({
spaceLeft: 0,
windowsDropdownIsOpen: false, // used to keep dropdown open when closing chat windows
};
- var chatReady = this.call('chat_manager', 'isReady');
- chatReady.then(function () {
+ this.call('chat_manager', 'isReady').then(function () {
var channels = self.call('chat_manager', 'getChannels');
_.each(channels, function (channel) {
if (channel.is_detached) {
@@ -88,7 +87,7 @@ var ChatWindowManager = AbstractService.extend({
* @param {boolean} [options.passively] if set to true, open the chat window
* without focusing the input and marking messages as read if it is not
* open yet, and do nothing otherwise
- */
+ */
openChat: function (session, options) {
var self = this;
if (!session) {
@@ -188,7 +187,7 @@ var ChatWindowManager = AbstractService.extend({
},
/**
* Called when unfolding the chat window
- *
+ *
* @param {Object} channel
* @param {integer} channel.id
* @param {boolean} [channel.is_folded]
@@ -326,12 +325,13 @@ var ChatWindowManager = AbstractService.extend({
var channel = self.call('chat_manager', 'getChannel', session.id);
self.call('chat_manager', 'markChannelAsSeen', channel);
}
- self.call('chat_manager', 'getMessages', {channelID: session.id}).then(function (messages) {
- session.window.render(messages);
- if (scrollBottom && messageVisible) {
- session.window.thread.scroll_to();
- }
- });
+ self.call('chat_manager', 'getMessages', {channelID: session.id})
+ .then(function (messages) {
+ session.window.render(messages);
+ if (scrollBottom && messageVisible) {
+ session.window.thread.scroll_to();
+ }
+ });
}
});
},
@@ -386,7 +386,7 @@ var ChatWindowManager = AbstractService.extend({
});
this.displayState.hiddenSessions = hiddenSessions;
this.displayState.hiddenUnreadCounter = hiddenUnreadCounter;
-
+
if (this.displayState.$hidden_windows_dropdown) {
this.displayState.$hidden_windows_dropdown.remove();
}
@@ -397,7 +397,7 @@ var ChatWindowManager = AbstractService.extend({
.appendTo($('body'));
this._repositionHiddenSessionsDropdown();
this.displayState.windowsDropdownIsOpen = false;
-
+
$hiddenWindowsDropdown.on('click', '.o_chat_header', function (event) {
var sessionID = $(event.currentTarget).data('session-id');
var session = _.findWhere(hiddenSessions, {id: sessionID});
@@ -527,6 +527,8 @@ var ChatWindowManager = AbstractService.extend({
});
+core.serviceRegistry.add('chat_window_manager', ChatWindowManager);
+
return ChatWindowManager;
});
diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js
index f2dfdc631ab..f6c19a1c164 100644
--- a/addons/mail/static/src/js/systray.js
+++ b/addons/mail/static/src/js/systray.js
@@ -36,8 +36,7 @@ var MessagingMenu = Widget.extend({
var chatBus = this.call('chat_manager', 'getChatBus');
chatBus.on("update_needaction", this, this.update_counter);
chatBus.on("update_channel_unread_counter", this, this.update_counter);
- var chatReady = this.call('chat_manager', 'isReady');
- chatReady.then(this.update_counter.bind(this));
+ this.call('chat_manager', 'isReady').then(this.update_counter.bind(this));
return this._super();
},
is_open: function () {
@@ -58,8 +57,7 @@ var MessagingMenu = Widget.extend({
// Display spinner while waiting for channels preview
this.$channels_preview.html(QWeb.render('Spinner'));
- var chatReady = this.call('chat_manager', 'isReady');
- chatReady.then(function () {
+ this.call('chat_manager', 'isReady').then(function () {
var allChannels = self.call('chat_manager', 'getChannels');
var channels = _.filter(allChannels, function (channel) {
if (self.filter === 'chat') {
@@ -70,23 +68,28 @@ var MessagingMenu = Widget.extend({
return channel.type !== 'static';
}
});
- self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'}).then(function (result) {
- var res = [];
- _.each(result, function (message) {
- message.unread_counter = 1;
- var duplicatedMessage = _.findWhere(res, {model: message.model, 'res_id': message.res_id});
- if (message.model && message.res_id && duplicatedMessage) {
- message.unread_counter = duplicatedMessage.unread_counter + 1;
- res[_.findIndex(res, duplicatedMessage)] = message;
- } else {
- res.push(message);
+ self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'})
+ .then(function (messages) {
+ var res = [];
+ _.each(messages, function (message) {
+ message.unread_counter = 1;
+ var duplicatedMessage = _.findWhere(res, {
+ model: message.model,
+ 'res_id': message.res_id
+ });
+ if (message.model && message.res_id && duplicatedMessage) {
+ message.unread_counter = duplicatedMessage.unread_counter + 1;
+ res[_.findIndex(res, duplicatedMessage)] = message;
+ } else {
+ res.push(message);
+ }
+ });
+ if (self.filter === 'channel_inbox' || !self.filter) {
+ channels = _.union(channels, res);
}
+ self.call('chat_manager', 'getChannelsPreview', channels)
+ .then(self._render_channels_preview.bind(self));
});
- if (self.filter === 'channel_inbox' || !self.filter) {
- channels = _.union(channels, res);
- }
- self.call('chat_manager', 'getChannelsPreview', channels).then(self._render_channels_preview.bind(self));
- });
});
},
_render_channels_preview: function (channels_preview) {
@@ -129,7 +132,7 @@ var MessagingMenu = Widget.extend({
this.do_action({
type: 'ir.actions.act_window',
res_model: resModel,
- views: [[false, 'form'], [false, 'kanban']],
+ views: [[false, 'form']],
res_id: resID
});
} else {
@@ -142,7 +145,7 @@ var MessagingMenu = Widget.extend({
} else {
var channel = this.call('chat_manager', 'getChannel', channelID);
if (channel) {
- self.call('chat_manager', 'openChannel', channel);
+ this.call('chat_manager', 'openChannel', channel);
}
}
},
@@ -159,10 +162,8 @@ var ActivityMenu = Widget.extend({
},
start: function () {
this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels');
- var chatBus = this.call('chat_manager', 'getChatBus');
- chatBus.on("activity_updated", this, this._updateCounter);
- var chatReady = this.call('chat_manager', 'isReady');
- chatReady.then(this._updateCounter.bind(this));
+ this.call('chat_manager', 'getChatBus').on("activity_updated", this, this._updateCounter);
+ this.call('chat_manager', 'isReady').then(this._updateCounter.bind(this));
this._updateActivityPreview();
return this._super();
},
diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js
index 0a745b69a96..6d036208eb2 100644
--- a/addons/mail/static/src/js/thread_field.js
+++ b/addons/mail/static/src/js/thread_field.js
@@ -21,8 +21,7 @@ var ThreadField = AbstractField.extend({
this.msgIDs = this.value.res_ids;
},
willStart: function () {
- var chatReady = this.call('chat_manager', 'isReady');
- return this.alive(chatReady);
+ return this.alive(this.call('chat_manager', 'isReady'));
},
start: function () {
var self = this;
@@ -78,7 +77,7 @@ var ThreadField = AbstractField.extend({
/**
* @param {Object} message
* @param {integer[]} message.partner_ids
- * @return {Deferred}
+ * @return {$.Promise}
*/
postMessage: function (message) {
var self = this;
diff --git a/addons/mail/static/tests/chat_window_tests.js b/addons/mail/static/tests/chat_window_tests.js
index a892d8e26e6..ab5a9f03538 100644
--- a/addons/mail/static/tests/chat_window_tests.js
+++ b/addons/mail/static/tests/chat_window_tests.js
@@ -11,7 +11,11 @@ var Widget = require('web.Widget');
var createBusService = mailTestUtils.createBusService;
-QUnit.module('mail', {}, function () {
+QUnit.module('mail', {
+ beforeEach: function () {
+ this.services = [ChatManager, createBusService()];
+ },
+}, function () {
QUnit.module('chat_window');
@@ -39,6 +43,7 @@ QUnit.module('mail', {}, function () {
}];
var parent = createParent({
data: {},
+ services: this.services,
});
var chatWindow = new ChatWindow(parent, 1, "user", false, messages, {});
@@ -67,6 +72,7 @@ QUnit.module('mail', {}, function () {
}
var parent = createParent({
data: {},
+ services: this.services,
});
var $dom = $('#qunit-fixture');
@@ -93,8 +99,6 @@ QUnit.module('mail', {}, function () {
QUnit.test('emoji popover should open correctly in chat windows', function (assert) {
assert.expect(1);
- var BusService = createBusService();
-
function createParent(params) {
var widget = new Widget();
@@ -107,7 +111,7 @@ QUnit.module('mail', {}, function () {
}
var parent = createParent({
data: {},
- services: [ChatManager, BusService],
+ services: this.services,
});
var $dom = $('#qunit-fixture');
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index d348decbb1a..985f61047b3 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -21,7 +21,7 @@ QUnit.module('mail', {}, function () {
QUnit.module('Chatter', {
beforeEach: function () {
- this.BusService = createBusService();
+ this.services = [ChatManager, createBusService()];
this.data = {
partner: {
fields: {
@@ -98,6 +98,7 @@ QUnit.test('basic rendering', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -121,7 +122,6 @@ QUnit.test('basic rendering', function (assert) {
}
return this._super(route, args);
},
- services: [ChatManager, this.BusService],
res_id: 2,
});
@@ -150,6 +150,7 @@ QUnit.test('chatter in create mode', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -165,7 +166,6 @@ QUnit.test('chatter in create mode', function (assert) {
}
return this._super(route, args);
},
- services: [ChatManager, this.BusService],
});
assert.strictEqual(form.$('.o_chatter').length, 1,
@@ -213,6 +213,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -232,7 +233,6 @@ QUnit.test('chatter rendering inside the sheet', function (assert) {
}
return this._super(route, args);
},
- services: [ChatManager, this.BusService],
});
assert.strictEqual(form.$('.o_chatter').length, 1,
@@ -241,7 +241,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) {
form.$buttons.find('.o_form_button_create').click();
assert.ok(form.$el.find('.o_form_view').hasClass('o_form_editable'),
"we should be in create mode");
-
+
assert.strictEqual(form.$('.o_chatter').length, 1,
"chatter should be displayed");
@@ -396,6 +396,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: [ChatManager, BusService],
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -455,7 +456,6 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
return this._super(route, args);
},
session: {},
- services: [ChatManager, BusService],
});
assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length,
@@ -546,6 +546,7 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert)
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -582,10 +583,9 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert)
});
return $.when(42);
}
-
+
return this._super(route, args);
},
- services: [ChatManager, this.BusService],
});
assert.strictEqual(form.$('.o_thread_message').length, 0, "thread should not contain messages");
@@ -652,6 +652,7 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -675,7 +676,6 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
}
return this._super.apply(this, arguments);
},
- services: [ChatManager, this.BusService],
});
assert.strictEqual(form.$('.o_thread_message .o_attachment').length, 4,
"there should be three attachment on message");
@@ -720,6 +720,7 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -746,7 +747,6 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
}
return this._super.apply(this, arguments);
},
- services: [ChatManager, this.BusService],
intercepts: {
do_action: function (event) {
assert.deepEqual(event.data.action, {
@@ -784,6 +784,7 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -800,7 +801,6 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f
}
return this._super.apply(this, arguments);
},
- services: [ChatManager, this.BusService],
intercepts: {
do_action: function (event) {
event.data.options.on_close();
@@ -852,6 +852,7 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) {
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -903,7 +904,6 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) {
}
return this._super.apply(this, arguments);
},
- services: [ChatManager, this.BusService],
});
assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 2,
@@ -951,6 +951,7 @@ QUnit.test('followers widget: follow/unfollow, edit subtypes', function (assert)
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -1071,6 +1072,7 @@ QUnit.test('followers widget: do not display follower duplications', function (a
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form>' +
'<sheet></sheet>' +
'<div class="oe_chatter">' +
@@ -1129,6 +1131,7 @@ QUnit.test('does not render and crash when destroyed before chat system is ready
View: FormView,
model: 'partner',
data: this.data,
+ services: this.services,
arch: '<form string="Partners">' +
'<sheet>' +
'<field name="foo"/>' +
@@ -1159,7 +1162,6 @@ QUnit.test('does not render and crash when destroyed before chat system is ready
}
return this._super(route, args);
},
- services: [ChatManager, this.BusService],
intercepts: {
get_session: function (event) {
event.stopPropagation();
diff --git a/addons/mail/static/tests/discuss_mobile_tests.js b/addons/mail/static/tests/discuss_mobile_tests.js
index 431a433c19f..4e96c87f512 100644
--- a/addons/mail/static/tests/discuss_mobile_tests.js
+++ b/addons/mail/static/tests/discuss_mobile_tests.js
@@ -11,7 +11,7 @@ QUnit.module('mail', {}, function () {
QUnit.module('Discuss client action in mobile', {
beforeEach: function () {
- this.BusService = createBusService();
+ this.services = [ChatManager, createBusService()];
this.data = {
'mail.message': {
fields: {},
@@ -33,17 +33,17 @@ QUnit.test('mobile basic rendering', function (assert) {
id: 1,
context: {},
params: {},
+ data: this.data,
+ services: this.services,
mockRPC: function (route, args) {
if (args.method === 'message_fetch') {
return $.when([]);
}
return this._super.apply(this, arguments);
},
- services: [ChatManager, this.BusService],
});
- var chatReady = discuss.call('chat_manager', 'isReady');
- chatReady.then(function () {
+ discuss.call('chat_manager', 'isReady').then(function () {
// test basic rendering in mobile
assert.strictEqual(discuss.$('.o_mail_chat_mobile_control_panel').length, 1,
"should have rendered a control panel");
diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js
index 3d107688537..e0a8a0a13de 100644
--- a/addons/mail/static/tests/helpers/test_utils.js
+++ b/addons/mail/static/tests/helpers/test_utils.js
@@ -17,43 +17,41 @@ var Widget = require('web.Widget');
/**
* Create a mock bus_service, using 'bus' instead of bus.bus
- *
+ *
* @param {web.bus} bus
* @return {AbstractService} the mock bus_service
*/
function createBusService(bus) {
-
var BusService = AbstractService.extend({
name: 'bus_service',
/**
* @override
*/
- init: function (parent) {
+ init: function () {
this._super.apply(this, arguments);
if (!bus) {
bus = new Bus();
}
this.bus = new _.extend(bus, {
/**
- * Do nothing
+ * Do nothing
*/
start_polling: function () {},
});
},
-
+
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
-
+
/**
* Get the bus
*/
getBus: function () {
return this.bus;
},
-
});
-
+
return BusService;
}
diff --git a/addons/mail/static/tests/systray_tests.js b/addons/mail/static/tests/systray_tests.js
index 2481c4d6550..4ac60c7e123 100644
--- a/addons/mail/static/tests/systray_tests.js
+++ b/addons/mail/static/tests/systray_tests.js
@@ -13,7 +13,7 @@ QUnit.module('mail', {}, function () {
QUnit.module('ActivityMenu', {
beforeEach: function () {
- this.BusService = createBusService();
+ this.services = [ChatManager, createBusService()];
this.data = {
'mail.activity.menu': {
fields: {
@@ -58,13 +58,13 @@ QUnit.test('activity menu widget: menu with no records', function (assert) {
var activityMenu = new systray.ActivityMenu();
testUtils.addMockEnvironment(activityMenu, {
+ services: this.services,
mockRPC: function (route, args) {
if (args.method === 'activity_user_count') {
return $.when([]);
}
return this._super(route, args);
},
- services: [ChatManager, this.BusService]
});
activityMenu.appendTo($('#qunit-fixture'));
assert.ok(activityMenu.$('.o_no_activity').hasClass('o_no_activity'), "should not have instance of widget");
@@ -76,13 +76,13 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
var self = this;
var activityMenu = new systray.ActivityMenu();
testUtils.addMockEnvironment(activityMenu, {
+ services: this.services,
mockRPC: function (route, args) {
if (args.method === 'activity_user_count') {
return $.when(self.data['mail.activity.menu']['records']);
}
return this._super(route, args);
},
- services: [ChatManager, this.BusService],
});
activityMenu.appendTo($('#qunit-fixture'));
assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget');