diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-02-12 18:34:43 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-02-12 18:34:43 +0100 |
| commit | adeea2a88db4443b2b426877a28bb1471c2f5db1 (patch) | |
| tree | 2f43fecc9649947d3061e8c482219fa167b3385a /addons/mail | |
| parent | 317f5f11112266128ac2292ecfafcd92cda74fd0 (diff) | |
| parent | 0f02dcb63918228d48df50f2d5805b7fda25c14a (diff) | |
[MERGE] forward port branch saas-11.2 up to 0f02dcb6391
Diffstat (limited to 'addons/mail')
| -rw-r--r-- | addons/mail/data/mail_data.xml | 12 | ||||
| -rw-r--r-- | addons/mail/i18n/de.po | 28 | ||||
| -rw-r--r-- | addons/mail/i18n/zh_CN.po | 2 | ||||
| -rw-r--r-- | addons/mail/models/mail_message.py | 5 | ||||
| -rw-r--r-- | addons/mail/models/mail_template.py | 39 | ||||
| -rw-r--r-- | addons/mail/models/mail_thread.py | 35 | ||||
| -rw-r--r-- | addons/mail/models/res_partner.py | 7 | ||||
| -rw-r--r-- | addons/mail/static/src/js/composer.js | 6 | ||||
| -rw-r--r-- | addons/mail/static/src/js/discuss.js | 35 | ||||
| -rw-r--r-- | addons/mail/static/src/js/extended_chat_window.js | 3 | ||||
| -rw-r--r-- | addons/mail/static/src/js/services/chat_manager.js | 62 | ||||
| -rw-r--r-- | addons/mail/static/src/js/services/chat_window_manager.js | 26 | ||||
| -rw-r--r-- | addons/mail/static/src/js/systray.js | 51 | ||||
| -rw-r--r-- | addons/mail/static/src/js/thread_field.js | 5 | ||||
| -rw-r--r-- | addons/mail/static/tests/chat_window_tests.js | 12 | ||||
| -rw-r--r-- | addons/mail/static/tests/chatter_tests.js | 28 | ||||
| -rw-r--r-- | addons/mail/static/tests/discuss_mobile_tests.js | 8 | ||||
| -rw-r--r-- | addons/mail/static/tests/helpers/test_utils.js | 14 | ||||
| -rw-r--r-- | addons/mail/static/tests/systray_tests.js | 6 |
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) > 0 or is_notification" +<div t-if="has_button_access or len(actions) > 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'); |
