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 - -

@@ -198,12 +183,6 @@
- - -

-
- -
@@ -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 });