From 3bceb9159bb08f20cfae976554075c6255a66329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 25 Jul 2022 15:41:31 +0000 Subject: [PATCH] [IMP] im_livechat, *: Refactor JS Public Livechat (step 12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *: website_livechat This commit is a step towards refactoring the JS of public livechat, so that it reuses the same architecture as the code of Discuss. This implies code that uses JS models and OWL components. Task-2942001 closes odoo/odoo#97354 Signed-off-by: Alexandre Kühn (aku) --- .../src/legacy/models/public_livechat.js | 21 +- .../src/legacy/public_livechat_chatbot.js | 185 ++---------------- .../src/legacy/public_livechat_chatbot.xml | 4 +- .../src/legacy/widgets/livechat_button.js | 124 ++---------- .../public_livechat_window.js | 23 +-- .../im_livechat/static/src/models/chatbot.js | 95 +++++++++ .../static/src/models/livechat_button_view.js | 179 +++++++++++++++++ .../models/public_livechat_feedback_view.js | 4 +- ...ic_livechat_global_notification_handler.js | 2 +- .../src/models/public_livechat_message.js | 16 ++ .../static/src/models/public_livechat_view.js | 29 +++ .../src/models/public_livechat_window.js | 6 + .../website_livechat_chatbot_test_script.js | 26 --- .../static/src/models/livechat_button_view.js | 44 +++++ 14 files changed, 425 insertions(+), 333 deletions(-) create mode 100644 addons/im_livechat/static/src/models/public_livechat_view.js create mode 100644 addons/website_livechat/static/src/models/livechat_button_view.js diff --git a/addons/im_livechat/static/src/legacy/models/public_livechat.js b/addons/im_livechat/static/src/legacy/models/public_livechat.js index 0c920eb4382..39706013d75 100644 --- a/addons/im_livechat/static/src/legacy/models/public_livechat.js +++ b/addons/im_livechat/static/src/legacy/models/public_livechat.js @@ -107,17 +107,6 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { this.on('message_added', this, this._onTypingMessageAdded); this.on('message_posted', this, this._onTypingMessagePosted); - // Necessary for thread typing mixin to display is typing notification - // bar text (at least, for the operator in the members). - this._members = ( - this.messaging.publicLivechatGlobal.publicLivechat.operator - ? [{ - id: this.messaging.publicLivechatGlobal.publicLivechat.operator.id, - name: this.messaging.publicLivechatGlobal.publicLivechat.operator.name, - }] - : [] - ); - if (params.data.message_unread_counter !== undefined) { this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: params.data.message_unread_counter @@ -149,7 +138,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { */ getMessages() { // ignore removed messages - return this._messages.filter(message => !message.isEmpty()); + return this.messaging.publicLivechatGlobal.livechatButtonView.messages.filter(message => !message.legacyPublicLivechatMessage.isEmpty()).map(message => message.legacyPublicLivechatMessage); }, /** * Get the text to display when some partners are typing something on the @@ -178,9 +167,11 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { */ getTypingMembersToText() { const typingPartnerIDs = this._typingPartnerIDs; - const typingMembers = _.filter(this._members, function (member) { - return _.contains(typingPartnerIDs, member.id); - }); + const typingMembers = ( + this.messaging.publicLivechatGlobal.publicLivechat.operator && this._typingPartnerIDs.includes(this.messaging.publicLivechatGlobal.publicLivechat.operator.id) + ? [this.messaging.publicLivechatGlobal.publicLivechat.operator] + : [] + ); const sortedTypingMembers = _.sortBy(typingMembers, function (member) { return _.indexOf(typingPartnerIDs, member.id); }); diff --git a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js index 483c188f5dc..a19ea68668b 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js @@ -10,7 +10,6 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button'; import { clear, increment, insertAndReplace, replace } from '@mail/model/model_field_command'; const _t = core._t; -const QWeb = core.qweb; /** * Override of the LivechatButton to include chatbot capabilities. @@ -107,7 +106,7 @@ const QWeb = core.qweb; _chatbotAddMessage(message, options) { message.body = utils.Markup(message.body); this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(message, options); - if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom()) { + if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom()) { this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() }); } @@ -131,10 +130,10 @@ const QWeb = core.qweb; */ _chatbotAwaitUserInput() { if (this._isLastMessageFromCustomer()) { - if (this._chatbotShouldEndScript()) { - this._chatbotEndScript(); + if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } else { - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), @@ -224,54 +223,6 @@ const QWeb = core.qweb; $composerTextField.on('keydown', this.chatbotInputKeyDownHandler); } }, - /** - * Once the script ends, adds a visual element at the end of the chat window allowing to restart - * the whole script. - * - * @private - */ - _chatbotEndScript() { - if ( - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.conversation_closed - ) { - // don't touch anything if the user has closed the conversation, let the chat window - // handle the display - return; - } - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_composer_text_field').addClass('d-none'); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').show(); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_restart').one('click', - this._onChatbotRestartScript.bind(this)); - - }, - /** - * Will display a "Restart script" button in the conversation toolbar. - * - * Side-case: if the conversation has been forwarded to a human operator, we don't want to - * display that restart button. - * - * @private - */ - _chatbotDisplayRestartButton() { - return this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot && (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep || - (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type !== 'forward_operator' || - !this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found)); - }, - /** - * Works as a hook since other modules can add their own step types. - * - * @private - */ - _chatbotIsExpectingUserInput() { - return [ - 'question_phone', - 'question_email', - 'free_input_single', - 'free_input_multi', - ].includes(this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type); - }, /** * Processes the step, depending on the current state of the script and the author of the last * message that was typed into the conversation. @@ -300,15 +251,15 @@ const QWeb = core.qweb; * @private */ _chatbotProcessStep() { - if (this._chatbotShouldEndScript()) { - this._chatbotEndScript(); + if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' && this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found) { this._chatbotEnableInput(); - } else if (this._chatbotIsExpectingUserInput()) { + } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.isExpectingUserInput) { if (this._isLastMessageFromCustomer()) { // user has already typed a message in -> trigger next step - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), @@ -337,7 +288,7 @@ const QWeb = core.qweb; // -> in that case, don't wait and trigger the next step immediately nextStepDelay = 0; } else { - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); } this.messaging.publicLivechatGlobal.livechatButtonView.update({ @@ -349,7 +300,7 @@ const QWeb = core.qweb; } } - if (!this._chatbotDisplayRestartButton()) { + if (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton) { this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); } }, @@ -368,82 +319,6 @@ const QWeb = core.qweb; '_chatbotCurrentStep': this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data, })); }, - /** - * Adds a small "is typing" animation into the chat window. - * - * @private - */ - _chatbotSetIsTyping(isWelcomeMessage=false) { - if (this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout) { - clearTimeout(this.isTypingTimeout); - } - - this._chatbotDisableInput(''); - - this.messaging.publicLivechatGlobal.livechatButtonView.update({ - isTypingTimeout: setTimeout(() => { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_mail_thread_content').append( - $(QWeb.render('im_livechat.legacy.chatbot.is_typing_message', { - 'chatbotImageSrc': `/im_livechat/operator/${ - this.messaging.publicLivechatGlobal.publicLivechat.operator.id - }/avatar`, - 'chatbotName': this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.name, - 'isWelcomeMessage': isWelcomeMessage, - })) - ); - - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); - }, this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay / 3), - }); - }, - /** - * Helper method that checks if the script should be ended or not. - * If the user has closed the conversation -> script has ended. - * - * Otherwise, there are 2 use cases where we want to end the script: - * - * If the current step is the last one AND the conversation was not taken over by a human operator - * 1. AND we expect a user input (or we are on a selection) - * AND the user has already answered - * 2. AND we don't expect a user input - * - * @returns {Boolean} - * @private - */ - _chatbotShouldEndScript() { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.conversation_closed) { - return true; - } - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_is_last && - (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type !== 'forward_operator' || - !this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found) - ) { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_email' - && !this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.is_email_valid - ) { - // email is not (yet) valid, let the user answer / try again - return false; - } else if ( - (this._chatbotIsExpectingUserInput() || - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_selection') && - this.messaging.publicLivechatGlobal.livechatButtonView.messages.length !== 0 - ) { - const lastMessage = this.messaging.publicLivechatGlobal.livechatButtonView.messages[this.messaging.publicLivechatGlobal.livechatButtonView.messages.length - 1]; - if (lastMessage.legacyPublicLivechatMessage.getAuthorID() !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id) { - // we are on the last step of the script, expect a user input and the user has - // already answered - // -> end the script - return true; - } - } else if (!this._chatbotIsExpectingUserInput()) { - // we are on the last step of the script and we do not expect a user input - // -> end the script - return true; - } - } - - return false; - }, /** * A special case is handled for email steps, where we first validate the email (server side) * and we allow the user to try again in case the format is incorrect. @@ -512,7 +387,7 @@ const QWeb = core.qweb; // did not find next step -> end the script this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_is_last = true; this._renderMessages(); - this._chatbotEndScript(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } this._chatbotSaveSession(); @@ -537,37 +412,13 @@ const QWeb = core.qweb; */ _isLastMessageFromCustomer() { const lastMessage = this.messaging.publicLivechatGlobal.livechatButtonView.messages.length !== 0 ? this.messaging.publicLivechatGlobal.livechatButtonView.messages[this.messaging.publicLivechatGlobal.livechatButtonView.messages.length - 1] : null; - return lastMessage && lastMessage.legacyPublicLivechatMessage.getAuthorID() !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id; + return lastMessage && lastMessage.authorId !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id; }, //-------------------------------------------------------------------------- // Private - LiveChat Overrides //-------------------------------------------------------------------------- - /** - * When we enter the "ask feedback" process of the chat, we hide some elements that become - * unnecessary and irrelevant (restart / end messages, any text field values, ...). - * - * @override - * @private - */ - _askFeedback() { - this._super(...arguments); - - if ( - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data - ) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.conversation_closed = true; - this._chatbotSaveSession(); - } - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide(); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_composer_text_field') - .removeClass('d-none') - .val(''); - }, /** * Resuming the chatbot script if we are currently running one. * @@ -583,7 +434,7 @@ const QWeb = core.qweb; return this._super(...arguments).then(() => { window.addEventListener('resize', () => { if (this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); } }); @@ -681,8 +532,8 @@ const QWeb = core.qweb; return; // operator has taken over the conversation, let them speak } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') { this._debouncedChatbotAwaitUserInput(); - } else if (!this._chatbotShouldEndScript()) { - this._chatbotSetIsTyping(); + } else if (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), @@ -690,7 +541,7 @@ const QWeb = core.qweb; ), }); } else { - this._chatbotEndScript(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } this._chatbotSaveSession(); @@ -761,7 +612,7 @@ const QWeb = core.qweb; if (stepIndex + 1 < this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.welcomeSteps.length) { if (welcomeMessageDelay !== 0) { - this._chatbotSetIsTyping(true); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(true); } this.messaging.publicLivechatGlobal.livechatButtonView.update({ @@ -817,7 +668,7 @@ const QWeb = core.qweb; } this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: clear() }); - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), diff --git a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml index 50d3271a0c5..528f4c34291 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml @@ -20,12 +20,12 @@ - - diff --git a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js index 7a2da22e5f6..0ad0276233f 100644 --- a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js +++ b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js @@ -7,7 +7,7 @@ import time from 'web.time'; import utils from 'web.utils'; import Widget from 'web.Widget'; -import { clear, insertAndReplace } from '@mail/model/model_field_command'; +import { insertAndReplace } from '@mail/model/model_field_command'; const _t = core._t; @@ -21,7 +21,7 @@ const LivechatButton = Widget.extend({ 'updated_unread_counter': '_onUpdatedUnreadCounter', }, events: { - 'click': '_openChat' + 'click': '_onClick' }, init(parent, messaging) { this._super(parent); @@ -53,12 +53,15 @@ const LivechatButton = Widget.extend({ for (const m of this.messaging.publicLivechatGlobal.livechatButtonView.history) { this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(m); } - this._openChat(); + this.messaging.publicLivechatGlobal.livechatButtonView.openChat(); } else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.livechatButtonView.rule.action === 'auto_popup') { const autoPopupCookie = utils.get_cookie('im_livechat_auto_popup'); if (!autoPopupCookie || JSON.parse(autoPopupCookie)) { this.messaging.publicLivechatGlobal.livechatButtonView.update({ - autoOpenChatTimeout: setTimeout(this._openChat.bind(this), this.messaging.publicLivechatGlobal.livechatButtonView.rule.auto_popup_timer * 1000), + autoOpenChatTimeout: setTimeout( + this.messaging.publicLivechatGlobal.livechatButtonView.openChat, + this.messaging.publicLivechatGlobal.livechatButtonView.rule.auto_popup_timer * 1000, + ), }); } } @@ -82,85 +85,6 @@ const LivechatButton = Widget.extend({ // Private //-------------------------------------------------------------------------- - /** - * @private - */ - _askFeedback() { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_thread_composer input').prop('disabled', true); - this.messaging.publicLivechatGlobal.update({ feedbackView: insertAndReplace() }); - }, - /** - * @private - */ - _closeChat() { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatWindow: clear() }); - utils.set_cookie('im_livechat_session', "", -1); // remove cookie - }, - /** - * @private - */ - _openChat: _.debounce(function () { - if (this.messaging.publicLivechatGlobal.livechatButtonView.isOpeningChat) { - return; - } - const cookie = utils.get_cookie('im_livechat_session'); - let def; - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: true }); - clearTimeout(this.messaging.publicLivechatGlobal.livechatButtonView.autoOpenChatTimeout); - if (cookie) { - def = Promise.resolve(JSON.parse(cookie)); - } else { - // re-initialize messages cache - this.messaging.publicLivechatGlobal.livechatButtonView.update({ messages: clear() }); - def = session.rpc( - '/im_livechat/get_session', - this._prepareGetSessionParameters(), - { shadow: true }, - ); - } - def.then((livechatData) => { - if (!livechatData || !livechatData.operator_pid) { - try { - this.displayNotification({ - message: _t("No available collaborator, please try again later."), - sticky: true, - }); - } catch (_err) { - /** - * Failure in displaying notification happens when - * notification service doesn't exist, which is the case in - * external lib. We don't want notifications in external - * lib at the moment because they use bootstrap toast and - * we don't want to include boostrap in external lib. - */ - console.warn(_t("No available collaborator, please try again later.")); - } - } else { - this.messaging.publicLivechatGlobal.update({ - publicLivechat: insertAndReplace({ data: livechatData }), - }); - return this._openChatWindow().then(() => { - if (!this.messaging.publicLivechatGlobal.livechatButtonView.history) { - this._sendWelcomeMessage(); - } - this._renderMessages(); - this.messaging.publicLivechatGlobal.update({ notificationHandler: insertAndReplace() }); - - utils.set_cookie('im_livechat_session', utils.unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); - utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60 * 60); - if (this.messaging.publicLivechatGlobal.publicLivechat.operator) { - const operatorPidId = this.messaging.publicLivechatGlobal.publicLivechat.operator.id; - const oneWeek = 7 * 24 * 60 * 60; - utils.set_cookie('im_livechat_previous_operator_pid', operatorPidId, oneWeek); - } - }); - } - }).then(() => { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: false }); - }).guardedCatch(() => { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: false }); - }); - }, 200, true), /** * Will try to get a previous operator for this visitor. * If the visitor already had visitor A, it's better for his user experience @@ -206,11 +130,10 @@ const LivechatButton = Widget.extend({ * @private */ _renderMessages() { - const shouldScroll = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded && this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom(); - this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat._messages = this.messaging.publicLivechatGlobal.livechatButtonView.messages.map(message => message.legacyPublicLivechatMessage); + const shouldScroll = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded && this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom(); this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.render(); if (shouldScroll) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); } }, /** @@ -240,9 +163,9 @@ const LivechatButton = Widget.extend({ */ console.warn(_t("Session expired... Please refresh and try again.")); } - this._closeChat(); + this.messaging.publicLivechatGlobal.livechatButtonView.closeChat(); } - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); }); }, /** @@ -269,6 +192,12 @@ const LivechatButton = Widget.extend({ // Handlers //-------------------------------------------------------------------------- + /** + * @private + */ + _onClick() { + this.messaging.publicLivechatGlobal.livechatButtonView.openChat(); + }, /** * @private * @param {OdooEvent} ev @@ -281,11 +210,11 @@ const LivechatButton = Widget.extend({ }); if (shouldAskFeedback) { this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.toggleFold(false); - this._askFeedback(); + this.messaging.publicLivechatGlobal.livechatButtonView.askFeedback(); } else { - this._closeChat(); + this.messaging.publicLivechatGlobal.livechatButtonView.closeChat(); } - this._visitorLeaveSession(); + this.messaging.publicLivechatGlobal.livechatButtonView.leaveSession(); }, /** * @private @@ -324,19 +253,6 @@ const LivechatButton = Widget.extend({ ev.stopPropagation(); this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.renderHeader(); }, - /** - * @private - * Called when the visitor leaves the livechat chatter the first time (first click on X button) - * this will deactivate the mail_channel, notify operator that visitor has left the channel. - */ - _visitorLeaveSession() { - const cookie = utils.get_cookie('im_livechat_session'); - if (cookie) { - const channel = JSON.parse(cookie); - session.rpc('/im_livechat/visitor_leave_session', {uuid: channel.uuid}); - utils.set_cookie('im_livechat_session', "", -1); // remove cookie - } - }, }); export default LivechatButton; diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js index 0dd1efe2c1d..8eaa6c03afa 100644 --- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js +++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js @@ -1,7 +1,5 @@ /** @odoo-module **/ -import PublicLivechatView from '@im_livechat/legacy/widgets/public_livechat_view/public_livechat_view'; - import config from 'web.config'; import { _t, qweb } from 'web.core'; import Widget from 'web.Widget'; @@ -46,13 +44,6 @@ const PublicLivechatWindow = Widget.extend({ async start() { this.$input = this.$('.o_composer_text_field'); this.$header = this.$('.o_thread_window_header'); - const options = { - displayMarkAsRead: false, - }; - if (this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat._type === 'document_thread') { - options.displayDocumentLinks = false; - } - this._publicLivechatView = new PublicLivechatView(this, this.messaging, options); // animate the (un)folding of thread windows this.$el.css({ transition: 'height ' + this.FOLD_ANIMATION_DURATION + 'ms linear' }); @@ -65,8 +56,8 @@ const PublicLivechatWindow = Widget.extend({ const margin_dir = _t.database.parameters.direction === "rtl" ? "margin-left" : "margin-right"; this.$el.css(margin_dir, $.position.scrollbarWidth()); } - const def = this._publicLivechatView.replace(this.$('.o_thread_window_content')).then(() => { - this._publicLivechatView.$el.on('scroll', this, this._debouncedOnScroll); + const def = this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.replace(this.$('.o_thread_window_content')).then(() => { + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.$el.on('scroll', this, this._debouncedOnScroll); }); await Promise.all([this._super(), def]); if (this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor) { @@ -102,7 +93,7 @@ const PublicLivechatWindow = Widget.extend({ */ render() { this.renderHeader(); - this._publicLivechatView.render({ displayLoadMore: false }); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.render({ displayLoadMore: false }); }, /** * Render the header of this thread window. @@ -128,7 +119,7 @@ const PublicLivechatWindow = Widget.extend({ * @param {$.Element} $element */ replaceContentWith($element) { - $element.replace(this._publicLivechatView.$el); + $element.replace(this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.$el); }, /** * Toggle the fold state of this thread window. Also update the fold state @@ -155,7 +146,7 @@ const PublicLivechatWindow = Widget.extend({ */ updateVisualFoldState() { if (!this.messaging.publicLivechatGlobal.publicLivechat.isFolded) { - this._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); this._focusInput(); } const height = this.messaging.publicLivechatGlobal.publicLivechat.isFolded ? this.HEIGHT_FOLDED : this.HEIGHT_OPEN; @@ -203,7 +194,7 @@ const PublicLivechatWindow = Widget.extend({ this.trigger_up('post_message_chat_window', { messageData }); this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.postMessage(messageData) .then(() => { - this._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); }); }, @@ -301,7 +292,7 @@ const PublicLivechatWindow = Widget.extend({ * @private */ _onScroll() { - if (this._publicLivechatView.isAtBottom()) { + if (this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom()) { this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.markAsRead(); } }, diff --git a/addons/im_livechat/static/src/models/chatbot.js b/addons/im_livechat/static/src/models/chatbot.js index 64f8ec3b8cd..644109c8b42 100644 --- a/addons/im_livechat/static/src/models/chatbot.js +++ b/addons/im_livechat/static/src/models/chatbot.js @@ -8,6 +8,21 @@ registerModel({ name: 'Chatbot', identifyingFields: ['livechatButtonViewOwner'], recordMethods: { + /** + * @private + * @returns {boolean} + */ + _computeIsExpectingUserInput() { + if (!this.currentStep) { + return clear(); + } + return [ + 'question_phone', + 'question_email', + 'free_input_single', + 'free_input_multi', + ].includes(this.currentStep.data.chatbot_step_type); + }, /** * @private * @returns {string} @@ -15,6 +30,24 @@ registerModel({ _computeName() { return this.data.name; }, + /** + * Will display a "Restart script" button in the conversation toolbar. + * + * Side-case: if the conversation has been forwarded to a human operator, we don't want to + * display that restart button. + * + * @private + * @returns {boolean} + */ + _computeHasRestartButton() { + return Boolean( + !this.currentStep || + ( + this.currentStep.data.chatbot_step_type !== 'forward_operator' || + !this.currentStep.data.chatbot_operator_found + ) + ); + }, /** * @private * @returns {Array|FieldCommand} @@ -42,6 +75,56 @@ registerModel({ _computeScriptId() { return this.data.chatbot_script_id; }, + /** + * Helper method that checks if the script should be ended or not. + * If the user has closed the conversation -> script has ended. + * + * Otherwise, there are 2 use cases where we want to end the script: + * + * If the current step is the last one AND the conversation was not taken over by a human operator + * 1. AND we expect a user input (or we are on a selection) + * AND the user has already answered + * 2. AND we don't expect a user input + * + * @private + * @returns {boolean} + */ + _computeShouldEndScript() { + if (!this.currentStep) { + return clear(); + } + if (this.currentStep.data.conversation_closed) { + return true; + } + if (this.currentStep.data.chatbot_step_is_last && + (this.currentStep.data.chatbot_step_type !== 'forward_operator' || + !this.currentStep.data.chatbot_operator_found) + ) { + if (this.currentStep.data.chatbot_step_type === 'question_email' + && !this.currentStep.data.is_email_valid + ) { + // email is not (yet) valid, let the user answer / try again + return false; + } else if ( + (this.isExpectingUserInput || + this.currentStep.data.chatbot_step_type === 'question_selection') && + this.messaging.publicLivechatGlobal.livechatButtonView.messages.length !== 0 + ) { + const lastMessage = this.messaging.publicLivechatGlobal.livechatButtonView.messages[this.messaging.publicLivechatGlobal.livechatButtonView.messages.length - 1]; + if (lastMessage.authorId !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id) { + // we are on the last step of the script, expect a user input and the user has + // already answered + // -> end the script + return true; + } + } else if (!this.isExpectingUserInput) { + // we are on the last step of the script and we do not expect a user input + // -> end the script + return true; + } + } + return false; + }, /** * @private * @returns {Array|FieldCommand} @@ -56,6 +139,14 @@ registerModel({ inverse: 'chabotOwner', isCausal: true, }), + hasRestartButton: attr({ + compute: '_computeHasRestartButton', + default: false, + }), + isExpectingUserInput: attr({ + compute: '_computeIsExpectingUserInput', + default: false, + }), lastWelcomeStep: attr({ compute: '_computeLastWelcomeStep', }), @@ -74,6 +165,10 @@ registerModel({ scriptId: attr({ compute: '_computeScriptId', }), + shouldEndScript: attr({ + compute: '_computeShouldEndScript', + default: false, + }), welcomeSteps: attr({ compute: '_computeWelcomeSteps', }), diff --git a/addons/im_livechat/static/src/models/livechat_button_view.js b/addons/im_livechat/static/src/models/livechat_button_view.js index eed3d6bc1dd..290855f429b 100644 --- a/addons/im_livechat/static/src/models/livechat_button_view.js +++ b/addons/im_livechat/static/src/models/livechat_button_view.js @@ -6,6 +6,9 @@ import { registerModel } from '@mail/model/model_core'; import { attr, many, one } from '@mail/model/model_field'; import { clear, insertAndReplace, replace } from '@mail/model/model_field_command'; +import { qweb } from 'web.core'; +import { get_cookie, set_cookie, unaccent } from 'web.utils'; + registerModel({ name: 'LivechatButtonView', identifyingFields: ['publicLivechatGlobalOwner'], @@ -43,6 +46,46 @@ registerModel({ }); } }, + askFeedback() { + this.chatWindow.legacyChatWindow.$('.o_thread_composer input').prop('disabled', true); + this.messaging.publicLivechatGlobal.update({ feedbackView: insertAndReplace() }); + /** + * When we enter the "ask feedback" process of the chat, we hide some elements that become + * unnecessary and irrelevant (restart / end messages, any text field values, ...). + */ + if ( + this.chatbot && + this.chatbot.currentStep && + this.chatbot.currentStep.data + ) { + this.chatbot.currentStep.data.conversation_closed = true; + this.widget._chatbotSaveSession(); + } + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide(); + this.chatWindow.legacyChatWindow.$('.o_composer_text_field') + .removeClass('d-none') + .val(''); + }, + /** + * Once the script ends, adds a visual element at the end of the chat window allowing to restart + * the whole script. + */ + chatbotEndScript() { + if ( + this.chatbot.currentStep && + this.chatbot.currentStep.data && + this.chatbot.currentStep.data.conversation_closed + ) { + // don't touch anything if the user has closed the conversation, let the chat window + // handle the display + return; + } + this.chatWindow.legacyChatWindow.$('.o_composer_text_field').addClass('d-none'); + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').show(); + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_restart').one('click', + this.widget._onChatbotRestartScript.bind(this.widget)); + }, /** * See '_chatbotSaveSession'. * @@ -68,6 +111,57 @@ registerModel({ this.chatbot.update({ currentStep: insertAndReplace({ data: this.localStorageChatbotState._chatbotCurrentStep }) }); } }, + closeChat() { + this.update({ chatWindow: clear() }); + set_cookie('im_livechat_session', "", -1); // remove cookie + }, + /** + * Called when the visitor leaves the livechat chatter the first time (first click on X button) + * this will deactivate the mail_channel, notify operator that visitor has left the channel. + */ + leaveSession() { + const cookie = get_cookie('im_livechat_session'); + if (cookie) { + const channel = JSON.parse(cookie); + this.messaging.rpc({ route: '/im_livechat/visitor_leave_session', params: { uuid: channel.uuid } }); + set_cookie('im_livechat_session', "", -1); // remove cookie + } + }, + openChat() { + if (this.isOpenChatDebounced) { + this.openChatDebounced(); + } else { + this._openChat(); + } + }, + /** + * Adds a small "is typing" animation into the chat window. + * + * @param {boolean} [isWelcomeMessage=false] + */ + chatbotSetIsTyping(isWelcomeMessage = false) { + if (this.isTypingTimeout) { + clearTimeout(this.isTypingTimeout); + } + this.widget._chatbotDisableInput(''); + this.update({ + isTypingTimeout: setTimeout( + () => { + this.chatWindow.legacyChatWindow.$('.o_mail_thread_content').append( + $(qweb.render('im_livechat.legacy.chatbot.is_typing_message', { + 'chatbotImageSrc': `/im_livechat/operator/${ + this.messaging.publicLivechatGlobal.publicLivechat.operator.id + }/avatar`, + 'chatbotName': this.chatbot.name, + 'isWelcomeMessage': isWelcomeMessage, + })) + ); + this.chatWindow.publicLivechatView.widget.scrollToBottom(); + }, + this.chatbot.messageDelay / 3, + ), + }); + }, /** * @private * @returns {string} @@ -213,6 +307,13 @@ registerModel({ } return clear(); }, + /** + * @private + * @returns {FieldCommand} + */ + _computeIsOpenChatDebounced() { + return clear(); + }, /** * @private * @returns {FieldCommand} @@ -227,6 +328,13 @@ registerModel({ } return JSON.parse(data); }, + /** + * @private + * @returns {_.debounce} + */ + _computeOpenChatDebounced() { + return _.debounce(this._openChat, 200, true); + }, /** * @private * @returns {string} @@ -253,6 +361,70 @@ registerModel({ _computeTitleColor() { return this.messaging.publicLivechatGlobal.options.title_color; }, + /** + * @private + */ + _openChat() { + if (this.isOpeningChat) { + return; + } + const cookie = get_cookie('im_livechat_session'); + let def; + this.update({ isOpeningChat: true }); + clearTimeout(this.autoOpenChatTimeout); + if (cookie) { + def = Promise.resolve(JSON.parse(cookie)); + } else { + // re-initialize messages cache + this.update({ messages: clear() }); + def = this.messaging.rpc({ + route: '/im_livechat/get_session', + params: this.widget._prepareGetSessionParameters(), + }, { silent: true }); + } + def.then((livechatData) => { + if (!livechatData || !livechatData.operator_pid) { + try { + this.widget.displayNotification({ + message: this.env._t("No available collaborator, please try again later."), + sticky: true, + }); + } catch (_err) { + /** + * Failure in displaying notification happens when + * notification service doesn't exist, which is the case in + * external lib. We don't want notifications in external + * lib at the moment because they use bootstrap toast and + * we don't want to include boostrap in external lib. + */ + console.warn(this.env._t("No available collaborator, please try again later.")); + } + } else { + this.messaging.publicLivechatGlobal.update({ + publicLivechat: insertAndReplace({ data: livechatData }), + }); + return this.widget._openChatWindow().then(() => { + if (!this.history) { + this.widget._sendWelcomeMessage(); + } + this.widget._renderMessages(); + this.messaging.publicLivechatGlobal.update({ notificationHandler: insertAndReplace() }); + + set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); + set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60 * 60); + if (this.messaging.publicLivechatGlobal.publicLivechat.operator) { + const operatorPidId = this.messaging.publicLivechatGlobal.publicLivechat.operator.id; + const oneWeek = 7 * 24 * 60 * 60; + set_cookie('im_livechat_previous_operator_pid', operatorPidId, oneWeek); + } + }); + } + }).then(() => { + this.update({ isOpeningChat: false }); + }).guardedCatch(() => { + this.update({ isOpeningChat: false }); + }); + }, }, fields: { autoOpenChatTimeout: attr(), @@ -311,6 +483,10 @@ registerModel({ isChatbotRedirecting: attr({ default: false, }), + isOpenChatDebounced: attr({ + compute: '_computeIsOpenChatDebounced', + default: true, + }), isOpeningChat: attr({ default: false, }), @@ -326,6 +502,9 @@ registerModel({ compute: '_computeLocalStorageChatbotState', }), messages: many('PublicLivechatMessage'), + openChatDebounced: attr({ + compute: '_computeOpenChatDebounced', + }), publicLivechatGlobalOwner: one('PublicLivechatGlobal', { inverse: 'livechatButtonView', readonly: true, diff --git a/addons/im_livechat/static/src/models/public_livechat_feedback_view.js b/addons/im_livechat/static/src/models/public_livechat_feedback_view.js index 661486c379d..1db032a4c33 100644 --- a/addons/im_livechat/static/src/models/public_livechat_feedback_view.js +++ b/addons/im_livechat/static/src/models/public_livechat_feedback_view.js @@ -26,8 +26,8 @@ registerModel({ }, }, recordMethods: { - _onFeedbackSent(...args) { - this.messaging.publicLivechatGlobal.livechatButtonView.widget._closeChat(...args); + _onFeedbackSent() { + this.messaging.publicLivechatGlobal.livechatButtonView.closeChat(); }, _onSendMessage(...args) { this.messaging.publicLivechatGlobal.livechatButtonView.widget._sendMessage(...args); diff --git a/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js b/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js index e3bdedb03e7..78f8646c2c5 100644 --- a/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js +++ b/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js @@ -66,7 +66,7 @@ registerModel({ } notificationData.body = utils.Markup(notificationData.body); this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(notificationData); - if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom()) { + if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom()) { this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() }); } this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages(); diff --git a/addons/im_livechat/static/src/models/public_livechat_message.js b/addons/im_livechat/static/src/models/public_livechat_message.js index 8a92f022229..76f60f51c2c 100644 --- a/addons/im_livechat/static/src/models/public_livechat_message.js +++ b/addons/im_livechat/static/src/models/public_livechat_message.js @@ -2,11 +2,27 @@ import { registerModel } from '@mail/model/model_core'; import { attr } from '@mail/model/model_field'; +import { clear } from '@mail/model/model_field_command'; registerModel({ name: 'PublicLivechatMessage', identifyingFields: ['id'], + recordMethods: { + /** + * @private + * @returns {FieldCommand|integer} + */ + _computeAuthorId() { + if (!(this.data.author_id && this.data.author_id[0])) { + return clear(); + } + return this.data.author_id[0]; + }, + }, fields: { + authorId: attr({ + compute: '_computeAuthorId', + }), data: attr(), id: attr({ readonly: true, diff --git a/addons/im_livechat/static/src/models/public_livechat_view.js b/addons/im_livechat/static/src/models/public_livechat_view.js new file mode 100644 index 00000000000..f191a0e4a1a --- /dev/null +++ b/addons/im_livechat/static/src/models/public_livechat_view.js @@ -0,0 +1,29 @@ +/** @odoo-module **/ + +import PublicLivechatView from '@im_livechat/legacy/widgets/public_livechat_view/public_livechat_view'; + +import { registerModel } from '@mail/model/model_core'; +import { attr, one } from '@mail/model/model_field'; + +registerModel({ + name: 'PublicLivechatView', + identifyingFields: ['publicLivechatWindowOwner'], + lifecycleHooks: { + _created() { + this.update({ + widget: new PublicLivechatView(this, this.messaging, { displayMarkAsRead: false }), + }); + }, + _willDelete() { + this.widget.destroy(); + }, + }, + fields: { + publicLivechatWindowOwner: one('PublicLivechatWindow', { + inverse: 'publicLivechatView', + readonly: true, + required: true, + }), + widget: attr(), + }, +}); diff --git a/addons/im_livechat/static/src/models/public_livechat_window.js b/addons/im_livechat/static/src/models/public_livechat_window.js index 4f61034a4b1..505e19ffb90 100644 --- a/addons/im_livechat/static/src/models/public_livechat_window.js +++ b/addons/im_livechat/static/src/models/public_livechat_window.js @@ -4,6 +4,7 @@ import PublicLivechatWindow from '@im_livechat/legacy/widgets/public_livechat_wi import { registerModel } from '@mail/model/model_core'; import { attr, one } from '@mail/model/model_field'; +import { insertAndReplace } from '@mail/model/model_field_command'; registerModel({ name: 'PublicLivechatWindow', @@ -46,5 +47,10 @@ registerModel({ readonly: true, required: true, }), + publicLivechatView: one('PublicLivechatView', { + default: insertAndReplace(), + inverse: 'publicLivechatWindowOwner', + isCausal: true, + }), }, }); diff --git a/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js b/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js index 0e9a369719f..4dbc58f2a69 100644 --- a/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js +++ b/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js @@ -34,7 +34,6 @@ const LivechatButtonTestChatbot = LivechatButton.extend({ data: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.lastWelcomeStep, }), }); - this._channelData = this.messaging.publicLivechatGlobal.livechatButtonView.testChatbotData.channel; }, /** @@ -46,31 +45,6 @@ const LivechatButtonTestChatbot = LivechatButton.extend({ willStart: function () { return this.messaging.publicLivechatGlobal.loadQWebTemplate(); }, - - /** - * Overridden to avoid calling the "get_session" endpoint as it requires a im_livechat.channel - * linked to work properly. - * - * Here, we already have a mail.channel created (see 'website_livechat_chatbot_test_script') so we - * use its configuration to create the 'WebsiteLivechat' Widget. - * - * @private - * @override - */ - _openChat: function () { - this.messaging.publicLivechatGlobal.update({ - publicLivechat: insertAndReplace({ data: this._channelData }), - }); - - return this._openChatWindow().then(() => { - this._sendWelcomeMessage(); - this._renderMessages(); - this.call('bus_service', 'addChannel', this.messaging.publicLivechatGlobal.publicLivechat.uuid); - this.call('bus_service', 'startPolling'); - utils.set_cookie('im_livechat_session', utils.unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: false }); - }); - }, }); publicWidget.registry.livechatChatbotTestScript = publicWidget.Widget.extend({ diff --git a/addons/website_livechat/static/src/models/livechat_button_view.js b/addons/website_livechat/static/src/models/livechat_button_view.js new file mode 100644 index 00000000000..fe6e7a99fbb --- /dev/null +++ b/addons/website_livechat/static/src/models/livechat_button_view.js @@ -0,0 +1,44 @@ +/** @odoo-module **/ + +import { patchRecordMethods } from '@mail/model/model_core'; +import { insertAndReplace } from '@mail/model/model_field_command'; +// ensure that the model definition is loaded before the patch +import '@im_livechat/models/livechat_button_view'; + +import { set_cookie, unaccent } from 'web.utils'; + +patchRecordMethods('LivechatButtonView', { + /** + * @override + */ + _computeIsOpenChatDebounced() { + if (this.isTestChatbot) { + return false; + } + return this._super(); + }, + /** + * Overridden to avoid calling the "get_session" endpoint as it requires a im_livechat.channel + * linked to work properly. + * + * Here, we already have a mail.channel created (see 'website_livechat_chatbot_test_script') so we + * use its configuration to create the 'WebsiteLivechat' Widget. + * + * @override + */ + async _openChat() { + if (!this.isTestChatbot) { + return this._super(); + } + this.messaging.publicLivechatGlobal.update({ + publicLivechat: insertAndReplace({ data: this.testChatbotData.channel }), + }); + await this.widget._openChatWindow(); + this.widget._sendWelcomeMessage(); + this.widget._renderMessages(); + this.env.services.bus_service.addChannel(this.messaging.publicLivechatGlobal.publicLivechat.uuid); + this.env.services.bus_service.startPolling(); + set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); + this.update({ isOpeningChat: false }); + }, +});