diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-07-29 14:10:30 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-07-29 14:10:30 +0200 |
| commit | d5e1fd16b4fd796cc25b2b2d694dadb375110ce7 (patch) | |
| tree | 35d130b4161985cd9ca3071a59980b52e8401be5 /addons/mail/static | |
| parent | ae71f95371b4a5c99c10a23e3553c5badc45a1ac (diff) | |
| parent | cda4f3c308ca233aead81f5ae97d250b5b7bf82f (diff) | |
[MERGE] forward port branch saas-12.4 up to cda4f3c308c
Diffstat (limited to 'addons/mail/static')
16 files changed, 306 insertions, 29 deletions
diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js index 93c6ca58d1c..79bb48819d5 100644 --- a/addons/mail/static/src/js/activity.js +++ b/addons/mail/static/src/js/activity.js @@ -382,7 +382,7 @@ var BasicActivity = AbstractField.extend({ container: $markDoneBtn, title : _t("Feedback"), html: true, - trigger:'click', + trigger: 'manual', placement: 'right', // FIXME: this should work, maybe a bug in the popper lib content : function () { var $popover = $(QWeb.render('mail.activity_feedback_form', { @@ -399,6 +399,11 @@ var BasicActivity = AbstractField.extend({ $popover.find('#activity_feedback').focus(); self._bindPopoverFocusout($(this)); }).popover('show'); + } else { + var popover = $markDoneBtn.data('bs.popover'); + if ($('#' + popover.tip.id).length === 0) { + popover.show(); + } } }, /** diff --git a/addons/mail/static/src/js/models/threads/dm_chat.js b/addons/mail/static/src/js/models/threads/dm_chat.js index c0ba9c527b0..78cc01a7044 100644 --- a/addons/mail/static/src/js/models/threads/dm_chat.js +++ b/addons/mail/static/src/js/models/threads/dm_chat.js @@ -101,6 +101,19 @@ var DMChat = TwoUserChannel.extend(ChannelSeenMixin, { getStatus: function () { return this.call('mail_service', 'getImStatus', { partnerID: this._directPartnerID }); }, + + /** + * @param {Object} data + * @param {string} data.outOfOfficeMessage + * @param {string} data.outOfOfficeDateEnd + */ + updateOutOfOfficeInfo: function (data) { + this._outOfOfficeMessage = data.outOfOfficeMessage; + this._outOfOfficeDateEnd = data.outOfOfficeDateEnd; + this.call('mail_service', 'getMailBus').trigger('updated_out_of_office', { + threadID: this.getID(), + }); + } }); return DMChat; diff --git a/addons/mail/static/src/js/models/threads/document_thread.js b/addons/mail/static/src/js/models/threads/document_thread.js index 08e441610ee..b4f1df097cf 100644 --- a/addons/mail/static/src/js/models/threads/document_thread.js +++ b/addons/mail/static/src/js/models/threads/document_thread.js @@ -329,7 +329,9 @@ var DocumentThread = Thread.extend({ .then(function (messages) { messages[0].model = resModel; messages[0].res_id = resID; - self.call('mail_service', 'addMessage', messages[0]); + self.call('mail_service', 'addMessage', messages[0], { + postedFromDocumentThread: true, + }); return messages[0]; }); }); diff --git a/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js b/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js index 7d97538dbb9..3c98836dc70 100644 --- a/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js +++ b/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js @@ -171,11 +171,15 @@ var ThreadTypingMixin = { if (this._isTypingMyselfInfo(params)) { return; } + var partnerID = params.partnerID; this._othersTypingTimers.registerTimer({ - timeoutCallbackArguments: [params.partnerID], - timerID: params.partnerID, + timeoutCallbackArguments: [partnerID], + timerID: partnerID, }); - this._typingPartnerIDs.push(params.partnerID); + if (_.contains(this._typingPartnerIDs, partnerID)) { + return; + } + this._typingPartnerIDs.push(partnerID); this._warnUpdatedTypingPartners(); }, /** @@ -209,6 +213,9 @@ var ThreadTypingMixin = { unregisterTyping: function (params) { var partnerID = params.partnerID; this._othersTypingTimers.unregisterTimer({ timerID: partnerID }); + if (!_.contains(this._typingPartnerIDs, partnerID)) { + return; + } this._typingPartnerIDs = _.reject(this._typingPartnerIDs, function (id) { return id === partnerID; }); diff --git a/addons/mail/static/src/js/services/mail_document_thread_manager.js b/addons/mail/static/src/js/services/mail_document_thread_manager.js index f1d9eefb44a..acf06461a04 100644 --- a/addons/mail/static/src/js/services/mail_document_thread_manager.js +++ b/addons/mail/static/src/js/services/mail_document_thread_manager.js @@ -48,23 +48,16 @@ MailManager.include({ * * @override * @param {Object} data - * @param {Array} [data.author_id] [int, string] where int is server ID of - * the author, and string is the name of the author. * @param {integer} data.id server ID of the message - * @param {string} [data.model] the model name of the document that this - * message is related to. - * @param {integer} [data.res_id] the ID of the document that this message - * is related to. - * + * @param {Object} [options] + * @param {boolean} [options.postedFromDocumentThread=false] */ - addMessage: function (data) { + addMessage: function (data, options) { var message = this.getMessage(data.id); if ( !message && - data.res_id && - data.model && - data.author_id && - data.author_id[0] === session.partner_id + options && + options.postedFromDocumentThread ) { var key = this.DOCUMENT_THREAD_MESSAGE_KEY; this.call('local_storage', 'setItem', key, data); diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js index caf0c3c4e12..c22276867b1 100644 --- a/addons/mail/static/src/js/services/mail_notification_manager.js +++ b/addons/mail/static/src/js/services/mail_notification_manager.js @@ -417,6 +417,9 @@ MailManager.include({ this._handlePartnerUserConnectionNotification(data); } else if (data.info === 'channel_seen') { this._handlePartnerChannnelSeenNotification(data); + } else if (data.type === 'simple_notification') { + var title = _.escape(data.title), message = _.escape(data.message); + data.warning ? this.do_warn(title, message, data.sticky) : this.do_notify(title, message, data.sticky); } else { this._handlePartnerChannelNotification(data); } diff --git a/addons/mail/static/src/js/services/mail_status_manager.js b/addons/mail/static/src/js/services/mail_status_manager.js index 7891461c9af..a40299a02d5 100644 --- a/addons/mail/static/src/js/services/mail_status_manager.js +++ b/addons/mail/static/src/js/services/mail_status_manager.js @@ -54,13 +54,45 @@ MailManager.include({ updateImStatus: function (statusList) { var updatedIDs = []; var self = this; + var dmChat; + var toUpdateOutOfOfficeChatIDs = []; _.each(statusList, function (status) { if (self._imStatus[status.id] === status.im_status) { return; } + if ( + status.im_status.indexOf('leave') !== -1 || + ( + self._imStatus[status.id] && + self._imStatus[status.id].indexOf('leave') !== -1 + ) + ) { + dmChat = self.getDMChatFromPartnerID(status.id); + if (dmChat) { + toUpdateOutOfOfficeChatIDs.push(dmChat.getID()); + } + } updatedIDs.push(status.id); self._imStatus[status.id] = status.im_status; }); + if (!_.isEmpty(toUpdateOutOfOfficeChatIDs)) { + this._rpc({ + model: 'mail.channel', + method: 'channel_info', + args: [toUpdateOutOfOfficeChatIDs], + }).then(function (channelsInfo) { + _.each(channelsInfo, function (channelInfo) { + dmChat = self.getChannel(channelInfo.id); + if (!dmChat) { + return; + } + dmChat.updateOutOfOfficeInfo({ + outOfOfficeMessage: channelInfo.direct_partner[0].out_of_office_message, + outOfOfficeDateEnd: channelInfo.direct_partner[0].out_of_office_date_end, + }); + }); + }); + } if (! _.isEmpty(updatedIDs)) { this._mailBus.trigger('updated_im_status', updatedIDs); // useful for thread window header this._renderImStatus(updatedIDs); diff --git a/addons/mail/static/src/js/services/mail_window_manager.js b/addons/mail/static/src/js/services/mail_window_manager.js index 6214fc8b5b7..81f0ef7e97c 100644 --- a/addons/mail/static/src/js/services/mail_window_manager.js +++ b/addons/mail/static/src/js/services/mail_window_manager.js @@ -344,6 +344,7 @@ MailManager.include({ .on('is_thread_bottom_visible', this, this._onIsThreadBottomVisible) .on('unsubscribe_from_channel', this, this._onUnsubscribeFromChannel) .on('updated_im_status', this, this._onUpdatedImStatus) + .on('updated_out_of_office', this, this._onUpdatedOutOfOffice) .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter); core.bus.on('resize', this, _.debounce(this._repositionThreadWindows.bind(this), 100)); @@ -701,6 +702,18 @@ MailManager.include({ }); }, /** + * @private + * @param {Object} data + * @param {integer} data.threadID + */ + _onUpdatedOutOfOffice: function (data) { + var threadWindow = this._getThreadWindow(data.threadID); + if (!threadWindow) { + return; + } + threadWindow.renderOutOfOffice(); + }, + /** * Called when a thread has its unread counter that has changed. * The unread counter on the thread windows should be updated. * diff --git a/addons/mail/static/src/js/thread_windows/thread_window.js b/addons/mail/static/src/js/thread_windows/thread_window.js index 9999014a08d..16d5cd31c86 100644 --- a/addons/mail/static/src/js/thread_windows/thread_window.js +++ b/addons/mail/static/src/js/thread_windows/thread_window.js @@ -6,6 +6,7 @@ var BasicComposer = require('mail.composer.Basic'); var core = require('web.core'); +var QWeb = core.qweb; var _t = core._t; /** @@ -153,6 +154,23 @@ var ThreadWindow = AbstractThreadWindow.extend({ removePassive: function () { this._passive = false; }, + renderOutOfOffice: function () { + var $outOfOffice = this.$('.o_out_of_office'); + if (!this.getOutOfOfficeInfo() && !this.getOutOfOfficeMessage()) { + if ($outOfOffice.length) { + $outOfOffice.remove(); + } + return; + } + var $newOutOfOffice = $(QWeb.render('mail.thread_window.OutOfOffice', { + widget: this, + })); + if ($outOfOffice.length) { + $outOfOffice.replaceWith($newOutOfOffice); + } else { + $newOutOfOffice.insertAfter(this.$('.o_thread_window_header')); + } + }, /** * Update this thread window * diff --git a/addons/mail/static/src/scss/abstract_thread_window.scss b/addons/mail/static/src/scss/abstract_thread_window.scss index f49797e928f..ab868b5dc18 100644 --- a/addons/mail/static/src/scss/abstract_thread_window.scss +++ b/addons/mail/static/src/scss/abstract_thread_window.scss @@ -39,8 +39,9 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default; padding: 8px; @include media-breakpoint-down(sm) { + align-items: center; height: $o-mail-chat-header-height; - padding-top: 12px; + padding: 0; border-radius: 0px; .o_thread_window_title { font-size: 16px; @@ -48,7 +49,9 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default; } .o_thread_window_close { - font-size: 17px; + $o-close-font-size: 17px; + padding: (($o-mail-chat-header-height - $o-close-font-size) / 2); + font-size: $o-close-font-size; color: white; } } diff --git a/addons/mail/static/src/scss/discuss.scss b/addons/mail/static/src/scss/discuss.scss index 8c5780a7185..6092ab733fc 100644 --- a/addons/mail/static/src/scss/discuss.scss +++ b/addons/mail/static/src/scss/discuss.scss @@ -287,7 +287,8 @@ } .o_mail_discuss_content { - flex: 1 0 0; + flex: 1 1 auto; + min-height: 0; margin-left: 10px; margin-right: 10px; background-color: transparent; diff --git a/addons/mail/static/src/xml/abstract_thread_window.xml b/addons/mail/static/src/xml/abstract_thread_window.xml index 89c45323c7a..9bc83b79242 100644 --- a/addons/mail/static/src/xml/abstract_thread_window.xml +++ b/addons/mail/static/src/xml/abstract_thread_window.xml @@ -66,7 +66,7 @@ --> <t t-name="mail.AbstractThreadWindow.HeaderContent"> <span t-if="widget.isMobile()"> - <a href="#" class="o_thread_window_close fa fa-1x fa-arrow-left mr4" aria-label="Close chat window" title="Close chat window"/> + <a href="#" class="o_thread_window_close fa fa-1x fa-arrow-left" aria-label="Close chat window" title="Close chat window"/> </span> <span class="o_thread_window_title"> <t t-if="status" t-call="mail.UserStatus"/> diff --git a/addons/mail/static/src/xml/thread_window.xml b/addons/mail/static/src/xml/thread_window.xml index 76a130d1ba2..a9e2131cbdb 100644 --- a/addons/mail/static/src/xml/thread_window.xml +++ b/addons/mail/static/src/xml/thread_window.xml @@ -6,13 +6,7 @@ --> <t t-name="mail.ThreadWindow" t-extend="mail.AbstractThreadWindow"> <t t-jquery=".o_thread_window_header" t-operation="after"> - <div class="o_out_of_office" t-if="widget.getOutOfOfficeInfo() or widget.getOutOfOfficeMessage()"> - <div class="o_out_of_office_info" t-if="widget.getOutOfOfficeInfo()" t-esc="widget.getOutOfOfficeInfo()"/> - <div class="o_out_of_office_text o_text_wrap" t-if="widget.getOutOfOfficeMessage()"> - <t t-esc="widget.getOutOfOfficeMessage()"/> - <a href="#" class="o_out_of_office_read_more_less_button">Read more</a> - </div> - </div> + <t t-call="mail.thread_window.OutOfOffice" t-if="widget.getOutOfOfficeInfo() or widget.getOutOfOfficeMessage()"/> <div t-if="!widget.hasThread()" class="o_thread_search_input"> <span> To: </span> <input type="text" placeholder="User name"/> @@ -21,6 +15,17 @@ </t> <!-- + @param {mail.ThreadWindow} widget + --> + <div class="o_out_of_office" t-name="mail.thread_window.OutOfOffice"> + <div class="o_out_of_office_info" t-if="widget.getOutOfOfficeInfo()" t-esc="widget.getOutOfOfficeInfo()"/> + <div class="o_out_of_office_text o_text_wrap" t-if="widget.getOutOfOfficeMessage()"> + <t t-esc="widget.getOutOfOfficeMessage()"/> + <a href="#" class="o_out_of_office_read_more_less_button">Read more</a> + </div> + </div> + + <!-- @param {string} expandTitle text to display on mouse hover (title attribute) --> <t t-extend="mail.AbstractThreadWindow.HeaderContent"> diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index 354cff95a75..bb569645c4d 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -7,6 +7,7 @@ var core = require('web.core'); var FormView = require('web.FormView'); var KanbanView = require('web.KanbanView'); var testUtils = require('web.test_utils'); +var concurrency = require('web.concurrency'); var createView = testUtils.createView; @@ -1858,6 +1859,74 @@ QUnit.test('form activity widget: schedule next activity', async function (asser form.destroy(); }); +QUnit.test('form activity widget: do not close activity popover on click', async function (assert) { + assert.expect(3); + this.data.partner.records[0].activity_ids = [1]; + this.data.partner.records[0].activity_state = 'today'; + this.data['mail.activity'].records = [{ + id: 1, + display_name: "An activity", + date_deadline: moment().format("YYYY-MM-DD"), // now + can_write: true, + state: "today", + user_id: 2, + create_user_id: 2, + activity_type_id: 2, + }]; + + const form = await 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"/> + <field name="activity_ids" widget="mail_activity"/> + </div> + </form>`, + res_id: 2, + mockRPC(route, args) { + if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') { + return Promise.resolve(); + } + return this._super.apply(this, arguments); + }, + intercepts: { + do_action(ev) { + ev.data.options.on_close(); + }, + }, + }); + + //Schedule next activity + await testUtils.dom.click(form.$('.o_mail_activity .o_mark_as_done[data-activity-id=1]')); + + for (let i = 0; i < 2; i++) { + // click twice in feedback area + await testUtils.dom.click(form.$('.o_mail_activity_feedback.popover textarea')); + const $popover = form.$('.o_mail_activity_feedback.popover'); + const $PopoverInDom = form.$(`#${$popover.attr('id')}`); + assert.isVisible( + $PopoverInDom, + "the feedback popover should still be visible on click"); + } + await concurrency.delay(250); // wait for popover transition + const $popover = form.$('.o_mail_activity_feedback.popover'); + if (!$popover) { + throw new Error("popover should be in DOM"); + } + const $PopoverInDom = form.$(`#${$popover.attr('id')}`); + assert.isVisible( + $PopoverInDom, + "the feedback popover should still be visible on click"); + + form.destroy(); +}); + QUnit.test('form activity widget: edit next activity', async function (assert) { assert.expect(3); diff --git a/addons/mail/static/tests/document_thread_window_tests.js b/addons/mail/static/tests/document_thread_window_tests.js index 51daf3a23b1..86d06ba36c4 100644 --- a/addons/mail/static/tests/document_thread_window_tests.js +++ b/addons/mail/static/tests/document_thread_window_tests.js @@ -48,7 +48,7 @@ QUnit.module('Document Thread', { }, records: [{ id: 1, - author_id: [1, 'Me'], + author_id: [partnerID, 'Me'], body: '<p>Some Message on a document</p>', channel_ids: [], model: 'some.res.model', diff --git a/addons/mail/static/tests/thread_window/basic_thread_window_tests.js b/addons/mail/static/tests/thread_window/basic_thread_window_tests.js index d5af9cf5357..7c3cfbdc793 100644 --- a/addons/mail/static/tests/thread_window/basic_thread_window_tests.js +++ b/addons/mail/static/tests/thread_window/basic_thread_window_tests.js @@ -726,6 +726,119 @@ QUnit.test('no out-of-office status in thread window', async function (assert) { parent.destroy(); }); +QUnit.test('auto-update out-of-office info on im_status change', async function (assert) { + assert.expect(5); + + var imStatusDefs = [testUtils.makeTestPromise(), testUtils.makeTestPromise()]; + var channelInfoDefs = [testUtils.makeTestPromise(), testUtils.makeTestPromise()]; + var timeoutMock = mailTestUtils.patchMailTimeouts(); + var step = 0; + + this.data = { + 'mail.message': { + fields: {}, + records: [], + }, + initMessaging: { + channel_slots: { + channel_channel: [{ + id: 1, + name: "DM", + channel_type: "chat", + message_unread_counter: 0, + direct_partner: [{ + id: 666, + name: 'DemoUser1', + im_status: 'online', + }], + }], + }, + }, + }; + + var parent = this.createParent({ + data: this.data, + services: this.services, + mockRPC: function (route, args) { + if (route === '/longpolling/im_status') { + step++; + if (step === 1) { + imStatusDefs[0].resolve(); + return Promise.resolve([ + { + id: 666, + im_status: 'leave', + }, + ]); + } else if (step === 2) { + imStatusDefs[1].resolve(); + return Promise.resolve([ + { + id: 666, + im_status: 'online', + }, + ]); + } + } + if (args.method === 'channel_info') { + if (step === 1) { + channelInfoDefs[0].resolve(); + return Promise.resolve([{ + id: 1, + direct_partner: [{ + out_of_office_message: "Leave me alone", + out_of_office_date_end: false, + }], + }]); + } + else if (step === 2) { + channelInfoDefs[1].resolve(); + return Promise.resolve([{ + id: 1, + direct_partner: [{ + out_of_office_message: false, + out_of_office_date_end: false, + }], + }]); + } + } + return this._super.apply(this, arguments); + }, + }); + await testUtils.nextTick(); + + // detach channel 1, so that it opens corresponding thread window. + parent.call('mail_service', 'getChannel', 1).detach(); + await testUtils.nextTick(); + + assert.containsNone($, '.o_out_of_office', + "should contain no out of office section on chat window initially"); + + timeoutMock.addTime(51*1000); // wait for next im status fetch + + await Promise.all([imStatusDefs[0], channelInfoDefs[0]]); + await testUtils.nextTick(); + assert.containsOnce($, '.o_out_of_office', + "should contain out of office section on chat window"); + assert.containsOnce($, '.o_out_of_office > .o_out_of_office_text', + "should contain out of office text on chat window"); + assert.ok( + $('.o_out_of_office > .o_out_of_office_text') + .text() + .replace(/\s/g, "") + .indexOf("Leavemealone") !== -1, + "should contain out of office text on chat window"); + timeoutMock.addTime(51*1000); // wait for next im status fetch + + await Promise.all([imStatusDefs[1], channelInfoDefs[1]]); + await testUtils.nextTick(); + assert.containsNone($, '.o_out_of_office', + "should no longer contain out of office section on chat window"); + + timeoutMock.runPendingTimeouts(); + parent.destroy(); +}); + }); }); }); |
