[IMP] im_livechat, *: Refactor JS Public Livechat (step 1)

*: 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-2887296

closes odoo/odoo#93966

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2022-06-17 21:37:35 +02:00
parent 873add0dd6
commit 1e72215353
6 changed files with 124 additions and 73 deletions
@@ -52,17 +52,16 @@ const QWeb = core.qweb;
async willStart() {
const superResult = await this._super(...arguments);
this._isChatbot = false;
this.chatbotState = null;
if (this._rule && !!this._rule.chatbot) {
this._isChatbot = true;
this.messaging.livechatButtonView.update({ isChatbot: true });
this.chatbotState = 'init';
} else if (this._history !== null && this._history.length === 0) {
this._livechatInit = await session.rpc('/im_livechat/init', {channel_id: this.options.channel_id});
if (this._livechatInit.rule.chatbot) {
this._isChatbot = true;
this.messaging.livechatButtonView.update({ isChatbot: true });
this.chatbotState = 'welcome';
}
} else if (this._history !== null && this._history.length !== 0) {
@@ -70,18 +69,12 @@ const QWeb = core.qweb;
if (sessionCookie) {
const sessionKey = 'im_livechat.chatbot.state.uuid_' + JSON.parse(sessionCookie).uuid;
if (localStorage.getItem(sessionKey)) {
this._isChatbot = true;
this.messaging.livechatButtonView.update({ isChatbot: true });
this.chatbotState = 'restore_session';
}
}
}
if (this._isChatbot) {
// void the default livechat placeholder in the user input
// as we use it for specific things (e.g: showing "please select an option above")
this.options.input_placeholder = '';
}
if (this.chatbotState === 'init') {
// we landed on a website page where a channel rule is configured to run a chatbot.script
// -> initialize necessary state
@@ -100,7 +93,7 @@ const QWeb = core.qweb;
this._history = null;
this._rule = this._livechatInit.rule;
this._chatbot = this._livechatInit.rule.chatbot;
this._isChatbot = true;
this.messaging.livechatButtonView.update({ isChatbot: true });
this._chatbotBatchWelcomeMessages = true;
} else if (this.chatbotState === 'restore_session') {
// we landed on a website page and a chatbot script is currently running
@@ -125,7 +118,7 @@ const QWeb = core.qweb;
_chatbotAddMessage(message, options) {
message.body = utils.Markup(message.body);
this._addMessage(message, options);
if (this._chatWindow.isFolded() || !this._chatWindow.isAtBottom()) {
if (this.messaging.livechatButtonView.chatWindow.isFolded() || !this.messaging.livechatButtonView.chatWindow.isAtBottom()) {
this._livechat.incrementUnreadCounter();
}
@@ -208,7 +201,7 @@ const QWeb = core.qweb;
* @private
*/
_chatbotDisableInput(disableText) {
this._chatWindow.$('.o_composer_text_field')
this.messaging.livechatButtonView.chatWindow.$('.o_composer_text_field')
.prop('disabled', true)
.addClass('text-center font-italic bg-200')
.val(disableText);
@@ -218,7 +211,7 @@ const QWeb = core.qweb;
* @private
*/
_chatbotEnableInput() {
const $composerTextField = this._chatWindow.$('.o_composer_text_field');
const $composerTextField = this.messaging.livechatButtonView.chatWindow.$('.o_composer_text_field');
$composerTextField
.prop('disabled', false)
.removeClass('text-center font-italic bg-200')
@@ -241,9 +234,9 @@ const QWeb = core.qweb;
* @private
*/
_chatbotEndScript() {
this._chatWindow.$('.o_composer_text_field').addClass('d-none');
this._chatWindow.$('.o_livechat_chatbot_end').show();
this._chatWindow.$('.o_livechat_chatbot_restart').one('click',
this.messaging.livechatButtonView.chatWindow.$('.o_composer_text_field').addClass('d-none');
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_end').show();
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_restart').one('click',
this._onChatbotRestartScript.bind(this));
},
@@ -256,7 +249,7 @@ const QWeb = core.qweb;
* @private
*/
_chatbotDisplayRestartButton() {
return this._isChatbot && (!this._chatbotCurrentStep ||
return this.messaging.livechatButtonView.isChatbot && (!this._chatbotCurrentStep ||
(this._chatbotCurrentStep.chatbot_step_type !== 'forward_operator' ||
!this._chatbotCurrentStep.chatbot_operator_found));
},
@@ -327,7 +320,7 @@ const QWeb = core.qweb;
if (triggerNextStep) {
let nextStepDelay = this._chatbotMessageDelay;
if (this._chatWindow.$('.o_livechat_chatbot_typing').length !== 0) {
if (this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_typing').length !== 0) {
// special case where we already have a "is typing" message displayed
// can happen when the previous step did not trigger any message posted from the bot
// e.g: previous step was "forward_operator" and no-one is available
@@ -343,7 +336,7 @@ const QWeb = core.qweb;
}
if (!this._chatbotDisplayRestartButton()) {
this._chatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
}
},
/**
@@ -374,7 +367,7 @@ const QWeb = core.qweb;
if (chatbotState) {
chatbotState = JSON.parse(chatbotState);
this._isChatbot = true;
this.messaging.livechatButtonView.update({ isChatbot: true });
this._chatbot = chatbotState._chatbot;
this._chatbotCurrentStep = chatbotState._chatbotCurrentStep;
}
@@ -408,7 +401,7 @@ const QWeb = core.qweb;
this._chatbotDisableInput('');
this.isTypingTimeout = setTimeout(() => {
this._chatWindow.$('.o_mail_thread_content').append(
this.messaging.livechatButtonView.chatWindow.$('.o_mail_thread_content').append(
$(QWeb.render('im_livechat.legacy.chatbot.is_typing_message', {
'chatbotImageSrc': `/im_livechat/operator/${this._livechat.getOperatorPID()[0]}/avatar`,
'chatbotName': this._chatbot.chatbot_name,
@@ -416,7 +409,7 @@ const QWeb = core.qweb;
}))
);
this._chatWindow.scrollToBottom();
this.messaging.livechatButtonView.chatWindow.scrollToBottom();
}, this._chatbotMessageDelay / 3);
},
/**
@@ -564,9 +557,9 @@ const QWeb = core.qweb;
_askFeedback() {
this._super(...arguments);
this._chatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
this._chatWindow.$('.o_livechat_chatbot_end').hide();
this._chatWindow.$('.o_composer_text_field')
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_end').hide();
this.messaging.livechatButtonView.chatWindow.$('.o_composer_text_field')
.removeClass('d-none')
.val('');
},
@@ -584,8 +577,8 @@ const QWeb = core.qweb;
_openChatWindow() {
return this._super(...arguments).then(() => {
window.addEventListener('resize', () => {
if (this._chatWindow) {
this._chatWindow.scrollToBottom();
if (this.messaging.livechatButtonView.chatWindow) {
this.messaging.livechatButtonView.chatWindow.scrollToBottom();
}
});
@@ -601,7 +594,7 @@ const QWeb = core.qweb;
_prepareGetSessionParameters() {
const parameters = this._super(...arguments);
if (this._isChatbot) {
if (this.messaging.livechatButtonView.isChatbot) {
parameters.chatbot_script_id = this._chatbot.chatbot_script_id;
}
@@ -615,11 +608,11 @@ const QWeb = core.qweb;
const self = this;
this._chatWindow.$('.o_thread_message:last .o_livechat_chatbot_options li').each(function () {
this.messaging.livechatButtonView.chatWindow.$('.o_thread_message:last .o_livechat_chatbot_options li').each(function () {
$(this).on('click', self._onChatbotOptionClicked.bind(self));
});
this._chatWindow.$('.o_livechat_chatbot_main_restart').on('click',
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_main_restart').on('click',
this._onChatbotRestartScript.bind(this));
if (this._messages.length !== 0) {
@@ -652,7 +645,7 @@ const QWeb = core.qweb;
const superArguments = arguments;
const superMethod = this._super;
if (this._isChatbot && this._chatbotCurrentStep) {
if (this.messaging.livechatButtonView.isChatbot && this._chatbotCurrentStep) {
await this._chatbotPostWelcomeMessages();
}
@@ -661,7 +654,7 @@ const QWeb = core.qweb;
return;
}
if (this._isChatbot && this._chatbotCurrentStep) {
if (this.messaging.livechatButtonView.isChatbot && this._chatbotCurrentStep) {
if (this._chatbotCurrentStep.chatbot_step_type === 'forward_operator' &&
this._chatbotCurrentStep.chatbot_operator_found) {
return; // operator has taken over the conversation, let them speak
@@ -684,7 +677,7 @@ const QWeb = core.qweb;
* @private
*/
_sendWelcomeMessage() {
if (this._isChatbot) {
if (this.messaging.livechatButtonView.isChatbot) {
this._sendWelcomeChatbotMessage(
0,
this._chatbotBatchWelcomeMessages ? 0 : this._chatbotMessageDelay,
@@ -769,8 +762,8 @@ const QWeb = core.qweb;
* @private
*/
async _onChatbotRestartScript(ev) {
this._chatWindow.$('.o_composer_text_field').removeClass('d-none');
this._chatWindow.$('.o_livechat_chatbot_end').hide();
this.messaging.livechatButtonView.chatWindow.$('.o_composer_text_field').removeClass('d-none');
this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_end').hide();
if (this.nextStepTimeout) {
clearTimeout(this.nextStepTimeout);
@@ -19,7 +19,7 @@ WebsiteLivechatMessage.include({
init(parent, data, options) {
this._super(...arguments);
if (parent._isChatbot) {
if (parent.messaging.livechatButtonView.isChatbot) {
this._chatbotId = parent._chatbot.chatbot_script_id;
this._chatbotName = parent._chatbot.chatbot_name;
this._chatbotOperatorPartnerId = parent._chatbot.chatbot_operator_partner_id;
@@ -32,17 +32,12 @@ const LivechatButton = Widget.extend({
this._super(parent);
this.messaging = messaging;
this.options = _.defaults(this.messaging.publicLivechatOptions || {}, {
input_placeholder: _t("Ask something ..."),
default_username: _t("Visitor"),
button_text: _t("Chat with one of our collaborators"),
default_message: _t("How may I help you?"),
});
this._history = null;
// livechat model
this._livechat = null;
// livechat window
this._chatWindow = null;
this._messages = [];
this._serverURL = this.messaging.publicLivechatServerUrl;
},
@@ -62,15 +57,16 @@ const LivechatButton = Widget.extend({
return this._loadQWebTemplate();
},
start() {
this.$el.text(this.options.button_text);
this.$el.text(this.messaging.livechatButtonView.buttonText);
if (this._history) {
this._history.forEach(m => this._addMessage(m));
this._openChat();
} else if (!config.device.isMobile && this._rule.action === 'auto_popup') {
const autoPopupCookie = utils.get_cookie('im_livechat_auto_popup');
if (!autoPopupCookie || JSON.parse(autoPopupCookie)) {
this._autoPopupTimeout =
setTimeout(this._openChat.bind(this), this._rule.auto_popup_timer * 1000);
this.messaging.livechatButtonView.update({
autoOpenChatTimeout: setTimeout(this._openChat.bind(this), this._rule.auto_popup_timer * 1000),
});
}
}
this.call('bus_service', 'onNotification', this, this._onNotification);
@@ -127,10 +123,10 @@ const LivechatButton = Widget.extend({
* @private
*/
_askFeedback() {
this._chatWindow.$('.o_thread_composer input').prop('disabled', true);
this.messaging.livechatButtonView.chatWindow.$('.o_thread_composer input').prop('disabled', true);
const feedback = new Feedback(this, this._livechat);
this._chatWindow.replaceContentWith(feedback);
this.messaging.livechatButtonView.chatWindow.replaceContentWith(feedback);
feedback.on('send_message', this, this._sendMessage);
feedback.on('feedback_sent', this, this._closeChat);
@@ -139,7 +135,7 @@ const LivechatButton = Widget.extend({
* @private
*/
_closeChat() {
this._chatWindow.destroy();
this.messaging.livechatButtonView.chatWindow.destroy();
utils.set_cookie('im_livechat_session', "", -1); // remove cookie
},
/**
@@ -190,7 +186,7 @@ const LivechatButton = Widget.extend({
}
notificationData.body = utils.Markup(notificationData.body);
this._addMessage(notificationData);
if (this._chatWindow.isFolded() || !this._chatWindow.isAtBottom()) {
if (this.messaging.livechatButtonView.chatWindow.isFolded() || !this.messaging.livechatButtonView.chatWindow.isAtBottom()) {
this._livechat.incrementUnreadCounter();
}
this._renderMessages();
@@ -221,13 +217,14 @@ const LivechatButton = Widget.extend({
* @private
*/
_openChat: _.debounce(function () {
if (this._openingChat) {
if (this.messaging.livechatButtonView.isOpeningChat) {
return;
}
const cookie = utils.get_cookie('im_livechat_session');
let def;
this._openingChat = true;
clearTimeout(this._autoPopupTimeout);
this.messaging.livechatButtonView.update({ isOpeningChat: true });
clearTimeout(this.messaging.livechatButtonView.autoOpenChatTimeout);
if (cookie) {
def = Promise.resolve(JSON.parse(cookie));
} else {
@@ -280,9 +277,9 @@ const LivechatButton = Widget.extend({
});
}
}).then(() => {
this._openingChat = false;
this.messaging.livechatButtonView.update({ isOpeningChat: false });
}).guardedCatch(() => {
this._openingChat = false;
this.messaging.livechatButtonView.update({ isOpeningChat: false });
});
}, 200, true),
/**
@@ -311,14 +308,16 @@ const LivechatButton = Widget.extend({
const options = {
displayStars: false,
headerBackgroundColor: this.options.header_background_color,
placeholder: this.options.input_placeholder || "",
placeholder: this.messaging.livechatButtonView.inputPlaceholder,
titleColor: this.options.title_color,
};
this._chatWindow = new WebsiteLivechatWindow(this, this._livechat, options);
return this._chatWindow.appendTo($('body')).then(() => {
this.messaging.livechatButtonView.update({
chatWindow: new WebsiteLivechatWindow(this, this._livechat, options),
});
return this.messaging.livechatButtonView.chatWindow.appendTo($('body')).then(() => {
const cssProps = { bottom: 0 };
cssProps[_t.database.parameters.direction === 'rtl' ? 'left' : 'right'] = 0;
this._chatWindow.$el.css(cssProps);
this.messaging.livechatButtonView.chatWindow.$el.css(cssProps);
this.$el.hide();
});
},
@@ -336,11 +335,11 @@ const LivechatButton = Widget.extend({
* @private
*/
_renderMessages() {
const shouldScroll = !this._chatWindow.isFolded() && this._chatWindow.isAtBottom();
const shouldScroll = !this.messaging.livechatButtonView.chatWindow.isFolded() && this.messaging.livechatButtonView.chatWindow.isAtBottom();
this._livechat.setMessages(this._messages);
this._chatWindow.render();
this.messaging.livechatButtonView.chatWindow.render();
if (shouldScroll) {
this._chatWindow.scrollToBottom();
this.messaging.livechatButtonView.chatWindow.scrollToBottom();
}
},
/**
@@ -372,19 +371,19 @@ const LivechatButton = Widget.extend({
}
this._closeChat();
}
this._chatWindow.scrollToBottom();
this.messaging.livechatButtonView.chatWindow.scrollToBottom();
});
},
/**
* @private
*/
_sendWelcomeMessage() {
if (this.options.default_message) {
if (this.messaging.livechatButtonView.defaultMessage) {
this._addMessage({
id: '_welcome',
attachment_ids: [],
author_id: this._livechat.getOperatorPID(),
body: this.options.default_message,
body: this.messaging.livechatButtonView.defaultMessage,
date: time.datetime_to_str(new Date()),
model: "mail.channel",
res_id: this._livechat.getID(),
@@ -402,12 +401,12 @@ const LivechatButton = Widget.extend({
*/
_onCloseChatWindow(ev) {
ev.stopPropagation();
const isComposerDisabled = this._chatWindow.$('.o_thread_composer input').prop('disabled');
const isComposerDisabled = this.messaging.livechatButtonView.chatWindow.$('.o_thread_composer input').prop('disabled');
const shouldAskFeedback = !isComposerDisabled && this._messages.find(function (message) {
return message.getID() !== '_welcome';
});
if (shouldAskFeedback) {
this._chatWindow.toggleFold(false);
this.messaging.livechatButtonView.chatWindow.toggleFold(false);
this._askFeedback();
} else {
this._closeChat();
@@ -450,7 +449,7 @@ const LivechatButton = Widget.extend({
*/
_onUpdatedTypingPartners(ev) {
ev.stopPropagation();
this._chatWindow.renderHeader();
this.messaging.livechatButtonView.chatWindow.renderHeader();
},
/**
* @private
@@ -458,7 +457,7 @@ const LivechatButton = Widget.extend({
*/
_onUpdatedUnreadCounter(ev) {
ev.stopPropagation();
this._chatWindow.renderHeader();
this.messaging.livechatButtonView.chatWindow.renderHeader();
},
/**
* @private
@@ -1,8 +1,69 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'LivechatButtonView',
identifyingFields: ['messaging'],
recordMethods: {
/**
* @returns {string}
*/
_computeButtonText() {
if (this.messaging.publicLivechatOptions.button_text) {
return this.messaging.publicLivechatOptions.button_text;
}
return this.env._t("Chat with one of our collaborators");
},
/**
* @private
* @returns {string}
*/
_computeDefaultMessage() {
if (this.messaging.publicLivechatOptions.default_message) {
return this.messaging.publicLivechatOptions.default_message;
}
return this.env._t("How may I help you?");
},
/**
* @private
* @returns {string|FieldCommand}
*/
_computeInputPlaceholder() {
if (this.isChatbot) {
// void the default livechat placeholder in the user input
// as we use it for specific things (e.g: showing "please select an option above")
return clear();
}
if (this.messaging.publicLivechatOptions.input_placeholder) {
return this.messaging.publicLivechatOptions.input_placeholder;
}
return this.env._t("Ask something ...");
},
},
fields: {
autoOpenChatTimeout: attr(),
buttonText: attr({
compute: '_computeButtonText',
}),
// livechat window
chatWindow: attr({
default: null,
}),
defaultMessage: attr({
compute: '_computeDefaultMessage',
}),
inputPlaceholder: attr({
compute: '_computeInputPlaceholder',
default: '',
}),
isChatbot: attr({
default: false,
}),
isOpeningChat: attr({
default: false,
}),
},
});
@@ -31,8 +31,8 @@ LivechatButton.include({
$(window).trigger('resize');
await this._super(...arguments);
this.el.innerHTML = "";
if (this.options.button_text && !config.device.touch) {
this.el.dataset.content = this.options.button_text;
if (this.messaging.livechatButtonView.buttonText && !config.device.touch) {
this.el.dataset.content = this.messaging.livechatButtonView.buttonText;
this.el.dataset.toggle = "popover";
this.el.dataset.trigger = "hover";
this.$el.popover({
@@ -28,10 +28,8 @@ const LivechatButtonTestChatbot = LivechatButton.extend({
this._chatbotCurrentStep = this._chatbot.chatbot_welcome_steps[
this._chatbot.chatbot_welcome_steps.length - 1];
this._channelData = chatbotData.channel;
this._isChatbot = true;
this.messaging.livechatButtonView.update({ isChatbot: true });
this._serverURL = chatbotData.serverUrl;
this.options.input_placeholder = '';
},
/**
@@ -66,7 +64,7 @@ const LivechatButtonTestChatbot = LivechatButton.extend({
this.call('bus_service', 'addChannel', this._livechat.getUUID());
this.call('bus_service', 'startPolling');
utils.set_cookie('im_livechat_session', utils.unaccent(JSON.stringify(this._livechat.toData()), true), 60 * 60);
this._openingChat = false;
this.messaging.livechatButtonView.update({ isOpeningChat: false });
});
},
});