From aecdc502784c4d07eb7d80a38e7099804645192b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexandre=20K=C3=BChn?=
Date: Fri, 26 Aug 2022 14:11:17 +0000
Subject: [PATCH] [IMP] im_livechat, *: Refactor JS Public Livechat (step 16)
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-2965839
closes odoo/odoo#99137
Signed-off-by: Alexandre Kühn (aku)
---
.../src/legacy/models/public_livechat.js | 85 ++++-------
.../legacy/models/public_livechat_message.js | 140 +-----------------
.../static/src/legacy/public_livechat.scss | 14 --
.../src/legacy/public_livechat_chatbot.js | 71 +--------
.../src/legacy/widgets/livechat_button.js | 74 ---------
.../public_livechat_view.js | 100 +------------
.../public_livechat_view.xml | 69 +--------
.../public_livechat_window.js | 24 ++-
.../public_livechat_window.xml | 2 +-
.../im_livechat/static/src/models/chatbot.js | 64 +++++++-
.../static/src/models/livechat_button_view.js | 45 +-----
...ic_livechat_global_notification_handler.js | 4 +-
.../src/models/public_livechat_message.js | 10 ++
.../src/models/public_livechat_window.js | 24 +++
.../static/src/models/livechat_button_view.js | 4 +-
15 files changed, 165 insertions(+), 565 deletions(-)
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 ee4cbc84b22..fc2349c413c 100644
--- a/addons/im_livechat/static/src/legacy/models/public_livechat.js
+++ b/addons/im_livechat/static/src/legacy/models/public_livechat.js
@@ -41,14 +41,6 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
/**
* Initialize the internal data for typing feature on threads.
- *
- * Also listens on some internal events of the thread:
- *
- * - 'message_added': when a message is added, remove the author in the
- * typing partners.
- * - 'message_posted': when a message is posted, let the user have the
- * possibility to immediately notify if he types something right away,
- * instead of waiting for a throttle behaviour.
*/
// Store the last "myself typing" status that has been sent to the
@@ -104,9 +96,6 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
// something, in order to display the oldest typing partners.
this._typingPartnerIDs = [];
- this.on('message_added', this, this._onTypingMessageAdded);
- this.on('message_posted', this, this._onTypingMessagePosted);
-
if (params.data.message_unread_counter !== undefined) {
this.messaging.publicLivechatGlobal.publicLivechat.update({
unreadCounter: params.data.message_unread_counter
@@ -125,12 +114,23 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
//--------------------------------------------------------------------------
/**
- * Add a message to this thread.
+ * Called when a new message is added to the thread
+ * On receiving a message from a typing partner, unregister this partner
+ * from typing partners (otherwise, it will still display it until timeout).
+ *
+ * Note that it only unregister typing operators.
+ *
+ * Note that in the frontend, there is no way to identify a message that is
+ * from the current user, because there is no partner ID in the session and
+ * a message with an author sets the partner ID of the author.
*
* @param {@im_livechat/legacy/models/public_livechat_message} message
*/
addMessage(message) {
- this.trigger('message_added', message);
+ const operatorID = this.messaging.publicLivechatGlobal.publicLivechat.operator.id;
+ if (message.hasAuthor() && message.getAuthorID() === operatorID) {
+ this.unregisterTyping({ partnerID: operatorID });
+ }
},
/**
* @override
@@ -215,19 +215,27 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
markAsRead() {
if (this.messaging.publicLivechatGlobal.publicLivechat.unreadCounter > 0) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: 0 });
- this.trigger_up('updated_unread_counter');
+ this.messaging.publicLivechatGlobal.chatWindow.widget.renderHeader();
return Promise.resolve();
}
return Promise.resolve();
},
/**
- * Post a message on this thread
+ * Called when current user has posted a message on this thread.
*
- * @returns {Promise} resolved with the message object to be sent to the
- * server
+ * The current user receives the possibility to immediately notify the
+ * other users if he is typing something else.
+ *
+ * Refresh the context for the current user to notify that he starts or
+ * stops typing something. In other words, when this function is called and
+ * then the current user types something, it immediately notifies the
+ * server as if it is the first time he is typing something.
*/
async postMessage() {
- this.trigger('message_posted');
+ this._lastNotifiedMyselfTyping = false;
+ this._throttleNotifyMyselfTyping.clear();
+ this._myselfLongTypingTimer.clear();
+ this._myselfTypingInactivityTimer.clear();
},
/**
* Register someone that is currently typing something in this thread.
@@ -342,7 +350,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
* @private
*/
_warnUpdatedTypingPartners() {
- this.trigger_up('updated_typing_partners');
+ this.messaging.publicLivechatGlobal.chatWindow.widget.renderHeader();
},
//--------------------------------------------------------------------------
@@ -404,45 +412,6 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
_onOthersTypingTimeout(partnerID) {
this.unregisterTyping({ partnerID });
},
- /**
- * Called when a new message is added to the thread
- * On receiving a message from a typing partner, unregister this partner
- * from typing partners (otherwise, it will still display it until timeout).
- *
- * Note that it only unregister typing operators.
- *
- * Note that in the frontend, there is no way to identify a message that is
- * from the current user, because there is no partner ID in the session and
- * a message with an author sets the partner ID of the author.
- *
- * @private
- * @param {mail.model.AbstractMessage} message
- */
- _onTypingMessageAdded(message) {
- const operatorID = this.messaging.publicLivechatGlobal.publicLivechat.operator.id;
- if (message.hasAuthor() && message.getAuthorID() === operatorID) {
- this.unregisterTyping({ partnerID: operatorID });
- }
- },
- /**
- * Called when current user has posted a message on this thread.
- *
- * The current user receives the possibility to immediately notify the
- * other users if he is typing something else.
- *
- * Refresh the context for the current user to notify that he starts or
- * stops typing something. In other words, when this function is called and
- * then the current user types something, it immediately notifies the
- * server as if it is the first time he is typing something.
- *
- * @private
- */
- _onTypingMessagePosted() {
- this._lastNotifiedMyselfTyping = false;
- this._throttleNotifyMyselfTyping.clear();
- this._myselfLongTypingTimer.clear();
- this._myselfTypingInactivityTimer.clear();
- },
});
export default PublicLivechat;
diff --git a/addons/im_livechat/static/src/legacy/models/public_livechat_message.js b/addons/im_livechat/static/src/legacy/models/public_livechat_message.js
index 05f0199815d..ecc73fe8156 100644
--- a/addons/im_livechat/static/src/legacy/models/public_livechat_message.js
+++ b/addons/im_livechat/static/src/legacy/models/public_livechat_message.js
@@ -43,7 +43,7 @@ const PublicLivechatMessage = Class.extend({
this._defaultUsername = this.messaging.publicLivechatGlobal.options.default_username;
this._serverURL = this.messaging.publicLivechatGlobal.serverUrl;
- if (parent.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isActive) {
this._chatbotStepId = data.chatbot_script_step_id;
this._chatbotStepAnswers = data.chatbot_step_answers;
this._chatbotStepAnswerId = data.chatbot_selected_answer_id;
@@ -66,14 +66,6 @@ const PublicLivechatMessage = Class.extend({
}
return this._serverAuthorID[0];
},
- /**
- * Threads do not have an im status by default
- *
- * @return {undefined}
- */
- getAuthorImStatus() {
- return undefined;
- },
/**
* Get the relative url of the avatar to display next to the message
*
@@ -154,34 +146,6 @@ const PublicLivechatMessage = Class.extend({
getID() {
return this._id;
},
- /**
- * Gets the class to use as the notification icon.
- *
- * @returns {string}
- */
- getNotificationIcon() {
- if (!this.hasNotificationsError()) {
- return 'fa fa-envelope-o';
- }
- return 'fa fa-envelope';
- },
- /**
- * Gets the list of notifications of this message, in no specific order.
- * By default messages do not have notifications.
- *
- * @returns {Object[]}
- */
- getNotifications() {
- return [];
- },
- /**
- * Gets the text to display next to the notification icon.
- *
- * @returns {string}
- */
- getNotificationText() {
- return '';
- },
/**
* Get the time elapsed between sent message and now
*
@@ -207,68 +171,13 @@ const PublicLivechatMessage = Class.extend({
hasAuthor() {
return !!(this._serverAuthorID && this._serverAuthorID[0]);
},
- /**
- * State whether this message has an email of its sender.
- * By default, messages do not have any email of its sender.
- *
- * @return {string}
- */
- hasEmailFrom() {
- return false;
- },
- /**
- * States whether this message has some notifications.
- *
- * @returns {boolean}
- */
- hasNotifications() {
- return this.getNotifications().length > 0;
- },
- /**
- * States whether this message has notifications that are in error.
- *
- * @returns {boolean}
- */
- hasNotificationsError() {
- return this.getNotifications().some(notif =>
- notif.notification_status === 'exception' ||
- notif.notification_status === 'bounce'
- );
- },
- /**
- * State whether this message has a subject
- * By default, messages do not have any subject.
- *
- * @return {boolean}
- */
- hasSubject() {
- return false;
- },
/**
* State whether this message is empty
*
* @return {boolean}
*/
isEmpty() {
- return !this.hasTrackingValues() &&
- !this.getBody();
- },
- /**
- * By default, messages do not have any subtype description
- *
- * @return {boolean}
- */
- hasSubtypeDescription() {
- return false;
- },
- /**
- * State whether this message contains some tracking values
- * By default, messages do not have any tracking values.
- *
- * @return {boolean}
- */
- hasTrackingValues() {
- return false;
+ return !this.getBody();
},
/**
* State whether this message is a discussion
@@ -278,24 +187,6 @@ const PublicLivechatMessage = Class.extend({
isDiscussion() {
return this._isDiscussion;
},
- /**
- * State whether this message is linked to a document thread
- * By default, messages are not linked to a document thread.
- *
- * @return {boolean}
- */
- isLinkedToDocumentThread() {
- return false;
- },
- /**
- * State whether this message is needaction
- * By default, messages are not needaction.
- *
- * @return {boolean}
- */
- isNeedaction() {
- return false;
- },
/**
* State whether this message is a note (i.e. a message from "Log note")
*
@@ -320,33 +211,6 @@ const PublicLivechatMessage = Class.extend({
isNotification() {
return this._isNotification;
},
- /**
- * State whether this message is starred
- * By default, messages are not starred.
- *
- * @return {boolean}
- */
- isStarred() {
- return false;
- },
- /**
- * State whether this message is a system notification
- * By default, messages are not system notifications
- *
- * @return {boolean}
- */
- isSystemNotification() {
- return false;
- },
- /**
- * States whether the current message needs moderation in general.
- * By default, messages do not require any moderation.
- *
- * @returns {boolean}
- */
- needsModeration() {
- return false;
- },
setChatbotStepAnswerId(chatbotStepAnswerId) {
this._chatbotStepAnswerId = chatbotStepAnswerId;
},
diff --git a/addons/im_livechat/static/src/legacy/public_livechat.scss b/addons/im_livechat/static/src/legacy/public_livechat.scss
index ccc552ff7e9..70d0693458a 100644
--- a/addons/im_livechat/static/src/legacy/public_livechat.scss
+++ b/addons/im_livechat/static/src/legacy/public_livechat.scss
@@ -223,11 +223,6 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
height: 15px;
}
- .o_thread_tooltip_container {
- display: inline;
- position: relative;
- }
-
.o_thread_date_separator {
margin-top: 15px;
margin-bottom: 30px;
@@ -419,11 +414,6 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
margin-bottom: $grid-gutter-width;
}
}
-
- .o_thread_tooltip_container {
- display: inline;
- position: relative;
- }
}
}
@@ -432,10 +422,6 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
}
}
-.o_web_client .popover .o_thread_tooltip_icon {
- min-width: 1rem;
-}
-
.o_web_client.o_touch_device {
.o_mail_thread .o_thread_icon {
opacity: $o-mail-thread-icon-opacity;
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 36aeadb91c0..12f024c9161 100644
--- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js
+++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js
@@ -1,14 +1,11 @@
/** @odoo-module **/
-import core from 'web.core';
import session from 'web.session';
import time from 'web.time';
import utils from 'web.utils';
import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
-const _t = core._t;
-
/**
* Override of the LivechatButton to include chatbot capabilities.
* Main changes / hooking points are:
@@ -19,42 +16,6 @@ const _t = core._t;
*/
LivechatButton.include({
- //--------------------------------------------------------------------------
- // Private - Chatbot specifics
- //--------------------------------------------------------------------------
-
- /**
- * 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.
- *
- * The validation is made server-side to have the same test when we validate here and when we
- * register the answer, but also to easily post a message as the bot ("Sorry, try again...").
- *
- * Returns a boolean stating whether the email was valid or not.
- *
- * @private
- */
- async _chatbotValidateEmail() {
- let emailValidResult = await session.rpc('/chatbot/step/validate_email', {
- channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
- });
-
- if (emailValidResult.success) {
- this.messaging.publicLivechatGlobal.chatbot.currentStep.data.is_email_valid = true;
- this.messaging.publicLivechatGlobal.chatbot.saveSession();
-
- return true;
- } else {
- // email is not valid, let the user try again
- this.messaging.publicLivechatGlobal.chatWindow.enableInput();
- if (emailValidResult.posted_message) {
- this.messaging.publicLivechatGlobal.chatbot.addMessage(emailValidResult.posted_message);
- }
-
- return false;
- }
- },
-
//--------------------------------------------------------------------------
// Private - LiveChat Overrides
//--------------------------------------------------------------------------
@@ -66,42 +27,18 @@ const _t = core._t;
_prepareGetSessionParameters() {
const parameters = this._super(...arguments);
- if (this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isActive) {
parameters.chatbot_script_id = this.messaging.publicLivechatGlobal.chatbot.scriptId;
}
return parameters;
},
- /**
- * @private
- */
- _renderMessages() {
- this._super(...arguments);
-
- const self = this;
-
- this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_thread_message:last .o_livechat_chatbot_options li').each(function () {
- $(this).on('click', self._onChatbotOptionClicked.bind(self));
- });
-
- this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_main_restart').on('click',
- this.messaging.publicLivechatGlobal.livechatButtonView.onChatbotRestartScript
- );
-
- if (this.messaging.publicLivechatGlobal.messages.length !== 0) {
- const lastMessage = this.messaging.publicLivechatGlobal.lastMessage;
- const stepAnswers = lastMessage.widget.getChatbotStepAnswers();
- if (stepAnswers && stepAnswers.length !== 0 && !lastMessage.widget.getChatbotStepAnswerId()) {
- this.messaging.publicLivechatGlobal.chatWindow.disableInput(_t('Select an option above'));
- }
- }
- },
/**
* Small override to handle chatbot welcome message(s).
* @private
*/
_sendWelcomeMessage() {
- if (this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isActive) {
this._sendWelcomeChatbotMessage(
0,
this.messaging.publicLivechatGlobal.chatbot.state === 'welcome' ? 0 : this.messaging.publicLivechatGlobal.chatbot.messageDelay,
@@ -165,7 +102,7 @@ const _t = core._t;
this.messaging.publicLivechatGlobal.chatbot.update({
welcomeMessageTimeout: setTimeout(() => {
this._sendWelcomeChatbotMessage(stepIndex + 1, welcomeMessageDelay);
- this._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
}, welcomeMessageDelay),
});
} else {
@@ -227,7 +164,7 @@ const _t = core._t;
const messageId = stepMessage.id;
stepMessage.widget.setChatbotStepAnswerId(selectedAnswer);
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_selected_answer_id = selectedAnswer;
- this._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
this.messaging.publicLivechatGlobal.chatbot.saveSession();
const saveAnswerPromise = session.rpc('/chatbot/answer/save', {
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 90a84d3437e..f95a48565ee 100644
--- a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js
+++ b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js
@@ -6,13 +6,6 @@ import Widget from 'web.Widget';
const LivechatButton = Widget.extend({
className: 'openerp o_livechat_button d-print-none',
- custom_events: {
- 'close_chat_window': '_onCloseChatWindow',
- 'post_message_chat_window': '_onPostMessageChatWindow',
- 'save_chat_window': '_onSaveChatWindow',
- 'updated_typing_partners': '_onUpdatedTypingPartners',
- 'updated_unread_counter': '_onUpdatedUnreadCounter',
- },
events: {
'click': '_onClick'
},
@@ -57,16 +50,6 @@ const LivechatButton = Widget.extend({
previous_operator_id: this._get_previous_operator_id(),
};
},
- /**
- * @private
- */
- _renderMessages() {
- const shouldScroll = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded && this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.isAtBottom();
- this.messaging.publicLivechatGlobal.chatWindow.widget.render();
- if (shouldScroll) {
- this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
- }
- },
/**
* @private
*/
@@ -96,63 +79,6 @@ const LivechatButton = Widget.extend({
_onClick() {
this.messaging.publicLivechatGlobal.livechatButtonView.openChat();
},
- /**
- * @private
- * @param {OdooEvent} ev
- */
- _onCloseChatWindow(ev) {
- ev.stopPropagation();
- const isComposerDisabled = this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_thread_composer input').prop('disabled');
- const shouldAskFeedback = !isComposerDisabled && this.messaging.publicLivechatGlobal.messages.find(function (message) {
- return message.id !== '_welcome';
- });
- if (shouldAskFeedback) {
- this.messaging.publicLivechatGlobal.chatWindow.widget.toggleFold(false);
- this.messaging.publicLivechatGlobal.livechatButtonView.askFeedback();
- } else {
- this.messaging.publicLivechatGlobal.livechatButtonView.closeChat();
- }
- this.messaging.publicLivechatGlobal.livechatButtonView.leaveSession();
- },
- /**
- * @private
- * @param {OdooEvent} ev
- * @param {Object} ev.data.messageData
- */
- async _onPostMessageChatWindow(ev) {
- ev.stopPropagation();
- const messageData = ev.data.messageData;
- try {
- await this.messaging.publicLivechatGlobal.livechatButtonView.sendMessage(messageData);
- } catch (reason) {
- reason.event.preventDefault();
- return this.messaging.publicLivechatGlobal.livechatButtonView.sendMessage(messageData); // try again just in case
- }
- },
- /**
- * @private
- * @param {OdooEvent} ev
- */
- _onSaveChatWindow(ev) {
- ev.stopPropagation();
- utils.set_cookie('im_livechat_session', utils.unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60);
- },
- /**
- * @private
- * @param {OdooEvent} ev
- */
- _onUpdatedTypingPartners(ev) {
- ev.stopPropagation();
- this.messaging.publicLivechatGlobal.chatWindow.widget.renderHeader();
- },
- /**
- * @private
- * @param {OdooEvent} ev
- */
- _onUpdatedUnreadCounter(ev) {
- ev.stopPropagation();
- this.messaging.publicLivechatGlobal.chatWindow.widget.renderHeader();
- },
});
export default LivechatButton;
diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js b/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js
index e66eb03e35e..6df9b8a06a7 100644
--- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js
+++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js
@@ -37,8 +37,6 @@ const PublicLivechatView = Widget.extend({
'click .o_thread_message': '_onClickMessage',
'click': '_onClick',
'click .o_thread_message_notification_error': '_onClickMessageNotificationError',
- 'click .o_thread_message_moderation': '_onClickMessageModeration',
- 'change .moderation_checkbox': '_onChangeModerationCheckbox',
},
/**
@@ -55,25 +53,13 @@ const PublicLivechatView = Widget.extend({
this._enabledOptions = _.defaults(options || {}, {
displayOrder: ORDER.ASC,
displayMarkAsRead: true,
- displayModerationCommands: false,
displayDocumentLinks: true,
displayAvatars: true,
squashCloseMessages: true,
loadMoreOnScroll: false,
});
- // options when the thread is disabled
- this._disabledOptions = {
- displayOrder: this._enabledOptions.displayOrder,
- displayMarkAsRead: false,
- displayModerationCommands: false,
- displayDocumentLinks: false,
- displayAvatars: this._enabledOptions.displayAvatars,
- squashCloseMessages: false,
- loadMoreOnScroll: this._enabledOptions.loadMoreOnScroll,
- };
this._selectedMessageID = null;
this._currentThreadID = null;
- this._messageMailPopover = null;
},
/**
* The message mail popover may still be shown at this moment. If we do not
@@ -83,9 +69,6 @@ const PublicLivechatView = Widget.extend({
*/
destroy() {
clearInterval(this._updateTimestampsInterval);
- if (this._messageMailPopover) {
- this._messageMailPopover.popover('hide');
- }
this._super();
},
/**
@@ -97,7 +80,6 @@ const PublicLivechatView = Widget.extend({
* @param {boolean} [options.displayLoadMore]
* @param {Array} [options.domain=[]] the domain for the messages in the
* thread.
- * @param {boolean} [options.isCreateMode]
* @param {boolean} [options.scrollToBottom=false]
* @param {boolean} [options.squashCloseMessages]
*/
@@ -111,8 +93,7 @@ const PublicLivechatView = Widget.extend({
// copy so that reverse do not alter order in the thread object
const messages = _.clone(this.messaging.publicLivechatGlobal.publicLivechat.widget.getMessages());
- const modeOptions = options.isCreateMode ? this._disabledOptions :
- this._enabledOptions;
+ const modeOptions = this._enabledOptions;
options = Object.assign({}, modeOptions, options, {
selectedMessageID: this._selectedMessageID,
@@ -136,19 +117,7 @@ const PublicLivechatView = Widget.extend({
prevMessage.getType() !== 'comment' ||
message.getType() !== 'comment' ||
// from a different author
- (prevMessage.getAuthorID() !== message.getAuthorID()) ||
- (
- // messages are linked to a document thread
- (
- prevMessage.isLinkedToDocumentThread() &&
- message.isLinkedToDocumentThread()
- ) &&
- (
- // are from different documents
- prevMessage.getDocumentModel() !== message.getDocumentModel() ||
- prevMessage.getDocumentID() !== message.getDocumentID()
- )
- )
+ prevMessage.getAuthorID() !== message.getAuthorID()
) {
displayAuthorMessages[message.getID()] = true;
} else {
@@ -162,11 +131,11 @@ const PublicLivechatView = Widget.extend({
}
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.Thread', {
- thread: this.messaging.publicLivechatGlobal.publicLivechat,
displayAuthorMessages,
options,
ORDER,
dateFormat: time.getLangDatetimeFormat(),
+ widget: this,
}));
for (let message of messages) {
@@ -185,8 +154,6 @@ const PublicLivechatView = Widget.extend({
this._updateTimestamps();
}, 1000 * 60);
}
-
- this._renderMessageNotificationPopover(messages);
},
/**
@@ -258,15 +225,6 @@ const PublicLivechatView = Widget.extend({
this.scrollToBottom();
}
},
- /**
- * Toggle all the moderation checkboxes in the thread
- *
- * @param {boolean} checked if true, check the boxes,
- * otherwise uncheck them.
- */
- toggleModerationCheckboxes(checked) {
- this.$('.moderation_checkbox').prop('checked', checked);
- },
/**
* Unselect the selected message
*/
@@ -374,38 +332,6 @@ const PublicLivechatView = Widget.extend({
this.trigger('redirect', options.model, options.id);
}
}, 500, true),
- /**
- * Render the popover when mouse-hovering on the notification icon of a
- * message in the thread.
- * There is at most one such popover at any given time.
- *
- * @private
- * @param {@im_livechat/legacy/models/public_livechat_message[]} messages list of messages in the
- * rendered thread, for which popover on mouseover interaction is
- * permitted.
- */
- _renderMessageNotificationPopover(messages) {
- if (this._messageMailPopover) {
- this._messageMailPopover.popover('hide');
- }
- if (!this.$('.o_thread_tooltip').length) {
- return;
- }
- this._messageMailPopover = this.$('.o_thread_tooltip').popover({
- html: true,
- boundary: 'viewport',
- placement: 'auto',
- trigger: 'hover',
- offset: '0, 1',
- content() {
- const messageID = $(this).data('message-id');
- const message = messages.find(message => message.getID() === messageID);
- return QWeb.render('im_livechat.legacy.mail.widget.Thread.Message.MailTooltip', {
- notifications: message.getNotifications(),
- });
- },
- });
- },
/**
* @private
*/
@@ -424,13 +350,6 @@ const PublicLivechatView = Widget.extend({
// Handlers
//--------------------------------------------------------------------------
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onChangeModerationCheckbox(ev) {
- this.trigger_up('update_moderation_buttons');
- },
/**
* @private
*/
@@ -494,19 +413,6 @@ const PublicLivechatView = Widget.extend({
const messageID = $(ev.currentTarget).data('message-id');
this.trigger('toggle_star_status', messageID);
},
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onClickMessageModeration(ev) {
- const $button = $(ev.currentTarget);
- const messageID = $button.data('message-id');
- const decision = $button.data('decision');
- this.trigger_up('message_moderation', {
- messageID,
- decision,
- });
- },
/**
* @private
* @param {MouseEvent} ev
diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.xml b/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.xml
index dd6f716de79..e57b674b382 100644
--- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.xml
+++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.xml
@@ -5,12 +5,11 @@
@param {mail.model.AbstractThread} thread
@param {Object} options
@param {boolean} [options.displayEmptyThread]
- @param {boolean} [options.displayModerationCommands]
@param {boolean} [options.displayNoMatchFound]
@param {Array} [options.domain=[]] the domain to restrict messages on the thread.
-->
-
+
@@ -43,7 +42,7 @@
-->
-
+
@@ -128,8 +127,6 @@
@param {boolean} [options.displayAvatars]
@param {boolean} [options.displayDocumentLinks]
@param {boolean} [options.displayMarkAsRead]
- @param {boolean} [options.displayModerationCommands] when set, display the moderation commands on
- the message. This includes the moderation checkboxes (needs a control panel such as in Discuss app).
@param {boolean} [options.displaySubjectsOnMessages]
@param {string|integer} [options.messagesSeparatorPosition] 'top' or
message ID, the separator is placed just after this message.
@@ -147,7 +144,6 @@
t-att-data-oe-id="message.shouldRedirectToAuthor() ? message.getAuthorID() : ''"
t-attf-class="o_thread_message_avatar rounded-circle #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}"/>
-
@@ -173,24 +169,13 @@
t-attf-class="o_thread_author o_PublicLivechatMessage_headerAuthor #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}">
-
-
-
-
-
-
- on
-
-
@@ -213,44 +192,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- :
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
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 784fd6094e2..2364f7176f0 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
@@ -4,6 +4,8 @@ import config from 'web.config';
import { _t, qweb } from 'web.core';
import Widget from 'web.Widget';
+import { set_cookie, unaccent } from 'web.utils';
+
/**
* This is the widget that represent windows of livechat in the frontend.
*
@@ -74,7 +76,17 @@ const PublicLivechatWindow = Widget.extend({
* @override
*/
close() {
- this.trigger_up('close_chat_window');
+ const isComposerDisabled = this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_thread_composer input').prop('disabled');
+ const shouldAskFeedback = !isComposerDisabled && this.messaging.publicLivechatGlobal.messages.find(function (message) {
+ return message.id !== '_welcome';
+ });
+ if (shouldAskFeedback) {
+ this.messaging.publicLivechatGlobal.chatWindow.widget.toggleFold(false);
+ this.messaging.publicLivechatGlobal.livechatButtonView.askFeedback();
+ } else {
+ this.messaging.publicLivechatGlobal.livechatButtonView.closeChat();
+ }
+ this.messaging.publicLivechatGlobal.livechatButtonView.leaveSession();
},
/**
* States whether the current environment is in mobile or not. This is
@@ -125,7 +137,7 @@ const PublicLivechatWindow = Widget.extend({
folded = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded;
}
this.messaging.publicLivechatGlobal.publicLivechat.update({ isFolded: folded });
- this.trigger_up('save_chat_window');
+ set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60);
this.updateVisualFoldState();
},
/**
@@ -179,8 +191,12 @@ const PublicLivechatWindow = Widget.extend({
* @private
* @param {Object} messageData
*/
- _postMessage(messageData) {
- this.trigger_up('post_message_chat_window', { messageData });
+ async _postMessage(messageData) {
+ try {
+ await this.messaging.publicLivechatGlobal.livechatButtonView.sendMessage(messageData);
+ } catch (_err) {
+ await this.messaging.publicLivechatGlobal.livechatButtonView.sendMessage(messageData); // try again just in case
+ }
this.messaging.publicLivechatGlobal.publicLivechat.widget.postMessage(messageData)
.then(() => {
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.xml b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.xml
index c7b6f09c90d..335101276b6 100644
--- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.xml
+++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.xml
@@ -32,7 +32,7 @@
-
+
()
diff --git a/addons/im_livechat/static/src/models/chatbot.js b/addons/im_livechat/static/src/models/chatbot.js
index 2fea443fb68..38d539d8a9a 100644
--- a/addons/im_livechat/static/src/models/chatbot.js
+++ b/addons/im_livechat/static/src/models/chatbot.js
@@ -25,7 +25,7 @@ registerModel({
}
if (!options || !options.skipRenderMessages) {
- this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
}
},
/**
@@ -99,7 +99,7 @@ registerModel({
});
});
- this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
},
/**
* Processes the step, depending on the current state of the script and the author of the last
@@ -259,7 +259,7 @@ registerModel({
this.currentStep.data &&
this.currentStep.data.chatbot_step_type === 'question_email'
) {
- triggerNextStep = await this.messaging.publicLivechatGlobal.livechatButtonView.widget._chatbotValidateEmail();
+ triggerNextStep = await this.validateEmail();
}
if (!triggerNextStep) {
@@ -285,7 +285,7 @@ registerModel({
} else {
// did not find next step -> end the script
this.currentStep.data.chatbot_step_is_last = true;
- this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
this.endScript();
}
@@ -293,6 +293,36 @@ registerModel({
return nextStep;
},
+ /**
+ * 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.
+ *
+ * The validation is made server-side to have the same test when we validate here and when we
+ * register the answer, but also to easily post a message as the bot ("Sorry, try again...").
+ *
+ * Returns a boolean stating whether the email was valid or not.
+ */
+ async validateEmail() {
+ let emailValidResult = await this.messaging.rpc({
+ route: '/chatbot/step/validate_email',
+ params: { channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid },
+ });
+
+ if (emailValidResult.success) {
+ this.currentStep.data.is_email_valid = true;
+ this.saveSession();
+
+ return true;
+ } else {
+ // email is not valid, let the user try again
+ this.messaging.publicLivechatGlobal.chatWindow.enableInput();
+ if (emailValidResult.posted_message) {
+ this.addMessage(emailValidResult.posted_message);
+ }
+
+ return false;
+ }
+ },
/**
* This method will be transformed into a 'debounced' version (see init).
*
@@ -361,6 +391,28 @@ registerModel({
this.awaitUserInputDebounceTime,
);
},
+ /**
+ * @private
+ * @returns {boolean|FieldCommand}
+ */
+ _computeIsActive() {
+ if (this.messaging.publicLivechatGlobal.isTestChatbot) {
+ return true;
+ }
+ if (this.messaging.publicLivechatGlobal.rule && this.messaging.publicLivechatGlobal.rule.chatbot) {
+ return true;
+ }
+ if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
+ return true;
+ }
+ if (this.state === 'welcome') {
+ return true;
+ }
+ if (this.localStorageState) {
+ return true;
+ }
+ return clear();
+ },
/**
* @private
* @returns {boolean}
@@ -555,6 +607,10 @@ registerModel({
compute: '_computeHasRestartButton',
default: false,
}),
+ isActive: attr({
+ compute: '_computeIsActive',
+ default: false,
+ }),
isExpectingUserInput: attr({
compute: '_computeIsExpectingUserInput',
default: false,
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 6aa82a1e70d..42544aa3b4d 100644
--- a/addons/im_livechat/static/src/models/livechat_button_view.js
+++ b/addons/im_livechat/static/src/models/livechat_button_view.js
@@ -1,7 +1,5 @@
/** @odoo-module **/
-import PublicLivechatMessage from '@im_livechat/legacy/models/public_livechat_message';
-
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
@@ -24,10 +22,8 @@ registerModel({
* @param {Object} [options={}]
*/
addMessage(data, options) {
- const messageWidget = new PublicLivechatMessage(this, this.messaging, data);
-
const hasAlreadyMessage = _.some(this.messaging.publicLivechatGlobal.messages, function (msg) {
- return messageWidget.getID() === msg.id;
+ return data.id === msg.id;
});
if (hasAlreadyMessage) {
return;
@@ -35,11 +31,10 @@ registerModel({
const message = this.messaging.models['PublicLivechatMessage'].insert({
data,
id: data.id,
- widget: messageWidget,
});
if (this.messaging.publicLivechatGlobal.publicLivechat && this.messaging.publicLivechatGlobal.publicLivechat.widget) {
- this.messaging.publicLivechatGlobal.publicLivechat.widget.addMessage(messageWidget);
+ this.messaging.publicLivechatGlobal.publicLivechat.widget.addMessage(message.widget);
}
if (options && options.prepend) {
@@ -244,7 +239,7 @@ registerModel({
* @returns {string|FieldCommand}
*/
_computeInputPlaceholder() {
- if (this.isChatbot) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isActive) {
// 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();
@@ -254,28 +249,6 @@ registerModel({
}
return this.env._t("Ask something ...");
},
- /**
- * @private
- * @returns {boolean}
- */
- _computeIsChatbot() {
- if (this.messaging.publicLivechatGlobal.isTestChatbot) {
- return true;
- }
- if (this.messaging.publicLivechatGlobal.rule && this.messaging.publicLivechatGlobal.rule.chatbot) {
- return true;
- }
- if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
- return true;
- }
- if (this.messaging.publicLivechatGlobal.chatbot.state === 'welcome') {
- return true;
- }
- if (this.messaging.publicLivechatGlobal.chatbot.localStorageState) {
- return true;
- }
- return clear();
- },
/**
* @private
* @returns {FieldCommand}
@@ -295,7 +268,7 @@ registerModel({
* @returns {string}
*/
_computeServerUrl() {
- if (this.isChatbot) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isActive) {
return this.messaging.publicLivechatGlobal.chatbot.serverUrl;
}
return this.messaging.publicLivechatGlobal.serverUrl;
@@ -353,7 +326,7 @@ registerModel({
if (!this.messaging.publicLivechatGlobal.history) {
this.widget._sendWelcomeMessage();
}
- this.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
this.messaging.publicLivechatGlobal.update({ notificationHandler: {} });
set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60);
@@ -437,7 +410,7 @@ registerModel({
return;
}
if (
- this.isChatbot &&
+ this.messaging.publicLivechatGlobal.chatbot.isActive &&
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
@@ -482,7 +455,7 @@ registerModel({
*/
async _sendMessageChatbotBefore() {
if (
- this.isChatbot &&
+ this.messaging.publicLivechatGlobal.chatbot.isActive &&
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
@@ -519,10 +492,6 @@ registerModel({
compute: '_computeInputPlaceholder',
default: '',
}),
- isChatbot: attr({
- compute: '_computeIsChatbot',
- default: false,
- }),
isOpenChatDebounced: attr({
compute: '_computeIsOpenChatDebounced',
default: true,
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 6a0bb1d4800..0eaae1274b1 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
@@ -67,7 +67,7 @@ registerModel({
if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.isAtBottom()) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() });
}
- this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
return;
}
case 'mail.message/insert': {
@@ -76,7 +76,7 @@ registerModel({
return;
}
message.widget._body = utils.Markup(payload.body);
- this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
return;
}
}
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 514739158e6..c24a98beb98 100644
--- a/addons/im_livechat/static/src/models/public_livechat_message.js
+++ b/addons/im_livechat/static/src/models/public_livechat_message.js
@@ -4,8 +4,18 @@ import { registerModel } from '@mail/model/model_core';
import { attr } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
+import PublicLivechatMessage from '@im_livechat/legacy/models/public_livechat_message';
+
registerModel({
name: 'PublicLivechatMessage',
+ lifecycleHooks: {
+ _created() {
+ this.update({ widget: new PublicLivechatMessage(this.messaging.publicLivechatGlobal.livechatButtonView.widget, this.messaging, this.data) });
+ },
+ _willDelete() {
+ this.widget.destroy();
+ },
+ },
recordMethods: {
/**
* @private
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 cf81ca1a043..e1b3a953067 100644
--- a/addons/im_livechat/static/src/models/public_livechat_window.js
+++ b/addons/im_livechat/static/src/models/public_livechat_window.js
@@ -47,6 +47,30 @@ registerModel({
.addClass('text-center fst-italic bg-200')
.val(disableText);
},
+ renderMessages() {
+ const shouldScroll = !this.isFolded && this.publicLivechatView.widget.isAtBottom();
+ this.widget.render();
+ if (shouldScroll) {
+ this.publicLivechatView.widget.scrollToBottom();
+ }
+ const self = this;
+
+ this.widget.$('.o_thread_message:last .o_livechat_chatbot_options li').each(function () {
+ $(this).on('click', self.messaging.publicLivechatGlobal.livechatButtonView.widget._onChatbotOptionClicked.bind(self.messaging.publicLivechatGlobal.livechatButtonView.widget));
+ });
+
+ this.widget.$('.o_livechat_chatbot_main_restart').on('click',
+ this.messaging.publicLivechatGlobal.livechatButtonView.onChatbotRestartScript
+ );
+
+ if (this.messaging.publicLivechatGlobal.messages.length !== 0) {
+ const lastMessage = this.messaging.publicLivechatGlobal.lastMessage;
+ const stepAnswers = lastMessage.widget.getChatbotStepAnswers();
+ if (stepAnswers && stepAnswers.length !== 0 && !lastMessage.widget.getChatbotStepAnswerId()) {
+ this.disableInput(this.env._t("Select an option above"));
+ }
+ }
+ },
/**
* @private
* @returns {string}
diff --git a/addons/website_livechat/static/src/models/livechat_button_view.js b/addons/website_livechat/static/src/models/livechat_button_view.js
index cd00dcdc03f..8be0128e25e 100644
--- a/addons/website_livechat/static/src/models/livechat_button_view.js
+++ b/addons/website_livechat/static/src/models/livechat_button_view.js
@@ -18,7 +18,7 @@ patchRecordMethods('LivechatButtonView', {
async onChatbotRestartScript(ev) {
if (this.messaging.publicLivechatGlobal.chatbot.isWebsiteLivechatTourFlow) {
this.messaging.publicLivechatGlobal.update({ messages: clear() });
- this.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
}
return this._super(ev);
},
@@ -49,7 +49,7 @@ patchRecordMethods('LivechatButtonView', {
});
await this.openChatWindow();
this.widget._sendWelcomeMessage();
- this.widget._renderMessages();
+ this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
this.env.services.bus_service.addChannel(this.messaging.publicLivechatGlobal.publicLivechat.uuid);
set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60);
this.update({ isOpeningChat: false });