[IMP] im_livechat, *: Refactor JS Public Livechat (step 12)
*: 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) <aku@odoo.com>
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
<!-- Extend the base livechat window header to include a button allowing to restart the script -->
|
||||
<t t-extend="im_livechat.legacy.PublicLivechatWindow.HeaderContent">
|
||||
<t t-jquery="span.o_thread_window_title" t-operation="after">
|
||||
<a t-if="widget.isMobile() && widget.getParent() && widget.getParent()._chatbotDisplayRestartButton()"
|
||||
<a t-if="widget.isMobile() && widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot && widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton"
|
||||
href="#" class="o_livechat_chatbot_main_restart fa fa-1x fa-refresh"
|
||||
title="Restart Conversation"/>
|
||||
</t>
|
||||
<t t-jquery="span.o_thread_window_buttons a.o_thread_window_close" t-operation="before">
|
||||
<a t-if="widget.getParent() && widget.getParent()._chatbotDisplayRestartButton()"
|
||||
<a t-if="widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot && widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton"
|
||||
href="#" class="o_livechat_chatbot_main_restart fa fa-refresh"
|
||||
title="Restart Conversation"/>
|
||||
</t>
|
||||
|
||||
@@ -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;
|
||||
|
||||
+7
-16
@@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user