diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-06-28 19:01:52 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-06-28 19:01:52 +0200 |
| commit | c8bb02141d2801c243ae65ba6800b07f090bb5c6 (patch) | |
| tree | 2d099c5577df4783a6db3d853cf0141bf41c1aa9 /addons/mail | |
| parent | 4e2733e83a1f42773fb28f66431a0d6fb0393be9 (diff) | |
| parent | 3cdcbce93cbde548d2d24426ee8de1d93f06706e (diff) | |
[MERGE] forward port branch saas-11.3 up to 3cdcbce93cb
Diffstat (limited to 'addons/mail')
| -rw-r--r-- | addons/mail/models/mail_message.py | 4 | ||||
| -rw-r--r-- | addons/mail/static/src/js/chatter.js | 70 | ||||
| -rw-r--r-- | addons/mail/static/src/js/chatter_composer.js | 25 | ||||
| -rw-r--r-- | addons/mail/static/src/js/services/chat_window_manager.js | 2 | ||||
| -rw-r--r-- | addons/mail/static/src/scss/composer.scss | 2 | ||||
| -rw-r--r-- | addons/mail/static/tests/chatter_tests.js | 279 | ||||
| -rw-r--r-- | addons/mail/static/tests/helpers/test_utils.js | 3 | ||||
| -rw-r--r-- | addons/mail/views/mail_templates.xml | 11 |
8 files changed, 376 insertions, 20 deletions
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index c5d3abda206..a32ccfa9b54 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -6,6 +6,7 @@ import re from operator import itemgetter from email.utils import formataddr +from openerp.http import request from odoo import _, api, fields, models, modules, SUPERUSER_ID, tools from odoo.exceptions import UserError, AccessError @@ -334,11 +335,12 @@ class Message(models.Model): # 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see attachments_data = attachments.sudo().read(['id', 'datas_fname', 'name', 'mimetype']) + safari = request and request.httprequest.user_agent.browser == 'safari' attachments_tree = dict((attachment['id'], { 'id': attachment['id'], 'filename': attachment['datas_fname'], 'name': attachment['name'], - 'mimetype': attachment['mimetype'], + 'mimetype': 'application/octet-stream' if safari and 'video' in attachment['mimetype'] else attachment['mimetype'], }) for attachment in attachments_data) # 3. Tracking values diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index e48a7c60028..63948b8429a 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -24,6 +24,7 @@ var QWeb = core.qweb; var Chatter = Widget.extend({ template: 'mail.Chatter', custom_events: { + discard_record_changes: '_onDiscardRecordChanges', reload_mail_fields: '_onReloadMailFields', }, events: { @@ -155,6 +156,37 @@ var Chatter = Widget.extend({ } }, /** + * Discard changes on the record. + * + * @private + * @returns {$.Deferred} resolved if successfully discarding changes on + * the record, rejected otherwise + */ + _discardChanges: function () { + var def = $.Deferred(); + this.trigger_up('discard_changes', { + recordID: this.record.id, + onSuccess: def.resolve.bind(def), + onFailure: def.reject.bind(def), + }); + return def; + }, + /** + * Discard changes on the record if the message will reload the record + * after posting it + * + * @private + * @param {Object} message + * @return {$.Deferred} resolved if no reload or proceed to discard the + * changes on the record, rejected otherwise + */ + _discardOnReload: function (message) { + if (this._reloadAfterPost(message)) { + return this._discardChanges(); + } + return $.when(); + }, + /** * @private */ _disableChatter: function () { @@ -199,11 +231,13 @@ var Chatter = Widget.extend({ self.composer.focus(); } self.composer.on('post_message', self, function (message) { - self.fields.thread.postMessage(message).then(function () { - self._closeComposer(true); - if (self.postRefresh === 'always' || (self.postRefresh === 'recipients' && message.partner_ids.length)) { - self.trigger_up('reload'); - } + self._discardOnReload(message).then(function () { + self.fields.thread.postMessage(message).then(function () { + self._closeComposer(true); + if (self._reloadAfterPost(message)) { + self.trigger_up('reload'); + } + }); }); }); self.composer.on('need_refresh', self, self.trigger_up.bind(self, 'reload')); @@ -216,6 +250,24 @@ var Chatter = Widget.extend({ }); }, /** + * State if the record will be reloaded after posting a message. + * Useful to warn the user of unsaved changes if the record is dirty. + * + * @private + * @param {Object} message + * @param {Array} [message.partner_ids] list of recipients of a message + * @return {boolean} true if record will be reloaded after posting the + * message, false otherwise + */ + _reloadAfterPost: function (message) { + return this.postRefresh === 'always' || + ( + this.postRefresh === 'recipients' && + message.partner_ids && + message.partner_ids.length + ); + }, + /** * @private * @param {Deferred} def * @returns {Deferred} @@ -314,8 +366,16 @@ var Chatter = Widget.extend({ //-------------------------------------------------------------------------- /** + * Discard changes on the record. + * This is notified by the composer, when opening the full-composer. + * * @private + * @param {OdooEvent} ev + * @param {function} ev.data.proceed callback to tell to proceed */ + _onDiscardRecordChanges: function (ev) { + this._discardChanges().then(ev.data.proceed); + }, _onOpenComposerMessage: function () { var self = this; if (!this.suggested_partners_def) { diff --git a/addons/mail/static/src/js/chatter_composer.js b/addons/mail/static/src/js/chatter_composer.js index da85f331863..b5c17f3f593 100644 --- a/addons/mail/static/src/js/chatter_composer.js +++ b/addons/mail/static/src/js/chatter_composer.js @@ -202,14 +202,23 @@ var ChatterComposer = composer.BasicComposer.extend({ } var self = this; - var recipient_done = $.Deferred(); - if (this.options.is_log) { - recipient_done.resolve([]); - } else { - var checked_suggested_partners = this.get_checked_suggested_partners(); - recipient_done = this.check_suggested_partners(checked_suggested_partners); - } - recipient_done.then(function (partner_ids) { + var recipientDoneDef = $.Deferred(); + + // any operation on the full-composer will reload the record, so + // warn the user that any unsaved changes on the record will be lost. + this.trigger_up('discard_record_changes', { + proceed: function () { + if (self.options.is_log) { + recipientDoneDef.resolve([]); + } else { + var checked_suggested_partners = self.get_checked_suggested_partners(); + self.check_suggested_partners(checked_suggested_partners) + .then(recipientDoneDef.resolve.bind(recipientDoneDef)); + } + }, + }); + + recipientDoneDef.then(function (partner_ids) { var context = { default_parent_id: self.id, default_body: utils.get_text2html(self.$input.val()), 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 bea6eaf0ace..fd45b336c23 100644 --- a/addons/mail/static/src/js/services/chat_window_manager.js +++ b/addons/mail/static/src/js/services/chat_window_manager.js @@ -108,7 +108,7 @@ var ChatWindowManager = AbstractService.extend({ uuid: session.uuid, name: session.name, keep_unread: options.passively, // don't automatically mark unread messages as seen - window: new ExtendedChatWindow(web_client, session.id, prefix + session.name, session.is_folded, session.unread_counter, windowOptions), + window: new ExtendedChatWindow(this, session.id, prefix + session.name, session.is_folded, session.unread_counter, windowOptions), }; chatSession.window.on("close_chat_session", null, function () { self._closeChat(chatSession); diff --git a/addons/mail/static/src/scss/composer.scss b/addons/mail/static/src/scss/composer.scss index cb079f81558..fed625ace35 100644 --- a/addons/mail/static/src/scss/composer.scss +++ b/addons/mail/static/src/scss/composer.scss @@ -152,7 +152,7 @@ &.o_chat_inline_composer { .o_composer_container { - @include o-flex(1, 0, auto); + @include o-flex(1, 1, auto); } .o_composer { padding: $o-mail-chatter-gap $o-mail-chatter-gap 0 $o-mail-chatter-gap; diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index 46de2e22d3f..f1f6097cb97 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -611,6 +611,285 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert) form.destroy(); }); +QUnit.test('chatter: discard changes on message post with post_refresh "always"', function (assert) { + // After posting a message that always reloads the record, if the record + // is dirty (= has some unsaved changes), we should warn the user that + // these changes will be lost if he proceeds. + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="foo"/>' + + '</sheet>' + + '<div class="oe_chatter">' + + '<field name="message_ids" widget="mail_thread"' + + ' options="{\'display_log_button\': True, \'post_refresh\': \'always\'}"/>' + + '</div>' + + '</form>', + res_id: 2, + session: {}, + mockRPC: function (route, args) { + if (route === "/web/dataset/call_kw/partner/message_get_suggested_recipients") { + return $.when({2: []}); + } + return this._super(route, args); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // Make record dirty + form.$('.o_form_sheet input').val('trululu').trigger('input'); + + // Send a message + form.$('.o_chatter_button_new_message').click(); + form.$('.oe_chatter .o_composer_text_field:first()').val("My first message"); + form.$('.oe_chatter .o_composer_button_send').click(); + + var $modal = $('.modal-dialog'); + assert.strictEqual($modal.length, 1, "should have a modal opened"); + assert.strictEqual($modal.find('.modal-body').text(), + "The record has been modified, your changes will be discarded. Do you want to proceed?", + "should warn the user that any unsaved changes will be lost"); + + form.destroy(); +}); + +QUnit.test('chatter: discard changes on message post without post_refresh', function (assert) { + // After posting a message, if the record is dirty and there are no + // post_refresh rule, it will not discard the changes on the record. + assert.expect(2); + + var hasDiscardChanges = false; // set if `discard_changes` has been triggered up + var messages = []; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="foo"/>' + + '</sheet>' + + '<div class="oe_chatter">' + + '<field name="message_ids" widget="mail_thread"' + + ' options="{\'display_log_button\': True}"/>' + + '</div>' + + '</form>', + res_id: 2, + session: {}, + mockRPC: function (route, args) { + if (route === "/web/dataset/call_kw/partner/message_get_suggested_recipients") { + return $.when({2: []}); + } + if (args.method === 'message_format') { + var requested_msgs = _.filter(messages, function (msg) { + return _.contains(args.args[0], msg.id); + }); + return $.when(requested_msgs); + } + if (args.method === 'message_post') { + messages.push({ + attachment_ids: [], + author_id: ["42", "Me"], + body: args.kwargs.body, + date: moment().format('YYYY-MM-DD HH:MM:SS'), // now + displayed_author: "Me", + id: 42, + is_note: args.kwargs.subtype === 'mail.mt_note', + is_starred: false, + model: 'partner', + res_id: 2, + }); + return $.when(42); + } + return this._super(route, args); + }, + intercepts: { + discard_changes: function () { + hasDiscardChanges = true; // should not do that + }, + }, + viewOptions: { + mode: 'edit', + }, + }); + + // Make record dirty + form.$('.o_form_sheet input').val('trululu').trigger('input'); + + // Send a message + form.$('.o_chatter_button_new_message').click(); + form.$('.oe_chatter .o_composer_text_field:first()').val("My first message"); + form.$('.oe_chatter .o_composer_button_send').click(); + + var $modal = $('.modal-dialog'); + assert.strictEqual($modal.length, 0, "should have no modal opened"); + assert.notOk(hasDiscardChanges); + + form.destroy(); +}); + +QUnit.test('chatter: discard changes on message post with post_refresh "recipients"', function (assert) { + // After posting a message with mentions, the record will be reloaded, + // as the rpc `message_post` may make changes on some fields of the record. + // If the record is dirty (= has some unsaved changes), we should warn the + // user that these changes will be lost if he proceeds. + assert.expect(2); + var done = assert.async(); + + var getSuggestionsDef = $.Deferred(); + + var messages = []; + var bus = new Bus(); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="foo"/>' + + '</sheet>' + + '<div class="oe_chatter">' + + '<field name="message_ids" widget="mail_thread"' + + ' options="{\'display_log_button\': True, \'post_refresh\': \'recipients\'}"/>' + + '</div>' + + '</form>', + res_id: 2, + session: {}, + mockRPC: function (route, args) { + if (route === "/web/dataset/call_kw/partner/message_get_suggested_recipients") { + return $.when({2: [[42, "Me"]]}); + } + if (args.method === 'get_mention_suggestions') { + getSuggestionsDef.resolve(); + return $.when([{email: "me@odoo.com", id: 42, name: "Me"}]); + } + if (args.method === 'message_format') { + var requested_msgs = _.filter(messages, function (msg) { + return _.contains(args.args[0], msg.id); + }); + return $.when(requested_msgs); + } + if (args.method === 'message_post') { + messages.push({ + attachment_ids: [], + author_id: ["42", "Me"], + body: args.kwargs.body, + date: moment().format('YYYY-MM-DD HH:MM:SS'), // now + displayed_author: "Me", + id: 42, + is_note: args.kwargs.subtype === 'mail.mt_note', + is_starred: false, + model: 'partner', + res_id: 2, + }); + return $.when(42); + } + return this._super(route, args); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // Make record dirty + form.$('.o_form_sheet input').val('trululu').trigger('input'); + + // create a new message + form.$('.o_chatter_button_new_message').click(); + + // Add a user as mention + form.$('.oe_chatter .o_composer_text_field:first()').val("@"); + + var $input = form.$('.oe_chatter .o_composer_text_field:first()'); + $input.val('@'); + // the cursor position must be set for the mention manager to detect that we are mentionning + $input[0].selectionStart = 1; + $input[0].selectionEnd = 1; + $input.trigger('keyup'); + + getSuggestionsDef + .then(concurrency.delay.bind(concurrency, 0)) + .then(function () { + // click on mention + $input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER})); + + // untick recipient as follower (prompts a res.partner form otherwise) + form.$('.o_checkbox input').prop('checked', false); + + // send message + form.$('.oe_chatter .o_composer_button_send').click(); + + var $modal = $('.modal-dialog'); + assert.strictEqual($modal.length, 1, "should have a modal opened"); + assert.strictEqual($modal.find('.modal-body').text(), + "The record has been modified, your changes will be discarded. Do you want to proceed?", + "should warn the user that any unsaved changes will be lost"); + + form.destroy(); + done(); + }); +}); + +QUnit.test('chatter: discard changes on opening full-composer', function (assert) { + // When we open the full-composer, any following operations by the user + // will reload the record (even closing the full-composer). Therefore, + // we should warn the user when we open the full-composer if the record + // is dirty (= has some unsaved changes). + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="foo"/>' + + '</sheet>' + + '<div class="oe_chatter">' + + '<field name="message_ids" widget="mail_thread"' + + ' options="{\'display_log_button\': True,' + + ' \'post_refresh\': \'always\'}"/>' + + '</div>' + + '</form>', + res_id: 2, + session: {}, + mockRPC: function (route, args) { + if (route === "/web/dataset/call_kw/partner/message_get_suggested_recipients") { + return $.when({2: []}); + } + return this._super(route, args); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // Make record dirty + form.$('.o_form_sheet input').val('trululu').trigger('input'); + + // Open full-composer + form.$('.o_chatter_button_new_message').click(); + form.$('.o_composer_button_full_composer').click(); + + var $modal = $('.modal-dialog'); + assert.strictEqual($modal.length, 1, "should have a modal opened"); + assert.strictEqual($modal.find('.modal-body').text(), + "The record has been modified, your changes will be discarded. Do you want to proceed?", + "should warn the user that any unsaved changes will be lost"); + + form.destroy(); +}); + QUnit.test('chatter: Attachment viewer', function (assert) { assert.expect(6); this.data.partner.records[0].message_ids = [1]; diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js index 3382fa90dd0..527d873863e 100644 --- a/addons/mail/static/tests/helpers/test_utils.js +++ b/addons/mail/static/tests/helpers/test_utils.js @@ -38,6 +38,9 @@ function createBusService(bus) { * Do nothing */ start_polling: function () {}, + is_odoo_focused: function () { + return true; + }, }); }, diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml index 5dd1b057bd9..7283014052e 100644 --- a/addons/mail/views/mail_templates.xml +++ b/addons/mail/views/mail_templates.xml @@ -46,6 +46,13 @@ </xpath> </template> + <template id="js_test_assets" name="mail_js_test_assets" inherit_id="web.js_tests_assets"> + <xpath expr="." position="inside"> + <script type="text/javascript" src="/mail/static/tests/helpers/mock_server.js"></script> + <script type="text/javascript" src="/mail/static/tests/helpers/test_utils.js"></script> + </xpath> + </template> + <template id="qunit_suite" name="mail_tests" inherit_id="web.qunit_suite"> <xpath expr="//t[@t-set='head']" position="inside"> <script type="text/javascript" src="/mail/static/tests/chat_window_tests.js"></script> @@ -54,16 +61,12 @@ <script type="text/javascript" src="/mail/static/tests/discuss_tests.js"></script> <script type="text/javascript" src="/mail/static/tests/systray_tests.js"></script> <script type="text/javascript" src="/mail/static/tests/discuss_moderation_tests.js"></script> - <script type="text/javascript" src="/mail/static/tests/helpers/mock_server.js"></script> - <script type="text/javascript" src="/mail/static/tests/helpers/test_utils.js"></script> </xpath> </template> <template id="qunit_mobile_suite" name="mail_mobile_tests" inherit_id="web.qunit_mobile_suite"> <xpath expr="//t[@t-set='head']" position="inside"> <script type="text/javascript" src="/mail/static/tests/discuss_mobile_tests.js"></script> - <script type="text/javascript" src="/mail/static/tests/helpers/mock_server.js"></script> - <script type="text/javascript" src="/mail/static/tests/helpers/test_utils.js"></script> </xpath> </template> |
