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

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

closes odoo/odoo#97219

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2022-08-01 18:21:00 +02:00
parent 47734939b4
commit 6c7f178bdc
10 changed files with 125 additions and 119 deletions
@@ -86,8 +86,8 @@ const QWeb = core.qweb;
this.messaging.publicLivechatGlobal.livechatButtonView.update({ rule: this.messaging.publicLivechatGlobal.livechatButtonView.livechatInit.rule });
} else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbotState === 'restore_session') {
// we landed on a website page and a chatbot script is currently running
// -> restore the user's session (see '_chatbotRestoreSession')
this._chatbotRestoreSession();
// -> restore the user's session (see 'chatbotRestoreSession')
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotRestoreSession();
}
return superResult;
@@ -106,7 +106,7 @@ const QWeb = core.qweb;
*/
_chatbotAddMessage(message, options) {
message.body = utils.Markup(message.body);
this._addMessage(message, options);
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(message, options);
if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom()) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() });
}
@@ -135,8 +135,12 @@ const QWeb = core.qweb;
this._chatbotEndScript();
} else {
this._chatbotSetIsTyping();
this.nextStepTimeout = setTimeout(
this._chatbotTriggerNextStep.bind(this), this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay);
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
)
});
}
}
},
@@ -305,8 +309,12 @@ const QWeb = core.qweb;
if (this._isLastMessageFromCustomer()) {
// user has already typed a message in -> trigger next step
this._chatbotSetIsTyping();
this.nextStepTimeout = setTimeout(
this._chatbotTriggerNextStep.bind(this), this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay);
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
),
});
} else {
this._chatbotEnableInput();
}
@@ -332,8 +340,12 @@ const QWeb = core.qweb;
this._chatbotSetIsTyping();
}
this.nextStepTimeout = setTimeout(
this._chatbotTriggerNextStep.bind(this), nextStepDelay);
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
nextStepDelay,
),
});
}
}
@@ -341,35 +353,6 @@ const QWeb = core.qweb;
this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
}
},
/**
* See '_chatbotSaveSession'.
*
* We retrieve the livechat uuid from the session cookie since the livechat Widget is not yet
* initialized when we restore the chatbot state.
*
* We also clear any older keys that store a previously saved chatbot session.
* (In that case we clear the actual browser's local storage, we don't use the localStorage
* object as it does not allow browsing existing keys, see 'local_storage.js'.)
*
* @private
*/
_chatbotRestoreSession() {
const browserLocalStorage = window.localStorage;
if (browserLocalStorage && browserLocalStorage.length) {
for (let i = 0; i < browserLocalStorage.length; i++) {
const key = browserLocalStorage.key(i);
if (key.startsWith('im_livechat.chatbot.state.uuid_') && key !== this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSessionCookieKey) {
browserLocalStorage.removeItem(key);
}
}
}
let chatbotState = localStorage.getItem(this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSessionCookieKey);
if (chatbotState) {
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: insertAndReplace({ data: this.messaging.publicLivechatGlobal.livechatButtonView.localStorageChatbotState._chatbotCurrentStep }) });
}
},
/**
* Register current chatbot step state into localStorage to be able to resume if the visitor
* goes to another website page or if he refreshes his page.
@@ -700,8 +683,12 @@ const QWeb = core.qweb;
this._debouncedChatbotAwaitUserInput();
} else if (!this._chatbotShouldEndScript()) {
this._chatbotSetIsTyping();
this.nextStepTimeout = setTimeout(
this._chatbotTriggerNextStep.bind(this), this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay);
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
),
});
} else {
this._chatbotEndScript();
}
@@ -750,7 +737,7 @@ const QWeb = core.qweb;
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: insertAndReplace({ data: chatbotStep }) });
if (chatbotStep.chatbot_step_message) {
this._addMessage({
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage({
id: '_welcome_' + stepIndex,
is_discussion: true, // important for css style -> we only want white background for chatbot
attachment_ids: [],
@@ -777,10 +764,12 @@ const QWeb = core.qweb;
this._chatbotSetIsTyping(true);
}
this.welcomeMessageTimeout = setTimeout(() => {
this._sendWelcomeChatbotMessage(stepIndex + 1, welcomeMessageDelay);
this._renderMessages();
}, welcomeMessageDelay);
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotWelcomeMessageTimeout: setTimeout(() => {
this._sendWelcomeChatbotMessage(stepIndex + 1, welcomeMessageDelay);
this._renderMessages();
}, welcomeMessageDelay),
});
} else {
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'forward_operator') {
// special case when the last welcome message is a forward to an operator
@@ -810,12 +799,12 @@ const QWeb = core.qweb;
this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_composer_text_field').removeClass('d-none');
this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide();
if (this.nextStepTimeout) {
clearTimeout(this.nextStepTimeout);
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbotNextStepTimeout) {
clearTimeout(this.messaging.publicLivechatGlobal.livechatButtonView.chatbotNextStepTimeout);
}
if (this.welcomeMessageTimeout) {
clearTimeout(this.welcomeMessageTimeout);
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbotWelcomeMessageTimeout) {
clearTimeout(this.messaging.publicLivechatGlobal.livechatButtonView.chatbotWelcomeMessageTimeout);
}
const postedMessage = await session.rpc('/chatbot/restart', {
@@ -829,8 +818,12 @@ const QWeb = core.qweb;
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: clear() });
this._chatbotSetIsTyping();
this.nextStepTimeout = setTimeout(
this._chatbotTriggerNextStep.bind(this), this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay);
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
),
});
},
_onChatbotInputKeyDown() {
@@ -7,12 +7,9 @@ import time from 'web.time';
import utils from 'web.utils';
import Widget from 'web.Widget';
import PublicLivechatMessage from '@im_livechat/legacy/models/public_livechat_message';
import { clear, insertAndReplace, link, replace } from '@mail/model/model_field_command';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
const _t = core._t;
const QWeb = core.qweb;
const LivechatButton = Widget.extend({
className: 'openerp o_livechat_button d-print-none',
@@ -48,13 +45,13 @@ const LivechatButton = Widget.extend({
}
this.messaging.publicLivechatGlobal.livechatButtonView.update({ rule: result.rule });
}
return this._loadQWebTemplate();
return this.messaging.publicLivechatGlobal.loadQWebTemplate();
},
start() {
this.$el.text(this.messaging.publicLivechatGlobal.livechatButtonView.buttonText);
if (this.messaging.publicLivechatGlobal.livechatButtonView.history) {
for (const m of this.messaging.publicLivechatGlobal.livechatButtonView.history) {
this._addMessage(m);
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(m);
}
this._openChat();
} else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.livechatButtonView.rule.action === 'auto_popup') {
@@ -85,45 +82,6 @@ const LivechatButton = Widget.extend({
// Private
//--------------------------------------------------------------------------
/**
* @private
* @param {Object} data
* @param {Object} [options={}]
*/
_addMessage(data, options) {
const legacyMessage = new PublicLivechatMessage(this, this.messaging, data);
const hasAlreadyMessage = _.some(this.messaging.publicLivechatGlobal.livechatButtonView.messages, function (msg) {
return legacyMessage.getID() === msg.id;
});
if (hasAlreadyMessage) {
return;
}
const message = this.messaging.models['PublicLivechatMessage'].insert({
id: data.id,
legacyPublicLivechatMessage: legacyMessage,
});
if (this.messaging.publicLivechatGlobal.publicLivechat) {
this.messaging.publicLivechatGlobal.publicLivechat.update({
messages: link(message),
});
}
if (this.messaging.publicLivechatGlobal.publicLivechat && this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat) {
this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.addMessage(legacyMessage);
}
if (options && options.prepend) {
this.messaging.publicLivechatGlobal.livechatButtonView.update({
messages: replace([message, ...this.messaging.publicLivechatGlobal.livechatButtonView.messages]),
});
} else {
this.messaging.publicLivechatGlobal.livechatButtonView.update({
messages: replace([...this.messaging.publicLivechatGlobal.livechatButtonView.messages, message]),
});
}
},
/**
* @private
*/
@@ -138,16 +96,6 @@ const LivechatButton = Widget.extend({
this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatWindow: clear() });
utils.set_cookie('im_livechat_session', "", -1); // remove cookie
},
/**
* @private
*/
_loadQWebTemplate() {
return session.rpc('/im_livechat/load_templates').then(function (templates) {
for (let template of templates) {
QWeb.add_template(template);
}
});
},
/**
* @private
*/
@@ -302,7 +250,7 @@ const LivechatButton = Widget.extend({
*/
_sendWelcomeMessage() {
if (this.messaging.publicLivechatGlobal.livechatButtonView.defaultMessage) {
this._addMessage({
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage({
id: '_welcome',
attachment_ids: [],
author_id: [
@@ -32,13 +32,10 @@ const PublicLivechatWindow = Widget.extend({
* @param {Widget} parent
* @param {Messaging} messaging
* @param {@im_livechat/legacy/models/public_livechat} thread
* @param {Object} options
* @param {string} options.titleColor
*/
init(parent, messaging, thread, options) {
init(parent, messaging, thread) {
this._super(parent);
this.messaging = messaging;
this.options = options;
this._debouncedOnScroll = _.debounce(this._onScroll.bind(this), 100);
},
@@ -75,8 +72,8 @@ const PublicLivechatWindow = Widget.extend({
if (this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor) {
this.$('.o_thread_window_header').css('background-color', this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor);
}
if (this.options.titleColor) {
this.$('.o_thread_window_header').css('color', this.options.titleColor);
if (this.messaging.publicLivechatGlobal.livechatButtonView.titleColor) {
this.$('.o_thread_window_header').css('color', this.messaging.publicLivechatGlobal.livechatButtonView.titleColor);
}
},
@@ -1,13 +1,73 @@
/** @odoo-module **/
import PublicLivechatMessage from '@im_livechat/legacy/models/public_livechat_message';
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'LivechatButtonView',
identifyingFields: ['publicLivechatGlobalOwner'],
recordMethods: {
/**
* @param {Object} data
* @param {Object} [options={}]
*/
addMessage(data, options) {
const legacyMessage = new PublicLivechatMessage(this, this.messaging, data);
const hasAlreadyMessage = _.some(this.messages, function (msg) {
return legacyMessage.getID() === msg.id;
});
if (hasAlreadyMessage) {
return;
}
const message = this.messaging.models['PublicLivechatMessage'].insert({
data,
id: data.id,
legacyPublicLivechatMessage: legacyMessage,
});
if (this.messaging.publicLivechatGlobal.publicLivechat && this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat) {
this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.addMessage(legacyMessage);
}
if (options && options.prepend) {
this.update({
messages: replace([message, ...this.messages]),
});
} else {
this.update({
messages: replace([...this.messages, message]),
});
}
},
/**
* See '_chatbotSaveSession'.
*
* We retrieve the livechat uuid from the session cookie since the livechat Widget is not yet
* initialized when we restore the chatbot state.
*
* We also clear any older keys that store a previously saved chatbot session.
* (In that case we clear the actual browser's local storage, we don't use the localStorage
* object as it does not allow browsing existing keys, see 'local_storage.js'.)
*/
chatbotRestoreSession() {
const browserLocalStorage = window.localStorage;
if (browserLocalStorage && browserLocalStorage.length) {
for (let i = 0; i < browserLocalStorage.length; i++) {
const key = browserLocalStorage.key(i);
if (key.startsWith('im_livechat.chatbot.state.uuid_') && key !== this.chatbotSessionCookieKey) {
browserLocalStorage.removeItem(key);
}
}
}
const chatbotState = localStorage.getItem(this.chatbotSessionCookieKey);
if (chatbotState) {
this.chatbot.update({ currentStep: insertAndReplace({ data: this.localStorageChatbotState._chatbotCurrentStep }) });
}
},
/**
* @private
* @returns {string}
@@ -213,12 +273,14 @@ registerModel({
inverse: 'livechatButtonViewOwner',
isCausal: true,
}),
chatbotNextStepTimeout: attr(),
chatbotSessionCookieKey: attr({
compute: '_computeChatbotSessionCookieKey',
}),
chatbotState: attr({
compute: '_computeChatbotState',
}),
chatbotWelcomeMessageTimeout: attr(),
chatWindow: one('PublicLivechatWindow', {
inverse: 'livechatButtonViewOwner',
isCausal: true,
@@ -3,7 +3,7 @@
import PublicLivechat from '@im_livechat/legacy/models/public_livechat';
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { attr, one } from '@mail/model/model_field';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
@@ -97,7 +97,6 @@ registerModel({
readonly: true,
required: true,
}),
messages: many('PublicLivechatMessage'),
name: attr({
compute: '_computeName',
}),
@@ -4,6 +4,7 @@ import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
import { qweb } from 'web.core';
import utils from 'web.utils';
registerModel({
@@ -28,6 +29,12 @@ registerModel({
},
},
recordMethods: {
async loadQWebTemplate() {
const templates = await this.messaging.rpc({ route: '/im_livechat/load_templates' });
for (const template of templates) {
qweb.add_template(template);
}
},
/**
* @private
* @returns {FieldCommand}
@@ -65,7 +65,7 @@ registerModel({
return;
}
notificationData.body = utils.Markup(notificationData.body);
this.messaging.publicLivechatGlobal.livechatButtonView.widget._addMessage(notificationData);
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(notificationData);
if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom()) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() });
}
@@ -7,6 +7,7 @@ registerModel({
name: 'PublicLivechatMessage',
identifyingFields: ['id'],
fields: {
data: attr(),
id: attr({
readonly: true,
required: true,
@@ -15,7 +15,6 @@ registerModel({
this.livechatButtonViewOwner.widget,
this.messaging,
this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat,
{ titleColor: this.livechatButtonViewOwner.titleColor },
),
});
},
@@ -44,7 +44,7 @@ const LivechatButtonTestChatbot = LivechatButton.extend({
* @override
*/
willStart: function () {
return this._loadQWebTemplate();
return this.messaging.publicLivechatGlobal.loadQWebTemplate();
},
/**