[IMP] im_livechat, web: prettify im_livechat module

closes odoo/odoo#112999

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
tsm-odoo
2023-02-18 02:49:45 +01:00
parent c79bb3c9c6
commit 5b4da6ae73
70 changed files with 2558 additions and 2119 deletions
@@ -1,13 +1,12 @@
/** @odoo-module **/
import { ThreadNeedactionPreviewView } from '@mail/components/thread_needaction_preview/thread_needaction_preview';
import { ThreadNeedactionPreviewView } from "@mail/components/thread_needaction_preview/thread_needaction_preview";
import { patch } from 'web.utils';
import { patch } from "web.utils";
const components = { ThreadNeedactionPreviewView };
patch(components.ThreadNeedactionPreviewView.prototype, 'thread_needaction_preview', {
patch(components.ThreadNeedactionPreviewView.prototype, "thread_needaction_preview", {
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -16,10 +15,12 @@ patch(components.ThreadNeedactionPreviewView.prototype, 'thread_needaction_previ
* @override
*/
image(...args) {
if (this.threadNeedactionPreviewView.thread.channel && this.threadNeedactionPreviewView.thread.channel.channel_type === 'livechat') {
return '/mail/static/src/img/smiley/avatar.jpg';
if (
this.threadNeedactionPreviewView.thread.channel &&
this.threadNeedactionPreviewView.thread.channel.channel_type === "livechat"
) {
return "/mail/static/src/img/smiley/avatar.jpg";
}
return this._super(...args);
}
},
});
@@ -3,10 +3,10 @@
import { assets } from "@web/core/assets";
/**
* This file should be used in the context of an external widget loading (e.g: live chat in a non-Odoo website)
* It overrides the 'loadJS' method that is supposed to load additional scripts, based on a relative URL (e.g: '/web/webclient/locale/en_US')
* As we're not in an Odoo website context, the calls will not work, and we avoid a 404 request.
*/
* This file should be used in the context of an external widget loading (e.g: live chat in a non-Odoo website)
* It overrides the 'loadJS' method that is supposed to load additional scripts, based on a relative URL (e.g: '/web/webclient/locale/en_US')
* As we're not in an Odoo website context, the calls will not work, and we avoid a 404 request.
*/
assets.loadJS = function (url) {
console.log('Tried to load the following script on an external website: ' + url);
console.log("Tried to load the following script on an external website: " + url);
};
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { registry } from "@web/core/registry";
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
const { Component } = owl;
@@ -20,4 +20,4 @@ ColorsResetButton.extractProps = ({ attrs }) => {
return attrs.options;
};
registry.category('view_widgets').add('colors_reset_button', ColorsResetButton);
registry.category("view_widgets").add("colors_reset_button", ColorsResetButton);
@@ -1,6 +1,5 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import {
Many2ManyTagsField,
@@ -19,11 +18,11 @@ export class ChatbotScriptTriggeringAnswersMany2Many extends Many2ManyTagsField
if (this.props.record.model.root.data.id) {
this.env.services.user.updateContext({
force_domain_chatbot_script_id: this.props.record.model.root.data.id
force_domain_chatbot_script_id: this.props.record.model.root.data.id,
});
}
}
};
}
export const chatbotScriptTriggeringAnswersMany2Many = {
...many2ManyTagsField,
@@ -2,24 +2,28 @@
import { ListRenderer } from "@web/views/list/list_renderer";
import { registry } from "@web/core/registry";
import { patch } from '@web/core/utils/patch';
import { useX2ManyCrud, useOpenX2ManyRecord, X2ManyFieldDialog } from "@web/views/fields/relational_utils";
import { patch } from "@web/core/utils/patch";
import {
useX2ManyCrud,
useOpenX2ManyRecord,
X2ManyFieldDialog,
} from "@web/views/fields/relational_utils";
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
const fieldRegistry = registry.category("fields");
patch(X2ManyFieldDialog.prototype, 'chatbot_script_step_sequence', {
patch(X2ManyFieldDialog.prototype, "chatbot_script_step_sequence", {
/**
* Dirty patching of the 'X2ManyFieldDialog'.
* It is done to force the "save and new" to close the dialog first, and then click again on
* the "Add a line" link.
*
*
* This is the only way (or at least the least complicated) to correctly compute the sequence
* field, which is crucial when creating chatbot.steps, as they depend on each other.
*
*
*/
async save({ saveAndNew }) {
if (this.record.resModel !== 'chatbot.script.step') {
if (this.record.resModel !== "chatbot.script.step") {
return this._super(...arguments);
}
@@ -30,12 +34,12 @@ patch(X2ManyFieldDialog.prototype, 'chatbot_script_step_sequence', {
}
if (saveAndNew) {
document.querySelector('.o_field_x2many_list_row_add a').click();
document.querySelector(".o_field_x2many_list_row_add a").click();
}
this.props.close();
return true;
}
},
});
export class ChatbotStepsOne2manyRenderer extends ListRenderer {
@@ -65,10 +69,7 @@ export class ChatbotStepsOne2many extends X2ManyField {
setup() {
super.setup();
const { saveRecord, updateRecord } = useX2ManyCrud(
() => this.list,
this.isMany2Many
);
const { saveRecord, updateRecord } = useX2ManyCrud(() => this.list, this.isMany2Many);
const openRecord = useOpenX2ManyRecord({
resModel: this.list.resModel,
@@ -77,7 +78,7 @@ export class ChatbotStepsOne2many extends X2ManyField {
getList: () => this.list,
saveRecord: async (record) => {
await saveRecord(record);
await this.props.record.save({stayInEdition: true});
await this.props.record.save({ stayInEdition: true });
},
updateRecord: updateRecord,
});
@@ -94,7 +95,7 @@ export class ChatbotStepsOne2many extends X2ManyField {
});
};
}
};
}
export const chatbotStepsOne2many = {
...x2ManyField,
@@ -105,5 +106,5 @@ fieldRegistry.add("chatbot_steps_one2many", chatbotStepsOne2many);
ChatbotStepsOne2many.components = {
...X2ManyField.components,
ListRenderer: ChatbotStepsOne2manyRenderer
ListRenderer: ChatbotStepsOne2manyRenderer,
};
@@ -1,6 +1,6 @@
/** @odoo-module **/
import CCThrottleFunctionObject from '@im_livechat/legacy/models/cc_throttle_function_object';
import CCThrottleFunctionObject from "@im_livechat/legacy/models/cc_throttle_function_object";
/**
* A function that creates a cancellable and clearable (CC) throttle version
@@ -1,13 +1,12 @@
/** @odoo-module **/
import Class from 'web.Class';
import Class from "web.Class";
/**
* This object models the behaviour of the clearable and cancellable (CC)
* throttle version of a provided function.
*/
const CCThrottleFunctionObject = Class.extend({
/**
* @param {Object} params
* @param {integer} params.duration duration of the 'cooldown' phase, i.e.
@@ -90,10 +89,7 @@ const CCThrottleFunctionObject = Class.extend({
*/
_cooldown() {
this.cancel();
this._cooldownTimeout = setTimeout(
this._onCooldownTimeout.bind(this),
this._duration
);
this._cooldownTimeout = setTimeout(this._onCooldownTimeout.bind(this), this._duration);
},
//--------------------------------------------------------------------------
@@ -1,14 +1,14 @@
/** @odoo-module **/
import CCThrottleFunction from '@im_livechat/legacy/models/cc_throttle_function';
import Timer from '@im_livechat/legacy/models/timer';
import Timers from '@im_livechat/legacy/models/timers';
import CCThrottleFunction from "@im_livechat/legacy/models/cc_throttle_function";
import Timer from "@im_livechat/legacy/models/timer";
import Timers from "@im_livechat/legacy/models/timers";
import Class from 'web.Class';
import { _t } from 'web.core';
import session from 'web.session';
import Mixins from 'web.mixins';
import { sprintf } from 'web.utils';
import Class from "web.Class";
import { _t } from "web.core";
import session from "web.session";
import Mixins from "web.mixins";
import { sprintf } from "web.utils";
/**
* Thread model that represents a livechat on the website-side. This livechat
@@ -98,14 +98,18 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
if (params.data.message_unread_counter !== undefined) {
this.messaging.publicLivechatGlobal.publicLivechat.update({
unreadCounter: params.data.message_unread_counter
unreadCounter: params.data.message_unread_counter,
});
}
if (_.isBoolean(params.data.folded)) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ isFolded: params.data.folded });
this.messaging.publicLivechatGlobal.publicLivechat.update({
isFolded: params.data.folded,
});
} else {
this.messaging.publicLivechatGlobal.publicLivechat.update({ isFolded: params.data.state === 'folded' });
this.messaging.publicLivechatGlobal.publicLivechat.update({
isFolded: params.data.state === "folded",
});
}
},
@@ -136,9 +140,11 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
* @override
* @returns {@im_livechat/legacy/models/public_livechat_message[]}
*/
getMessages() {
getMessages() {
// ignore removed messages
return this.messaging.publicLivechatGlobal.messages.filter(message => !message.widget.isEmpty()).map(message => message.widget);
return this.messaging.publicLivechatGlobal.messages
.filter((message) => !message.widget.isEmpty())
.map((message) => message.widget);
},
/**
* Get the text to display when some partners are typing something on the
@@ -167,28 +173,34 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
*/
getTypingMembersToText() {
const typingPartnerIDs = this._typingPartnerIDs;
const typingMembers = (
this.messaging.publicLivechatGlobal.publicLivechat.operator && this._typingPartnerIDs.includes(this.messaging.publicLivechatGlobal.publicLivechat.operator.id)
? [this.messaging.publicLivechatGlobal.publicLivechat.operator]
: []
);
const typingMembers =
this.messaging.publicLivechatGlobal.publicLivechat.operator &&
this._typingPartnerIDs.includes(
this.messaging.publicLivechatGlobal.publicLivechat.operator.id
)
? [this.messaging.publicLivechatGlobal.publicLivechat.operator]
: [];
const sortedTypingMembers = _.sortBy(typingMembers, function (member) {
return _.indexOf(typingPartnerIDs, member.id);
});
const displayableTypingMembers = sortedTypingMembers.slice(0, 3);
if (displayableTypingMembers.length === 0) {
return '';
return "";
} else if (displayableTypingMembers.length === 1) {
return sprintf(_t("%s is typing..."), displayableTypingMembers[0].name);
} else if (displayableTypingMembers.length === 2) {
return sprintf(_t("%s and %s are typing..."),
displayableTypingMembers[0].name,
displayableTypingMembers[1].name);
return sprintf(
_t("%s and %s are typing..."),
displayableTypingMembers[0].name,
displayableTypingMembers[1].name
);
} else {
return sprintf(_t("%s, %s and more are typing..."),
displayableTypingMembers[0].name,
displayableTypingMembers[1].name);
return sprintf(
_t("%s, %s and more are typing..."),
displayableTypingMembers[0].name,
displayableTypingMembers[1].name
);
}
},
/**
@@ -204,7 +216,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
* @returns {boolean}
*/
isSomeoneTyping() {
return !(_.isEmpty(this._typingPartnerIDs));
return !_.isEmpty(this._typingPartnerIDs);
},
/**
* Mark the thread as read, which resets the unread counter to 0. This is
@@ -289,18 +301,18 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
*/
toData() {
return {
chatbot_script_id: this.messaging.publicLivechatGlobal.publicLivechat.data.chatbot_script_id,
chatbot_script_id:
this.messaging.publicLivechatGlobal.publicLivechat.data.chatbot_script_id,
folded: this.messaging.publicLivechatGlobal.publicLivechat.isFolded,
id: this.messaging.publicLivechatGlobal.publicLivechat.id,
message_unread_counter: this.messaging.publicLivechatGlobal.publicLivechat.unreadCounter,
operator_pid: (
this.messaging.publicLivechatGlobal.publicLivechat.operator
message_unread_counter:
this.messaging.publicLivechatGlobal.publicLivechat.unreadCounter,
operator_pid: this.messaging.publicLivechatGlobal.publicLivechat.operator
? [
this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
this.messaging.publicLivechatGlobal.publicLivechat.operator.name,
]
: []
),
this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
this.messaging.publicLivechatGlobal.publicLivechat.operator.name,
]
: [],
name: this.messaging.publicLivechatGlobal.publicLivechat.name,
uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
};
@@ -344,10 +356,14 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
// sent. Until then, do not notify visitor is typing.
return;
}
return session.rpc('/im_livechat/notify_typing', {
uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
is_typing: params.typing,
}, { shadow: true });
return session.rpc(
"/im_livechat/notify_typing",
{
uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
is_typing: params.typing,
},
{ shadow: true }
);
},
/**
* Warn views that the list of users that are currently typing on this
@@ -1,11 +1,11 @@
/** @odoo-module **/
import * as mailUtils from '@mail/js/utils';
import * as mailUtils from "@mail/js/utils";
import Class from 'web.Class';
import { _t } from 'web.core';
import session from 'web.session';
import time from 'web.time';
import Class from "web.Class";
import { _t } from "web.core";
import session from "web.session";
import time from "web.time";
/**
* This is a message that is handled by im_livechat, without making use of the
@@ -15,7 +15,6 @@ import time from 'web.time';
* @see @im_livechat/legacy/models/public_livechat_message for more information.
*/
const PublicLivechatMessage = Class.extend({
/**
* @param {@im_livechat/legacy/widgets/livechat_button} parent
* @param {Messaging} messaging
@@ -78,7 +77,7 @@ const PublicLivechatMessage = Class.extend({
} else if (this.hasAuthor() && session.user_id) {
source += `/web/image/res.partner/${this.getAuthorID()}/avatar_128`;
} else {
source += '/mail/static/src/img/smiley/avatar.jpg';
source += "/mail/static/src/img/smiley/avatar.jpg";
}
return source;
},
@@ -120,13 +119,13 @@ const PublicLivechatMessage = Class.extend({
* @return {string}
*/
getDateDay() {
const date = this.getDate().format('YYYY-MM-DD');
if (date === moment().format('YYYY-MM-DD')) {
const date = this.getDate().format("YYYY-MM-DD");
if (date === moment().format("YYYY-MM-DD")) {
return _t("Today");
} else if (date === moment().subtract(1, 'days').format('YYYY-MM-DD')) {
} else if (date === moment().subtract(1, "days").format("YYYY-MM-DD")) {
return _t("Yesterday");
}
return this.getDate().format('LL');
return this.getDate().format("LL");
},
/**
* Get the text to display for the author of the message
@@ -260,9 +259,11 @@ const PublicLivechatMessage = Class.extend({
* @return {boolean}
*/
_isMyselfAuthor() {
return this.hasAuthor() && (this.getAuthorID() === this.messaging.publicLivechatGlobal.options.current_partner_id);
return (
this.hasAuthor() &&
this.getAuthorID() === this.messaging.publicLivechatGlobal.options.current_partner_id
);
},
});
export default PublicLivechatMessage;
@@ -1,12 +1,11 @@
/** @odoo-module **/
import Class from 'web.Class';
import Class from "web.Class";
/**
* This class creates a timer which, when times out, calls a function.
*/
const Timer = Class.extend({
/**
* Instantiate a new timer. Note that the timer is not started on
* initialization (@see start method).
@@ -60,7 +59,6 @@ const Timer = Class.extend({
_onTimeout() {
this._timeoutCallback();
},
});
export default Timer;
@@ -1,14 +1,13 @@
/** @odoo-module **/
import Timer from '@im_livechat/legacy/models/timer';
import Timer from "@im_livechat/legacy/models/timer";
import Class from 'web.Class';
import Class from "web.Class";
/**
* This class lists several timers that use a same callback and duration.
*/
const Timers = Class.extend({
/**
* Instantiate a new list of timers
*
@@ -39,7 +38,7 @@ const Timers = Class.extend({
* @param {Array} [params.timeoutCallbackArguments]
* @param {integer} params.timerID
*/
registerTimer(params) {
registerTimer(params) {
const timerID = params.timerID;
if (this._timers[timerID]) {
this._timers[timerID].clear();
@@ -48,11 +47,11 @@ const Timers = Class.extend({
duration: this._duration,
onTimeout: this._timeoutCallback,
};
if ('timeoutCallbackArguments' in params) {
timerParams.onTimeout = this._timeoutCallback.bind.apply(
this._timeoutCallback,
[null, ...params.timeoutCallbackArguments]
);
if ("timeoutCallbackArguments" in params) {
timerParams.onTimeout = this._timeoutCallback.bind.apply(this._timeoutCallback, [
null,
...params.timeoutCallbackArguments,
]);
} else {
timerParams.onTimeout = this._timeoutCallback;
}
@@ -66,14 +65,13 @@ const Timers = Class.extend({
* @param {Object} params
* @param {integer} params.timerID
*/
unregisterTimer(params) {
unregisterTimer(params) {
const timerID = params.timerID;
if (this._timers[timerID]) {
this._timers[timerID].clear();
delete this._timers[timerID];
}
},
});
export default Timers;
@@ -21,7 +21,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
@include media-breakpoint-down(md) {
width: 100%;
height: 100%!important;
height: 100% !important;
box-shadow: none;
&.o_folded {
display: none;
@@ -37,9 +37,9 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
display: flex;
flex: 0 0 auto;
color: white;
padding: $o-mail-chatter-gap*0.5 $o-mail-chatter-gap;
padding: $o-mail-chatter-gap * 0.5 $o-mail-chatter-gap;
border-radius: 3px 3px 0 0;
border-bottom: 1px solid map-get($grays, '300');
border-bottom: 1px solid map-get($grays, "300");
background-color: $o-brand-odoo;
padding: 8px;
cursor: pointer;
@@ -89,7 +89,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
padding-left: 2px;
.o_mail_thread_typing_icon_dot {
background: map-get($grays, '300');
background: map-get($grays, "300");
}
}
}
@@ -103,7 +103,6 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
@include o-hover-opacity(0.7, 1);
}
}
}
.o_mail_thread {
@@ -139,7 +138,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
width: auto;
height: 28px;
color: white;
background-color: map-get($grays, '900');
background-color: map-get($grays, "900");
cursor: pointer;
box-shadow: none;
@@ -172,7 +171,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
> li.o_thread_window_header {
font-size: 12px;
padding: 3px 5px;
&~li.o_thread_window_header {
& ~ li.o_thread_window_header {
border-top: 1px solid white;
}
&:hover {
@@ -197,7 +196,6 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
// legacy: mail/thread.scss
//-------------------------------------
.o_mail_thread_loading {
display: flex;
align-items: center;
@@ -208,7 +206,8 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
margin-right: 5px;
}
.o_mail_thread, .o_mail_activity {
.o_mail_thread,
.o_mail_activity {
.o_thread_show_more {
text-align: center;
}
@@ -230,7 +229,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
margin-top: 0px;
margin-bottom: 15px;
}
border-bottom: 1px solid map-get($grays, '400');
border-bottom: 1px solid map-get($grays, "400");
text-align: center;
.o_thread_date {
@@ -263,8 +262,8 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
margin-bottom: 0px;
&.o_mail_not_discussion {
background-color: rgba(map-get($grays, '300'), 0.5);
border-bottom: 1px solid map-get($grays, '400');
background-color: rgba(map-get($grays, "300"), 0.5);
border-bottom: 1px solid map-get($grays, "400");
}
.o_thread_message_sidebar {
@@ -323,7 +322,8 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
}
}
&:hover, &.o_thread_selected_message {
&:hover,
&.o_thread_selected_message {
.o_thread_message_side_date {
display: inline-block;
opacity: $o-mail-thread-side-date-opacity;
@@ -365,7 +365,8 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
color: gray;
}
[summary~=o_mail_notification] { // name conflicts with channel notifications, but is odoo notification buttons to hide in chatter if present
[summary~="o_mail_notification"] {
// name conflicts with channel notifications, but is odoo notification buttons to hide in chatter if present
display: none;
}
@@ -396,7 +397,10 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
}
}
.o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply, .o_thread_message_notification {
.o_thread_message_star,
.o_thread_message_needaction,
.o_thread_message_reply,
.o_thread_message_notification {
padding: 4px;
}
@@ -409,7 +413,8 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
}
}
.o_attachments_list, .o_attachments_previews {
.o_attachments_list,
.o_attachments_previews {
&:last-child {
margin-bottom: $grid-gutter-width;
}
@@ -443,7 +448,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
width: 3px;
height: 3px;
border-radius: 50%;
background: map-get($grays, '800');
background: map-get($grays, "800");
animation: o_mail_thread_typing_icon_dot 1.5s linear infinite;
&:nth-child(2) {
@@ -457,7 +462,9 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
}
@keyframes o_mail_thread_typing_icon_dot {
0%, 40%, 100% {
0%,
40%,
100% {
transform: initial;
}
20% {
@@ -478,7 +485,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
cursor: pointer;
white-space: nowrap;
background-color: rgba(60, 60, 60, 0.6);
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Arial, Verdana, sans-serif;
font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, Verdana, sans-serif;
font-size: 14px;
font-weight: bold;
padding: 10px;
@@ -498,7 +505,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
}
.btn {
color: #FFFFFF;
color: #ffffff;
background-color: #30908e;
border-color: #2d8685;
border: 1px solid transparent;
@@ -532,9 +539,9 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #FFFFFF;
background-color: #ffffff;
background-clip: padding-box;
border: 1px solid #CED4DA;
border: 1px solid #ced4da;
}
button {
display: inline-block;
@@ -564,10 +571,11 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
> img {
width: 65px;
opacity: 0.60;
opacity: 0.6;
cursor: pointer;
margin: 10px;
&:hover, &.selected {
&:hover,
&.selected {
opacity: 1;
}
}
@@ -606,7 +614,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
.o_PublicLivechatMessage {
display: flex;
position: relative;
&.o-isVisitorTheAuthor {
flex-direction: row-reverse !important;
}
@@ -648,7 +656,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
justify-content: flex-end;
margin-left: map-get($spacers, 4);
}
&.o-isOperatorTheAuthor {
margin-right: map-get($spacers, 4);
}
@@ -664,11 +672,10 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
&.o-isVisitorTheAuthor {
margin-left: map-get($spacers, 2);
}
&.o-isOperatorTheAuthor {
margin-right: map-get($spacers, 2);
}
}
.o_PublicLivechatMessage_background {
@@ -678,11 +685,11 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
top: 0;
width: 100%;
height: 100%;
transition: opacity .5s ease-out;
transition: opacity 0.5s ease-out;
margin-right: map-get($spacers, 5);
border-bottom-right-radius: 0.6rem !important;
border-bottom-left-radius: 0.6rem !important;
border: 1px solid #C9CCD2 !important;
border: 1px solid #c9ccd2 !important;
&.o-isVisitorTheAuthor {
border-bottom-left-radius: 0.6rem !important;
@@ -700,7 +707,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
border-color: #17a2b8 !important;
background-color: rgba(23, 162, 184, 0.5) !important;
color: #000;
opacity: 15%
opacity: 15%;
}
}
@@ -1,10 +1,10 @@
/** @odoo-module **/
import session from 'web.session';
import time from 'web.time';
import utils from 'web.utils';
import session from "web.session";
import time from "web.time";
import utils from "web.utils";
import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
import LivechatButton from "@im_livechat/legacy/widgets/livechat_button";
/**
* Override of the LivechatButton to include chatbot capabilities.
@@ -14,11 +14,10 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
* - When the user picks an option or answers to the chatbot, display a "chatbot is typing..."
* message for a couple seconds and then trigger the next step of the script
*/
LivechatButton.include({
//--------------------------------------------------------------------------
// Private - LiveChat Overrides
//--------------------------------------------------------------------------
LivechatButton.include({
//--------------------------------------------------------------------------
// Private - LiveChat Overrides
//--------------------------------------------------------------------------
/**
* @private
@@ -28,7 +27,11 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
const parameters = this._super(...arguments);
const { publicLivechat } = this.messaging.publicLivechatGlobal;
if (publicLivechat && publicLivechat.isTemporary && !publicLivechat.data.chatbot_script_id) {
if (
publicLivechat &&
publicLivechat.isTemporary &&
!publicLivechat.data.chatbot_script_id
) {
return parameters;
} else if (publicLivechat && publicLivechat.data.chatbot_script_id) {
parameters.chatbot_script_id = publicLivechat.data.chatbot_script_id;
@@ -46,7 +49,9 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
if (this.messaging.publicLivechatGlobal.chatbot.isActive) {
this._sendWelcomeChatbotMessage(
0,
this.messaging.publicLivechatGlobal.chatbot.state === 'welcome' ? 0 : this.messaging.publicLivechatGlobal.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.state === "welcome"
? 0
: this.messaging.publicLivechatGlobal.chatbot.messageDelay
);
} else {
this._super(...arguments);
@@ -79,16 +84,14 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
if (chatbotStep.chatbot_step_message) {
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage({
id: '_welcome_' + stepIndex,
id: "_welcome_" + stepIndex,
is_discussion: true, // important for css style -> we only want white background for chatbot
author: (
this.messaging.publicLivechatGlobal.publicLivechat.operator
author: this.messaging.publicLivechatGlobal.publicLivechat.operator
? {
id: this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
name: this.messaging.publicLivechatGlobal.publicLivechat.operator.name,
}
: [['clear']]
),
id: this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
name: this.messaging.publicLivechatGlobal.publicLivechat.operator.name,
}
: [["clear"]],
body: utils.Markup(chatbotStep.chatbot_step_message),
chatbot_script_step_id: chatbotStep.chatbot_script_step_id,
chatbot_step_answers: chatbotStep.chatbot_step_answers,
@@ -106,7 +109,10 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
this.messaging.publicLivechatGlobal.chatbot.update({
welcomeMessageTimeout: setTimeout(() => {
if (!this.messaging.publicLivechatGlobal.chatWindow || !this.messaging.publicLivechatGlobal.chatWindow.exists()) {
if (
!this.messaging.publicLivechatGlobal.chatWindow ||
!this.messaging.publicLivechatGlobal.chatWindow.exists()
) {
return;
}
this._sendWelcomeChatbotMessage(stepIndex + 1, welcomeMessageDelay);
@@ -114,7 +120,10 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
}, welcomeMessageDelay),
});
} else {
if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator') {
if (
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type ===
"forward_operator"
) {
// special case when the last welcome message is a forward to an operator
// we need to save the welcome messages before continuing the script
// indeed, if there are no operator available, the script will continue
@@ -147,10 +156,10 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
ev.stopPropagation();
const $target = $(ev.currentTarget);
const stepId = $target.closest('ul').data('chatbotStepId');
const selectedAnswer = $target.data('chatbotStepAnswerId');
const stepId = $target.closest("ul").data("chatbotStepId");
const selectedAnswer = $target.data("chatbotStepAnswerId");
const redirectLink = $target.data('chatbotStepRedirectLink');
const redirectLink = $target.data("chatbotStepRedirectLink");
this.messaging.publicLivechatGlobal.chatbot.update({ isRedirecting: !!redirectLink });
await this.messaging.publicLivechatGlobal.livechatButtonView.sendMessage({
@@ -171,18 +180,19 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
}
const messageId = stepMessage.id;
stepMessage.widget.setChatbotStepAnswerId(selectedAnswer);
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_selected_answer_id = selectedAnswer;
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_selected_answer_id =
selectedAnswer;
this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
this.messaging.publicLivechatGlobal.chatbot.saveSession();
const saveAnswerPromise = session.rpc('/chatbot/answer/save', {
const saveAnswerPromise = session.rpc("/chatbot/answer/save", {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
message_id: messageId,
selected_answer_id: selectedAnswer,
});
if (redirectLink) {
await saveAnswerPromise; // ensure answer is saved before redirecting
await saveAnswerPromise; // ensure answer is saved before redirecting
window.location = redirectLink;
}
},
@@ -1,10 +1,10 @@
/** @odoo-module **/
import concurrency from 'web.concurrency';
import core from 'web.core';
import session from 'web.session';
import utils from 'web.utils';
import Widget from 'web.Widget';
import concurrency from "web.concurrency";
import core from "web.core";
import session from "web.session";
import utils from "web.utils";
import Widget from "web.Widget";
const _t = core._t;
/*
@@ -14,14 +14,14 @@ const _t = core._t;
* (only for red smiley), and sends the user feedback to the server.
*/
const Feedback = Widget.extend({
template: 'im_livechat.legacy.im_livechat.FeedBack',
template: "im_livechat.legacy.im_livechat.FeedBack",
events: {
'click .o_livechat_rating_choices img': '_onClickSmiley',
'click .o_livechat_no_feedback span': '_onClickNoFeedback',
'click .o_rating_submit_button': '_onClickSend',
'click .o_email_chat_button': '_onEmailChat',
'click .o_livechat_email_error .alert-link': '_onTryAgain',
"click .o_livechat_rating_choices img": "_onClickSmiley",
"click .o_livechat_no_feedback span": "_onClickNoFeedback",
"click .o_rating_submit_button": "_onClickSend",
"click .o_email_chat_button": "_onEmailChat",
"click .o_livechat_email_error .alert-link": "_onTryAgain",
},
/**
@@ -45,32 +45,35 @@ const Feedback = Widget.extend({
* @private
* @param {Object} options
*/
_sendFeedback(reason) {
_sendFeedback(reason) {
const args = {
uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
rate: this.rating,
reason,
};
this.dp.add(session.rpc('/im_livechat/feedback', args)).then((response) => {
this.dp.add(session.rpc("/im_livechat/feedback", args)).then((response) => {
const emoji = this.messaging.publicLivechatGlobal.RATING_TO_EMOJI[this.rating] || "??";
let content;
if (!reason) {
content = utils.sprintf(_t("Rating: %s"), emoji);
}
else {
} else {
content = "Rating reason: \n" + reason;
}
this.trigger('send_message', { content, isFeedback: true });
this.trigger("send_message", { content, isFeedback: true });
});
},
/**
* @private
*/
* @private
*/
_showThanksMessage() {
this.$('.o_livechat_rating_box').empty().append($('<div />', {
text: _t('Thank you for your feedback'),
class: 'text-muted'
}));
this.$(".o_livechat_rating_box")
.empty()
.append(
$("<div />", {
text: _t("Thank you for your feedback"),
class: "text-muted",
})
);
},
//--------------------------------------------------------------------------
@@ -81,16 +84,16 @@ const Feedback = Widget.extend({
* @private
*/
_onClickNoFeedback() {
this.trigger('feedback_sent'); // will close the chat
this.trigger("feedback_sent"); // will close the chat
},
/**
* @private
*/
_onClickSend() {
this.$('.o_livechat_rating_reason').hide();
this.$(".o_livechat_rating_reason").hide();
this._showThanksMessage();
if (_.isNumber(this.rating)) {
this._sendFeedback(this.$('textarea').val());
this._sendFeedback(this.$("textarea").val());
}
},
/**
@@ -98,55 +101,59 @@ const Feedback = Widget.extend({
* @param {MouseEvent} ev
*/
_onClickSmiley(ev) {
this.rating = parseInt($(ev.currentTarget).data('value'));
this.$('.o_livechat_rating_choices img').removeClass('selected');
this.$('.o_livechat_rating_choices img[data-value="' + this.rating + '"]').addClass('selected');
this.rating = parseInt($(ev.currentTarget).data("value"));
this.$(".o_livechat_rating_choices img").removeClass("selected");
this.$('.o_livechat_rating_choices img[data-value="' + this.rating + '"]').addClass(
"selected"
);
// only display textearea if bad smiley selected
if (this.rating !== 5) {
this._sendFeedback();
this.$('.o_livechat_rating_reason').show();
this.$(".o_livechat_rating_reason").show();
} else {
this.$('.o_livechat_rating_reason').hide();
this.$(".o_livechat_rating_reason").hide();
this._showThanksMessage();
this._sendFeedback();
}
},
/**
* @private
*/
* @private
*/
_onEmailChat() {
const $email = this.$('#o_email');
const $email = this.$("#o_email");
if (utils.is_email($email.val())) {
$email.removeAttr('title').removeClass('is-invalid').prop('disabled', true);
this.$('.o_email_chat_button').prop('disabled', true);
$email.removeAttr("title").removeClass("is-invalid").prop("disabled", true);
this.$(".o_email_chat_button").prop("disabled", true);
this._rpc({
route: '/im_livechat/email_livechat_transcript',
route: "/im_livechat/email_livechat_transcript",
params: {
uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
email: $email.val(),
}
}).then(() => {
this.$('o_livechat_email_sentLabel').show();
this.$('o_livechat_email_receiveCopyLabel').hide();
this.$('o_livechat_email_receiveCopyForm').hide();
}).guardedCatch(() => {
this.$('.o_livechat_email').hide();
this.$('.o_livechat_email_error').show();
});
},
})
.then(() => {
this.$("o_livechat_email_sentLabel").show();
this.$("o_livechat_email_receiveCopyLabel").hide();
this.$("o_livechat_email_receiveCopyForm").hide();
})
.guardedCatch(() => {
this.$(".o_livechat_email").hide();
this.$(".o_livechat_email_error").show();
});
} else {
$email.addClass('is-invalid').prop('title', _t('Invalid email address'));
$email.addClass("is-invalid").prop("title", _t("Invalid email address"));
}
},
/**
* @private
*/
* @private
*/
_onTryAgain() {
this.$('#o_email').prop('disabled', false);
this.$('.o_email_chat_button').prop('disabled', false);
this.$('.o_livechat_email_error').hide();
this.$('.o_livechat_email').show();
this.$("#o_email").prop("disabled", false);
this.$(".o_email_chat_button").prop("disabled", false);
this.$(".o_livechat_email_error").hide();
this.$(".o_livechat_email").show();
},
});
@@ -1,13 +1,13 @@
/** @odoo-module **/
import time from 'web.time';
import {getCookie} from 'web.utils.cookies';
import Widget from 'web.Widget';
import time from "web.time";
import { getCookie } from "web.utils.cookies";
import Widget from "web.Widget";
const LivechatButton = Widget.extend({
className: 'openerp o_livechat_button d-print-none',
className: "openerp o_livechat_button d-print-none",
events: {
'click': '_onClick'
click: "_onClick",
},
init(parent, messaging) {
this._super(parent);
@@ -32,8 +32,8 @@ const LivechatButton = Widget.extend({
* @private
* @return {integer} operator_id.partner_id.id if the cookie is set
*/
_get_previous_operator_id() {
const cookie = getCookie('im_livechat_previous_operator_pid');
_get_previous_operator_id() {
const cookie = getCookie("im_livechat_previous_operator_pid");
if (cookie) {
return cookie;
}
@@ -55,17 +55,20 @@ const LivechatButton = Widget.extend({
*/
_sendWelcomeMessage() {
if (this.messaging.publicLivechatGlobal.livechatButtonView.defaultMessage) {
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage({
id: '_welcome',
author: {
id: this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
name: this.messaging.publicLivechatGlobal.publicLivechat.operator.name,
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(
{
id: "_welcome",
author: {
id: this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
name: this.messaging.publicLivechatGlobal.publicLivechat.operator.name,
},
body: this.messaging.publicLivechatGlobal.livechatButtonView.defaultMessage,
date: time.datetime_to_str(new Date()),
model: "mail.channel",
res_id: this.messaging.publicLivechatGlobal.publicLivechat.id,
},
body: this.messaging.publicLivechatGlobal.livechatButtonView.defaultMessage,
date: time.datetime_to_str(new Date()),
model: "mail.channel",
res_id: this.messaging.publicLivechatGlobal.publicLivechat.id,
}, { prepend: true });
{ prepend: true }
);
}
},
@@ -1,10 +1,10 @@
/** @odoo-module **/
import * as mailUtils from '@mail/js/utils';
import * as mailUtils from "@mail/js/utils";
import core from 'web.core';
import time from 'web.time';
import Widget from 'web.Widget';
import core from "web.core";
import time from "web.time";
import Widget from "web.Widget";
const QWeb = core.qweb;
const _lt = core._lt;
@@ -23,20 +23,20 @@ const READ_LESS = _lt("read less");
* widget.
*/
const PublicLivechatView = Widget.extend({
className: 'o_mail_thread',
className: "o_mail_thread",
events: {
'click a': '_onClickRedirect',
'click img': '_onClickRedirect',
'click strong': '_onClickRedirect',
'click .o_thread_show_more': '_onClickShowMore',
'click .o_thread_message_needaction': '_onClickMessageNeedaction',
'click .o_thread_message_star': '_onClickMessageStar',
'click .o_thread_message_reply': '_onClickMessageReply',
'click .oe_mail_expand': '_onClickMailExpand',
'click .o_thread_message': '_onClickMessage',
'click': '_onClick',
'click .o_thread_message_notification_error': '_onClickMessageNotificationError',
"click a": "_onClickRedirect",
"click img": "_onClickRedirect",
"click strong": "_onClickRedirect",
"click .o_thread_show_more": "_onClickShowMore",
"click .o_thread_message_needaction": "_onClickMessageNeedaction",
"click .o_thread_message_star": "_onClickMessageStar",
"click .o_thread_message_reply": "_onClickMessageReply",
"click .oe_mail_expand": "_onClickMailExpand",
"click .o_thread_message": "_onClickMessage",
click: "_onClick",
"click .o_thread_message_notification_error": "_onClickMessageNotificationError",
},
/**
@@ -85,13 +85,18 @@ const PublicLivechatView = Widget.extend({
*/
render(options) {
let shouldScrollToBottomAfterRendering = false;
if (this._currentThreadID === this.messaging.publicLivechatGlobal.publicLivechat.id && this.isAtBottom()) {
if (
this._currentThreadID === this.messaging.publicLivechatGlobal.publicLivechat.id &&
this.isAtBottom()
) {
shouldScrollToBottomAfterRendering = true;
}
this._currentThreadID = this.messaging.publicLivechatGlobal.publicLivechat.id;
// copy so that reverse do not alter order in the thread object
const messages = _.clone(this.messaging.publicLivechatGlobal.publicLivechat.widget.getMessages());
const messages = _.clone(
this.messaging.publicLivechatGlobal.publicLivechat.widget.getMessages()
);
const modeOptions = this._enabledOptions;
@@ -108,14 +113,14 @@ const PublicLivechatView = Widget.extend({
// and in the same document (users can now post message in documents
// directly from a channel that follows it)
let prevMessage;
for (let message of messages) {
for (const message of messages) {
if (
// is first message of thread
!prevMessage ||
// more than 1 min. elasped
(Math.abs(message.getDate().diff(prevMessage.getDate())) > 60000) ||
prevMessage.getType() !== 'comment' ||
message.getType() !== 'comment' ||
Math.abs(message.getDate().diff(prevMessage.getDate())) > 60000 ||
prevMessage.getType() !== "comment" ||
message.getType() !== "comment" ||
// from a different author
prevMessage.getAuthorID() !== message.getAuthorID()
) {
@@ -130,17 +135,19 @@ const PublicLivechatView = Widget.extend({
messages.reverse();
}
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.Thread', {
displayAuthorMessages,
options,
ORDER,
dateFormat: time.getLangDatetimeFormat(),
widget: this,
}));
this.$el.html(
QWeb.render("im_livechat.legacy.mail.widget.Thread", {
displayAuthorMessages,
options,
ORDER,
dateFormat: time.getLangDatetimeFormat(),
widget: this,
})
);
for (let message of messages) {
for (const message of messages) {
const $message = this.$('.o_thread_message[data-message-id="' + message.getID() + '"]');
$message.find('.o_mail_timestamp').data('date', message.getDate());
$message.find(".o_mail_timestamp").data("date", message.getDate());
this._insertReadMore($message);
}
@@ -161,7 +168,7 @@ const PublicLivechatView = Widget.extend({
* @see /mail/init_messaging
*/
renderLoading() {
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.ThreadLoading'));
this.$el.html(QWeb.render("im_livechat.legacy.mail.widget.ThreadLoading"));
},
//--------------------------------------------------------------------------
@@ -177,7 +184,7 @@ const PublicLivechatView = Widget.extend({
*
* @return {boolean}
*/
isAtBottom() {
isAtBottom() {
const fullHeight = this.el.scrollHeight;
const topHiddenHeight = this.$el.scrollTop();
const visibleHeight = this.$el.outerHeight();
@@ -197,14 +204,15 @@ const PublicLivechatView = Widget.extend({
* @param {integer} [options.duration]
* @param {boolean} [options.onlyIfNecessary]
*/
scrollToMessage(options) {
scrollToMessage(options) {
const $target = this.$('.o_thread_message[data-message-id="' + options.messageID + '"]');
if (options.onlyIfNecessary) {
const delta = $target.parent().height() - $target.height();
let offset = delta < 0 ?
0 :
delta - ($target.offset().top - $target.offsetParent().offset().top);
offset = - Math.min(offset, 0);
let offset =
delta < 0
? 0
: delta - ($target.offset().top - $target.offsetParent().offset().top);
offset = -Math.min(offset, 0);
this.$el.scrollTo("+=" + offset + "px", options.duration);
} else if ($target.length) {
this.$el.scrollTo($target);
@@ -229,7 +237,7 @@ const PublicLivechatView = Widget.extend({
* Unselect the selected message
*/
unselectMessage() {
this.$('.o_thread_message').removeClass('o_thread_selected_message');
this.$(".o_thread_message").removeClass("o_thread_selected_message");
this._selectedMessageID = null;
},
@@ -247,43 +255,34 @@ const PublicLivechatView = Widget.extend({
* @private
* @param {jQuery} $element
*/
_insertReadMore($element) {
_insertReadMore($element) {
const groups = [];
let readMoreNodes;
// nodeType 1: element_node
// nodeType 3: text_node
const $children = $element.contents()
.filter(function () {
return this.nodeType === 1 ||
this.nodeType === 3 &&
this.nodeValue.trim();
});
const $children = $element.contents().filter(function () {
return this.nodeType === 1 || (this.nodeType === 3 && this.nodeValue.trim());
});
for (let child of $children) {
for (const child of $children) {
let $child = $(child);
// Hide Text nodes if "stopSpelling"
if (
child.nodeType === 3 &&
$child.prevAll('[id*="stopSpelling"]').length > 0
) {
if (child.nodeType === 3 && $child.prevAll('[id*="stopSpelling"]').length > 0) {
// Convert Text nodes to Element nodes
$child = $('<span>', {
$child = $("<span>", {
text: child.textContent,
'data-o-mail-quote': '1',
"data-o-mail-quote": "1",
});
child.parentNode.replaceChild($child[0], child);
}
// Create array for each 'read more' with nodes to toggle
if (
$child.attr('data-o-mail-quote') ||
(
$child.get(0).nodeName === 'BR' &&
$child.prev('[data-o-mail-quote="1"]').length > 0
)
$child.attr("data-o-mail-quote") ||
($child.get(0).nodeName === "BR" &&
$child.prev('[data-o-mail-quote="1"]').length > 0)
) {
if (!readMoreNodes) {
readMoreNodes = [];
@@ -297,11 +296,11 @@ const PublicLivechatView = Widget.extend({
}
}
for (let group of groups) {
for (const group of groups) {
// Insert link just before the first node
const $readMore = $('<a>', {
class: 'o_mail_read_more',
href: '#',
const $readMore = $("<a>", {
class: "o_mail_read_more",
href: "#",
text: READ_MORE,
}).insertBefore(group[0]);
@@ -310,7 +309,7 @@ const PublicLivechatView = Widget.extend({
$readMore.click(function (e) {
e.preventDefault();
isReadMore = !isReadMore;
for (let $child of group) {
for (const $child of group) {
$child.hide();
$child.toggle(!isReadMore);
}
@@ -325,20 +324,24 @@ const PublicLivechatView = Widget.extend({
* @param {string} options.model
* @param {integer} options.id
*/
_redirect: _.debounce(function (options) {
if ('channelID' in options) {
this.trigger('redirect_to_channel', options.channelID);
} else {
this.trigger('redirect', options.model, options.id);
}
}, 500, true),
_redirect: _.debounce(
function (options) {
if ("channelID" in options) {
this.trigger("redirect_to_channel", options.channelID);
} else {
this.trigger("redirect", options.model, options.id);
}
},
500,
true
),
/**
* @private
*/
_updateTimestamps() {
_updateTimestamps() {
const isAtBottom = this.isAtBottom();
this.$('.o_mail_timestamp').each(function () {
const date = $(this).data('date');
this.$(".o_mail_timestamp").each(function () {
const date = $(this).data("date");
$(this).text(mailUtils.timeFromNow(date));
});
if (isAtBottom && !this.isAtBottom()) {
@@ -356,7 +359,7 @@ const PublicLivechatView = Widget.extend({
_onClick() {
if (this._selectedMessageID) {
this.unselectMessage();
this.trigger('unselect_message');
this.trigger("unselect_message");
}
},
/**
@@ -371,26 +374,26 @@ const PublicLivechatView = Widget.extend({
* @param {MouseEvent} ev
*/
_onClickMessage(ev) {
$(ev.currentTarget).toggleClass('o_thread_selected_message');
$(ev.currentTarget).toggleClass("o_thread_selected_message");
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickMessageNeedaction(ev) {
const messageID = $(ev.currentTarget).data('message-id');
this.trigger('mark_as_read', messageID);
_onClickMessageNeedaction(ev) {
const messageID = $(ev.currentTarget).data("message-id");
this.trigger("mark_as_read", messageID);
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickMessageNotificationError(ev) {
const messageID = $(ev.currentTarget).data('message-id');
this.do_action('mail.mail_resend_message_action', {
const messageID = $(ev.currentTarget).data("message-id");
this.do_action("mail.mail_resend_message_action", {
additional_context: {
mail_message_to_resend: messageID,
}
},
});
},
/**
@@ -398,20 +401,21 @@ const PublicLivechatView = Widget.extend({
* @param {MouseEvent} ev
*/
_onClickMessageReply(ev) {
this._selectedMessageID = $(ev.currentTarget).data('message-id');
this.$('.o_thread_message').removeClass('o_thread_selected_message');
this.$('.o_thread_message[data-message-id="' + this._selectedMessageID + '"]')
.addClass('o_thread_selected_message');
this.trigger('select_message', this._selectedMessageID);
this._selectedMessageID = $(ev.currentTarget).data("message-id");
this.$(".o_thread_message").removeClass("o_thread_selected_message");
this.$('.o_thread_message[data-message-id="' + this._selectedMessageID + '"]').addClass(
"o_thread_selected_message"
);
this.trigger("select_message", this._selectedMessageID);
ev.stopPropagation();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickMessageStar(ev) {
const messageID = $(ev.currentTarget).data('message-id');
this.trigger('toggle_star_status', messageID);
_onClickMessageStar(ev) {
const messageID = $(ev.currentTarget).data("message-id");
this.trigger("toggle_star_status", messageID);
},
/**
* @private
@@ -419,18 +423,18 @@ const PublicLivechatView = Widget.extend({
*/
_onClickRedirect(ev) {
// ignore inherited branding
if ($(ev.target).data('oe-field') !== undefined) {
if ($(ev.target).data("oe-field") !== undefined) {
return;
}
const id = $(ev.target).data('oe-id');
const id = $(ev.target).data("oe-id");
if (id) {
ev.preventDefault();
const model = $(ev.target).data('oe-model');
const model = $(ev.target).data("oe-model");
let options;
if (model && (model !== 'mail.channel')) {
if (model && model !== "mail.channel") {
options = {
model,
id
id,
};
} else {
options = { channelID: id };
@@ -442,7 +446,7 @@ const PublicLivechatView = Widget.extend({
* @private
*/
_onClickShowMore() {
this.trigger('load_more_messages');
this.trigger("load_more_messages");
},
});
@@ -1,11 +1,11 @@
/** @odoo-module **/
import config from 'web.config';
import { _t, qweb } from 'web.core';
import Widget from 'web.Widget';
import config from "web.config";
import { _t, qweb } from "web.core";
import Widget from "web.Widget";
import {unaccent} from 'web.utils';
import {setCookie} from 'web.utils.cookies';
import { unaccent } from "web.utils";
import { setCookie } from "web.utils.cookies";
/**
* This is the widget that represent windows of livechat in the frontend.
@@ -14,17 +14,17 @@ import {setCookie} from 'web.utils.cookies';
*/
const PublicLivechatWindow = Widget.extend({
FOLD_ANIMATION_DURATION: 200, // duration in ms for (un)fold transition
HEIGHT_OPEN: '400px', // height in px of thread window when open
HEIGHT_FOLDED: '34px', // height, in px, of thread window when folded
template: 'im_livechat.legacy.PublicLivechatWindow',
HEIGHT_OPEN: "400px", // height in px of thread window when open
HEIGHT_FOLDED: "34px", // height, in px, of thread window when folded
template: "im_livechat.legacy.PublicLivechatWindow",
events: {
'click .o_thread_window_close': '_onClickClose',
'click .o_thread_window_header': '_onClickFold',
'click .o_composer_text_field': '_onComposerClick',
'click .o_mail_thread': '_onThreadWindowClicked',
'keydown .o_composer_text_field': '_onKeydown',
'keypress .o_composer_text_field': '_onKeypress',
'input .o_composer_text_field': '_onInput',
"click .o_thread_window_close": "_onClickClose",
"click .o_thread_window_header": "_onClickFold",
"click .o_composer_text_field": "_onComposerClick",
"click .o_mail_thread": "_onThreadWindowClicked",
"keydown .o_composer_text_field": "_onKeydown",
"keypress .o_composer_text_field": "_onKeypress",
"input .o_composer_text_field": "_onInput",
},
/**
* @param {Widget} parent
@@ -42,29 +42,40 @@ const PublicLivechatWindow = Widget.extend({
* @return {Promise}
*/
async start() {
this.$input = this.$('.o_composer_text_field');
this.$header = this.$('.o_thread_window_header');
this.$input = this.$(".o_composer_text_field");
this.$header = this.$(".o_thread_window_header");
// animate the (un)folding of thread windows
this.$el.css({ transition: 'height ' + this.FOLD_ANIMATION_DURATION + 'ms linear' });
this.$el.css({ transition: "height " + this.FOLD_ANIMATION_DURATION + "ms linear" });
if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded) {
this.$el.css('height', this.HEIGHT_FOLDED);
this.$el.css("height", this.HEIGHT_FOLDED);
} else {
this._focusInput();
}
const def = this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.replace(this.$('.o_thread_window_content')).then(() => {
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.$el.on('scroll', this, this._debouncedOnScroll);
});
const def = this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget
.replace(this.$(".o_thread_window_content"))
.then(() => {
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.$el.on(
"scroll",
this,
this._debouncedOnScroll
);
});
await Promise.all([this._super(), def]);
if (this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor) {
this.$('.o_thread_window_header').css('background-color', this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor);
this.$(".o_thread_window_header").css(
"background-color",
this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor
);
}
if (this.messaging.publicLivechatGlobal.livechatButtonView.titleColor) {
this.$('.o_thread_window_header').css('color', this.messaging.publicLivechatGlobal.livechatButtonView.titleColor);
this.$(".o_thread_window_header").css(
"color",
this.messaging.publicLivechatGlobal.livechatButtonView.titleColor
);
}
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -73,10 +84,14 @@ const PublicLivechatWindow = Widget.extend({
* @override
*/
close() {
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';
});
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();
@@ -99,7 +114,9 @@ const PublicLivechatWindow = Widget.extend({
*/
render() {
this.renderHeader();
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.render({ displayLoadMore: false });
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.render({
displayLoadMore: false,
});
},
/**
* Render the header of this thread window.
@@ -109,7 +126,9 @@ const PublicLivechatWindow = Widget.extend({
* @private
*/
renderHeader() {
this.$header.html(qweb.render('im_livechat.legacy.PublicLivechatWindow.HeaderContent', { widget: this }));
this.$header.html(
qweb.render("im_livechat.legacy.PublicLivechatWindow.HeaderContent", { widget: this })
);
},
/**
@@ -126,9 +145,10 @@ const PublicLivechatWindow = Widget.extend({
*/
adjustPosition() {
const cssProps = { bottom: 0 };
cssProps[this.messaging.locale.textDirection === 'rtl' ? 'left' : 'right'] = 0;
cssProps[this.messaging.locale.textDirection === "rtl" ? "left" : "right"] = 0;
if (!config.device.isMobile) {
const margin_dir = _t.database.parameters.direction === "rtl" ? "margin-left" : "margin-right";
const margin_dir =
_t.database.parameters.direction === "rtl" ? "margin-left" : "margin-right";
cssProps[margin_dir] = $.position.scrollbarWidth();
}
this.$el.css(cssProps);
@@ -140,7 +160,9 @@ const PublicLivechatWindow = Widget.extend({
* @param {$.Element} $element
*/
replaceContentWith($element) {
$element.replace(this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.$el);
$element.replace(
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.$el
);
},
/**
* Toggle the fold state of this thread window. Also update the fold state
@@ -158,7 +180,17 @@ const PublicLivechatWindow = Widget.extend({
}
this.messaging.publicLivechatGlobal.publicLivechat.update({ isFolded: folded });
if (this.messaging.publicLivechatGlobal.publicLivechat.operator) {
setCookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()), true), 60 * 60, 'required');
setCookie(
"im_livechat_session",
unaccent(
JSON.stringify(
this.messaging.publicLivechatGlobal.publicLivechat.widget.toData()
),
true
),
60 * 60,
"required"
);
}
this.updateVisualFoldState();
},
@@ -172,7 +204,9 @@ const PublicLivechatWindow = Widget.extend({
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
this._focusInput();
}
const height = this.messaging.publicLivechatGlobal.publicLivechat.isFolded ? this.HEIGHT_FOLDED : this.HEIGHT_OPEN;
const height = this.messaging.publicLivechatGlobal.publicLivechat.isFolded
? this.HEIGHT_FOLDED
: this.HEIGHT_OPEN;
this.$el.css({ height });
},
@@ -188,10 +222,7 @@ const PublicLivechatWindow = Widget.extend({
* Set the focus on the input of the window
*/
_focusInput() {
if (
config.device.touch &&
config.device.size_class <= config.device.SIZES.SM
) {
if (config.device.touch && config.device.size_class <= config.device.SIZES.SM) {
return;
}
this.$input.focus();
@@ -204,7 +235,7 @@ const PublicLivechatWindow = Widget.extend({
* @returns {boolean}
*/
_hasFocus() {
return this.$input.is(':focus');
return this.$input.is(":focus");
},
/**
* Post a message on this thread window, and auto-scroll to the bottom of
@@ -222,7 +253,8 @@ const PublicLivechatWindow = Widget.extend({
if (!this.messaging.publicLivechatGlobal.publicLivechat.operator) {
return;
}
this.messaging.publicLivechatGlobal.publicLivechat.widget.postMessage(messageData)
this.messaging.publicLivechatGlobal.publicLivechat.widget
.postMessage(messageData)
.then(() => {
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
});
@@ -269,7 +301,7 @@ const PublicLivechatWindow = Widget.extend({
* @param {Event} ev
*/
_onComposerClick(ev) {
if ($(ev.target).closest('a, button').length) {
if ($(ev.target).closest("a, button").length) {
return;
}
this._focusInput();
@@ -281,7 +313,9 @@ const PublicLivechatWindow = Widget.extend({
*/
_onInput() {
const isTyping = this.$input.val().length > 0;
this.messaging.publicLivechatGlobal.publicLivechat.widget.setMyselfTyping({ typing: isTyping });
this.messaging.publicLivechatGlobal.publicLivechat.widget.setMyselfTyping({
typing: isTyping,
});
},
/**
* Called when typing something on the composer of this thread window.
@@ -299,7 +333,7 @@ const PublicLivechatWindow = Widget.extend({
attachment_ids: [],
partner_ids: [],
};
this.$input.val('');
this.$input.val("");
if (content) {
this._postMessage(messageData);
}
@@ -1,15 +1,15 @@
/** @odoo-module **/
import { attr, one, Patch } from '@mail/model';
import { attr, one, Patch } from "@mail/model";
Patch({
name: 'Channel',
name: "Channel",
fields: {
anonymous_country: one('Country'),
anonymous_country: one("Country"),
anonymous_name: attr(),
discussSidebarCategory: {
compute() {
if (this.channel_type === 'livechat') {
if (this.channel_type === "livechat") {
return this.messaging.discuss.categoryLivechat;
}
return this._super();
@@ -20,12 +20,16 @@ Patch({
if (!this.thread) {
return;
}
if (this.channel_type === 'livechat' && this.correspondent) {
if (this.channel_type === "livechat" && this.correspondent) {
if (!this.correspondent.is_public && this.correspondent.country) {
return `${this.thread.getMemberName(this.correspondent.persona)} (${this.correspondent.country.name})`;
return `${this.thread.getMemberName(this.correspondent.persona)} (${
this.correspondent.country.name
})`;
}
if (this.anonymous_country) {
return `${this.thread.getMemberName(this.correspondent.persona)} (${this.anonymous_country.name})`;
return `${this.thread.getMemberName(this.correspondent.persona)} (${
this.anonymous_country.name
})`;
}
return this.thread.getMemberName(this.correspondent.persona);
}
@@ -1,14 +1,14 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: 'ChannelPreviewView',
name: "ChannelPreviewView",
fields: {
imageUrl: {
compute() {
if (this.channel.channel_type === 'livechat') {
return '/mail/static/src/img/smiley/avatar.jpg';
if (this.channel.channel_type === "livechat") {
return "/mail/static/src/img/smiley/avatar.jpg";
}
return this._super();
},
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: 'ChatWindow',
name: "ChatWindow",
recordMethods: {
/**
* @override
@@ -12,7 +12,7 @@ Patch({
if (
this.thread &&
this.thread.channel &&
this.thread.channel.channel_type === 'livechat' &&
this.thread.channel.channel_type === "livechat" &&
this.thread.cache.isLoaded &&
this.thread.messages.length === 0
) {
@@ -1,13 +1,17 @@
/** @odoo-module **/
import { clear, Patch } from '@mail/model';
import { clear, Patch } from "@mail/model";
Patch({
name: 'ComposerView',
name: "ComposerView",
fields: {
dropZoneView: {
compute() {
if (this.composer.thread && this.composer.thread.channel && this.composer.thread.channel.channel_type === 'livechat') {
if (
this.composer.thread &&
this.composer.thread.channel &&
this.composer.thread.channel.channel_type === "livechat"
) {
return clear();
}
return this._super();
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { one, Patch } from '@mail/model';
import { one, Patch } from "@mail/model";
Patch({
name: 'Discuss',
name: "Discuss",
recordMethods: {
/**
* @override
@@ -19,9 +19,9 @@ Patch({
/**
* Discuss sidebar category for `livechat` channel threads.
*/
categoryLivechat: one('DiscussSidebarCategory', {
categoryLivechat: one("DiscussSidebarCategory", {
default: {},
inverse: 'discussAsLivechat',
inverse: "discussAsLivechat",
}),
},
});
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { clear, one, Patch } from '@mail/model';
import { clear, one, Patch } from "@mail/model";
Patch({
name: 'DiscussSidebarCategory',
name: "DiscussSidebarCategory",
fields: {
categoryItemsOrderedByLastAction: {
compute() {
@@ -13,9 +13,9 @@ Patch({
return this._super();
},
},
discussAsLivechat: one('Discuss', {
discussAsLivechat: one("Discuss", {
identifying: true,
inverse: 'categoryLivechat',
inverse: "categoryLivechat",
}),
isServerOpen: {
compute() {
@@ -27,7 +27,8 @@ Patch({
return clear();
}
if (this.discussAsLivechat) {
return this.messaging.currentUser.res_users_settings_id.is_discuss_sidebar_category_livechat_open;
return this.messaging.currentUser.res_users_settings_id
.is_discuss_sidebar_category_livechat_open;
}
return this._super();
},
@@ -51,7 +52,7 @@ Patch({
serverStateKey: {
compute() {
if (this.discussAsLivechat) {
return 'is_discuss_sidebar_category_livechat_open';
return "is_discuss_sidebar_category_livechat_open";
}
return this._super();
},
@@ -59,7 +60,7 @@ Patch({
supportedChannelTypes: {
compute() {
if (this.discussAsLivechat) {
return ['livechat'];
return ["livechat"];
}
return this._super();
},
@@ -1,13 +1,13 @@
/** @odoo-module **/
import { clear, Patch } from '@mail/model';
import { clear, Patch } from "@mail/model";
Patch({
name: 'DiscussSidebarCategoryItem',
name: "DiscussSidebarCategoryItem",
fields: {
avatarUrl: {
compute() {
if (this.channel.channel_type === 'livechat') {
if (this.channel.channel_type === "livechat") {
if (this.channel.correspondent && !this.channel.correspondent.is_public) {
return this.channel.correspondent.avatarUrl;
}
@@ -17,7 +17,7 @@ Patch({
},
categoryCounterContribution: {
compute() {
if (this.channel.channel_type === 'livechat') {
if (this.channel.channel_type === "livechat") {
return this.channel.localMessageUnreadCounter > 0 ? 1 : 0;
}
return this._super();
@@ -25,7 +25,7 @@ Patch({
},
counter: {
compute() {
if (this.channel.channel_type === 'livechat') {
if (this.channel.channel_type === "livechat") {
return this.channel.localMessageUnreadCounter;
}
return this._super();
@@ -33,7 +33,7 @@ Patch({
},
hasUnpinCommand: {
compute() {
if (this.channel.channel_type === 'livechat') {
if (this.channel.channel_type === "livechat") {
return !this.channel.localMessageUnreadCounter;
}
return this._super();
@@ -41,7 +41,7 @@ Patch({
},
threadIconView: {
compute() {
if (this.channel.channel_type === 'livechat') {
if (this.channel.channel_type === "livechat") {
return clear();
}
return this._super();
@@ -1,13 +1,17 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: 'Message',
name: "Message",
fields: {
hasReactionIcon: {
compute() {
if (this.originThread && this.originThread.channel && this.originThread.channel.channel_type === 'livechat') {
if (
this.originThread &&
this.originThread.channel &&
this.originThread.channel.channel_type === "livechat"
) {
return false;
}
return this._super();
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { clear, Patch } from '@mail/model';
import { clear, Patch } from "@mail/model";
Patch({
name: 'MessageActionList',
name: "MessageActionList",
fields: {
actionReplyTo: {
compute() {
@@ -11,12 +11,12 @@ Patch({
this.message &&
this.message.originThread &&
this.message.originThread.channel &&
this.message.originThread.channel.channel_type === 'livechat'
this.message.originThread.channel.channel_type === "livechat"
) {
return clear();
}
return this._super();
}
},
},
},
});
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: "MessageView",
@@ -1,15 +1,15 @@
/** @odoo-module **/
import { many, Patch } from '@mail/model';
import { many, Patch } from "@mail/model";
Patch({
name: 'Messaging',
name: "Messaging",
fields: {
/**
* All pinned livechats that are known.
*/
pinnedLivechats: many('Thread', {
inverse: 'messagingAsPinnedLivechat',
pinnedLivechats: many("Thread", {
inverse: "messagingAsPinnedLivechat",
readonly: true,
}),
},
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { insert, Patch } from '@mail/model';
import { insert, Patch } from "@mail/model";
Patch({
name: 'MessagingInitializer',
name: "MessagingInitializer",
recordMethods: {
/**
* @override
@@ -13,9 +13,9 @@ Patch({
this._super();
this.messaging.update({
commands: insert({
channel_types: ['livechat'],
channel_types: ["livechat"],
help: this.env._t("See 15 last visited pages"),
methodName: 'execute_command_history',
methodName: "execute_command_history",
name: "history",
}),
});
@@ -1,19 +1,22 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: 'MobileMessagingNavbarView',
name: "MobileMessagingNavbarView",
fields: {
tabs: {
compute() {
const res = this._super();
if (this.messaging.pinnedLivechats.length > 0) {
return [...res, {
icon: 'fa fa-comments',
id: 'livechat',
label: this.env._t("Livechat"),
}];
return [
...res,
{
icon: "fa fa-comments",
id: "livechat",
label: this.env._t("Livechat"),
},
];
}
return res;
},
@@ -1,16 +1,15 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: 'NotificationListView',
name: "NotificationListView",
fields: {
filteredChannels: {
compute() {
if (this.filter === 'livechat') {
return this.messaging.models['Channel'].all(channel =>
channel.channel_type === 'livechat' &&
channel.thread.isPinned
if (this.filter === "livechat") {
return this.messaging.models["Channel"].all(
(channel) => channel.channel_type === "livechat" && channel.thread.isPinned
);
}
return this._super();
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { attr, Patch } from '@mail/model';
import { attr, Patch } from "@mail/model";
Patch({
name: 'Partner',
name: "Partner",
fields: {
/**
* States the specific name of this partner in the context of livechat.
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { attr, Patch } from '@mail/model';
import { attr, Patch } from "@mail/model";
Patch({
name: 'res.users.settings',
name: "res.users.settings",
fields: {
is_discuss_sidebar_category_livechat_open: attr({
default: true,
+26 -10
View File
@@ -1,18 +1,29 @@
/** @odoo-module **/
import { clear, one, Patch } from '@mail/model';
import { clear, one, Patch } from "@mail/model";
Patch({
name: 'Thread',
name: "Thread",
recordMethods: {
/**
* @override
*/
getMemberName(persona) {
if (this.channel && this.channel.channel_type === 'livechat' && persona.partner && persona.partner.user_livechat_username) {
if (
this.channel &&
this.channel.channel_type === "livechat" &&
persona.partner &&
persona.partner.user_livechat_username
) {
return persona.partner.user_livechat_username;
}
if (this.channel && this.channel.channel_type === 'livechat' && persona.partner && persona.partner.is_public && this.channel.anonymous_name) {
if (
this.channel &&
this.channel.channel_type === "livechat" &&
persona.partner &&
persona.partner.is_public &&
this.channel.anonymous_name
) {
return this.channel.anonymous_name;
}
return this._super(persona);
@@ -21,7 +32,7 @@ Patch({
fields: {
hasInviteFeature: {
compute() {
if (this.channel && this.channel.channel_type === 'livechat') {
if (this.channel && this.channel.channel_type === "livechat") {
return true;
}
return this._super();
@@ -29,7 +40,7 @@ Patch({
},
hasMemberListFeature: {
compute() {
if (this.channel && this.channel.channel_type === 'livechat') {
if (this.channel && this.channel.channel_type === "livechat") {
return true;
}
return this._super();
@@ -37,7 +48,7 @@ Patch({
},
isChatChannel: {
compute() {
if (this.channel && this.channel.channel_type === 'livechat') {
if (this.channel && this.channel.channel_type === "livechat") {
return true;
}
return this._super();
@@ -46,14 +57,19 @@ Patch({
/**
* If set, current thread is a livechat.
*/
messagingAsPinnedLivechat: one('Messaging', {
messagingAsPinnedLivechat: one("Messaging", {
compute() {
if (!this.messaging || !this.channel || this.channel.channel_type !== 'livechat' || !this.isPinned) {
if (
!this.messaging ||
!this.channel ||
this.channel.channel_type !== "livechat" ||
!this.isPinned
) {
return clear();
}
return this.messaging;
},
inverse: 'pinnedLivechats',
inverse: "pinnedLivechats",
}),
},
});
@@ -1,15 +1,15 @@
/** @odoo-module **/
import { Patch } from '@mail/model';
import { Patch } from "@mail/model";
Patch({
name: 'ThreadIconView',
name: "ThreadIconView",
fields: {
threadTypingIconView: {
compute() {
if (
this.thread.channel &&
this.thread.channel.channel_type === 'livechat' &&
this.thread.channel.channel_type === "livechat" &&
this.thread.orderedOtherTypingMembers.length > 0
) {
return {};
+13 -11
View File
@@ -1,24 +1,26 @@
/** @odoo-module **/
import { publicLivechatService } from '@im_livechat/services/public_livechat_service';
import { isAvailable, options, serverUrl } from '@im_livechat/livechat_data';
import { publicLivechatService } from "@im_livechat/services/public_livechat_service";
import { isAvailable, options, serverUrl } from "@im_livechat/livechat_data";
import { messagingService } from '@mail/services/messaging_service';
import { makeMessagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env';
import { messagingService } from "@mail/services/messaging_service";
import { makeMessagingToLegacyEnv } from "@mail/utils/make_messaging_to_legacy_env";
import { registry } from '@web/core/registry';
import { registry } from "@web/core/registry";
const messagingValuesService = {
start() {
return {
publicLivechatGlobal: { isAvailable, options, serverUrl },
};
}
},
};
const serviceRegistry = registry.category('services');
serviceRegistry.add('messaging', messagingService);
serviceRegistry.add('messagingValues', messagingValuesService);
serviceRegistry.add('public_livechat_service', publicLivechatService);
const serviceRegistry = registry.category("services");
serviceRegistry.add("messaging", messagingService);
serviceRegistry.add("messagingValues", messagingValuesService);
serviceRegistry.add("public_livechat_service", publicLivechatService);
registry.category('wowlToLegacyServiceMappers').add('make_messaging_to_legacy_env', makeMessagingToLegacyEnv);
registry
.category("wowlToLegacyServiceMappers")
.add("make_messaging_to_legacy_env", makeMessagingToLegacyEnv);
@@ -1,7 +1,6 @@
odoo.define('web.session', function (require) {
const Session = require('web.Session');
const { serverUrl } = require('@im_livechat/livechat_data');
odoo.define("web.session", function (require) {
const Session = require("web.Session");
const { serverUrl } = require("@im_livechat/livechat_data");
return new Session(undefined, serverUrl, { use_cors: true });
});
@@ -1,12 +1,12 @@
/** @odoo-module **/
import { attr, one, clear, increment, Model } from '@mail/model';
import { attr, one, clear, increment, Model } from "@mail/model";
import { qweb } from 'web.core';
import { Markup } from 'web.utils';
import { qweb } from "web.core";
import { Markup } from "web.utils";
Model({
name: 'Chatbot',
name: "Chatbot",
recordMethods: {
/**
* Add message posted by the bot into the conversation.
@@ -18,8 +18,13 @@ Model({
addMessage(message, options) {
message.body = Markup(message.body);
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(message, options);
if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.isAtBottom()) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() });
if (
this.messaging.publicLivechatGlobal.publicLivechat.isFolded ||
!this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.isAtBottom()
) {
this.messaging.publicLivechatGlobal.publicLivechat.update({
unreadCounter: increment(),
});
}
if (!options || !options.skipRenderMessages) {
@@ -40,15 +45,24 @@ Model({
// handle the display
return;
}
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_composer_text_field').addClass('d-none');
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_end').show();
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_restart').one('click', this.messaging.publicLivechatGlobal.livechatButtonView.onChatbotRestartScript);
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_composer_text_field")
.addClass("d-none");
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_livechat_chatbot_end")
.show();
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_livechat_chatbot_restart")
.one(
"click",
this.messaging.publicLivechatGlobal.livechatButtonView.onChatbotRestartScript
);
},
onKeydownInput() {
if (
this.currentStep &&
this.currentStep.data &&
this.currentStep.data.chatbot_step_type === 'free_input_multi'
this.currentStep.data.chatbot_step_type === "free_input_multi"
) {
this.debouncedAwaitUserInput();
}
@@ -75,14 +89,14 @@ Model({
}
const postedWelcomeMessages = await this.messaging.rpc({
route: '/chatbot/post_welcome_steps',
route: "/chatbot/post_welcome_steps",
params: {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
chatbot_script_id: this.scriptId,
},
});
const welcomeMessagesIds = welcomeMessages.map(welcomeMessage => welcomeMessage.id);
const welcomeMessagesIds = welcomeMessages.map((welcomeMessage) => welcomeMessage.id);
this.messaging.publicLivechatGlobal.update({
messages: this.messaging.publicLivechatGlobal.messages.filter((message) => {
!welcomeMessagesIds.includes(message.id);
@@ -128,7 +142,7 @@ Model({
if (this.shouldEndScript) {
this.endScript();
} else if (
this.currentStep.data.chatbot_step_type === 'forward_operator' &&
this.currentStep.data.chatbot_step_type === "forward_operator" &&
this.currentStep.data.chatbot_operator_found
) {
this.messaging.publicLivechatGlobal.chatWindow.enableInput();
@@ -137,17 +151,14 @@ Model({
// user has already typed a message in -> trigger next step
this.setIsTyping();
this.update({
nextStepTimeout: setTimeout(
this.triggerNextStep,
this.messageDelay,
),
nextStepTimeout: setTimeout(this.triggerNextStep, this.messageDelay),
});
} else {
this.messaging.publicLivechatGlobal.chatWindow.enableInput();
}
} else {
let triggerNextStep = true;
if (this.currentStep.data.chatbot_step_type === 'question_selection') {
if (this.currentStep.data.chatbot_step_type === "question_selection") {
if (!this.messaging.publicLivechatGlobal.isLastMessageFromCustomer) {
// if there is no last message or if the last message is from the bot
// -> don't trigger the next step, we are waiting for the user to pick an option
@@ -157,7 +168,11 @@ Model({
if (triggerNextStep) {
let nextStepDelay = this.messageDelay;
if (this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_typing').length !== 0) {
if (
this.messaging.publicLivechatGlobal.chatWindow.widget.$(
".o_livechat_chatbot_typing"
).length !== 0
) {
// special case where we already have a "is typing" message displayed
// can happen when the previous step did not trigger any message posted from the bot
// e.g: previous step was "forward_operator" and no-one is available
@@ -168,16 +183,15 @@ Model({
}
this.update({
nextStepTimeout: setTimeout(
this.triggerNextStep,
nextStepDelay,
),
nextStepTimeout: setTimeout(this.triggerNextStep, nextStepDelay),
});
}
}
if (!this.hasRestartButton) {
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_main_restart').addClass('d-none');
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_livechat_chatbot_main_restart")
.addClass("d-none");
}
},
/**
@@ -195,7 +209,10 @@ Model({
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.sessionCookieKey) {
if (
key.startsWith("im_livechat.chatbot.state.uuid_") &&
key !== this.sessionCookieKey
) {
browserLocalStorage.removeItem(key);
}
}
@@ -213,10 +230,14 @@ Model({
* anyway in that case, since it's stored into a cookie).
*/
saveSession() {
localStorage.setItem('im_livechat.chatbot.state.uuid_' + this.messaging.publicLivechatGlobal.publicLivechat.uuid, JSON.stringify({
'_chatbot': this.data,
'_chatbotCurrentStep': this.currentStep.data,
}));
localStorage.setItem(
"im_livechat.chatbot.state.uuid_" +
this.messaging.publicLivechatGlobal.publicLivechat.uuid,
JSON.stringify({
_chatbot: this.data,
_chatbotCurrentStep: this.currentStep.data,
})
);
},
/**
* Adds a small "is typing" animation into the chat window.
@@ -225,28 +246,32 @@ Model({
*/
setIsTyping(isWelcomeMessage = false) {
if (this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout) {
clearTimeout(this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout);
clearTimeout(
this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout
);
}
this.messaging.publicLivechatGlobal.chatWindow.disableInput('');
this.messaging.publicLivechatGlobal.chatWindow.disableInput("");
this.messaging.publicLivechatGlobal.livechatButtonView.update({
isTypingTimeout: setTimeout(
() => {
if (!this.messaging.publicLivechatGlobal.chatWindow || !this.messaging.publicLivechatGlobal.chatWindow.exists()) {
return;
}
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_mail_thread_content').append(
$(qweb.render('im_livechat.legacy.chatbot.is_typing_message', {
'chatbotImageSrc': `/im_livechat/operator/${
this.messaging.publicLivechatGlobal.publicLivechat.operator.id
}/avatar`,
'chatbotName': this.name,
'isWelcomeMessage': isWelcomeMessage,
}))
isTypingTimeout: setTimeout(() => {
if (
!this.messaging.publicLivechatGlobal.chatWindow ||
!this.messaging.publicLivechatGlobal.chatWindow.exists()
) {
return;
}
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_mail_thread_content")
.append(
$(
qweb.render("im_livechat.legacy.chatbot.is_typing_message", {
chatbotImageSrc: `/im_livechat/operator/${this.messaging.publicLivechatGlobal.publicLivechat.operator.id}/avatar`,
chatbotName: this.name,
isWelcomeMessage: isWelcomeMessage,
})
)
);
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
},
this.messageDelay / 3,
),
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
}, this.messageDelay / 3),
});
},
/**
@@ -254,14 +279,17 @@ Model({
* This will receive the next step and call step processing.
*/
async triggerNextStep() {
if (!this.messaging.publicLivechatGlobal.chatWindow || !this.messaging.publicLivechatGlobal.chatWindow.exists()) {
if (
!this.messaging.publicLivechatGlobal.chatWindow ||
!this.messaging.publicLivechatGlobal.chatWindow.exists()
) {
return;
}
let triggerNextStep = true;
if (
this.currentStep &&
this.currentStep.data &&
this.currentStep.data.chatbot_step_type === 'question_email'
this.currentStep.data.chatbot_step_type === "question_email"
) {
triggerNextStep = await this.validateEmail();
}
@@ -271,7 +299,7 @@ Model({
}
const nextStep = await this.messaging.rpc({
route: '/chatbot/step/trigger',
route: "/chatbot/step/trigger",
params: {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
chatbot_script_id: this.scriptId,
@@ -307,8 +335,8 @@ Model({
* Returns a boolean stating whether the email was valid or not.
*/
async validateEmail() {
let emailValidResult = await this.messaging.rpc({
route: '/chatbot/step/validate_email',
const emailValidResult = await this.messaging.rpc({
route: "/chatbot/step/validate_email",
params: { channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid },
});
@@ -347,10 +375,7 @@ Model({
} else {
this.setIsTyping();
this.update({
nextStepTimeout: setTimeout(
this.triggerNextStep,
this.messageDelay,
)
nextStepTimeout: setTimeout(this.triggerNextStep, this.messageDelay),
});
}
}
@@ -367,31 +392,25 @@ Model({
if (this.messaging.publicLivechatGlobal.isTestChatbot) {
return this.messaging.publicLivechatGlobal.testChatbotData;
}
if (this.state === 'init') {
if (this.state === "init") {
return this.messaging.publicLivechatGlobal.rule.chatbot;
}
if (this.state === 'welcome') {
if (this.state === "welcome") {
return this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot;
}
if (
this.state === 'restore_session' &&
this.localStorageState
) {
if (this.state === "restore_session" && this.localStorageState) {
return this.localStorageState._chatbot;
}
return clear();
},
}),
currentStep: one('ChatbotStep', {
inverse: 'chabotOwner',
currentStep: one("ChatbotStep", {
inverse: "chabotOwner",
}),
debouncedAwaitUserInput: attr({
compute() {
// debounced to let the user type several sentences, see 'Chatbot/awaitUserInput' for details
return _.debounce(
this.awaitUserInput,
this.awaitUserInputDebounceTime,
);
return _.debounce(this.awaitUserInput, this.awaitUserInputDebounceTime);
},
}),
hasRestartButton: attr({
@@ -411,10 +430,8 @@ Model({
}
return Boolean(
!this.currentStep ||
(
this.currentStep.data.chatbot_step_type !== 'forward_operator' ||
this.currentStep.data.chatbot_step_type !== "forward_operator" ||
!this.currentStep.data.chatbot_operator_found
)
);
},
default: false,
@@ -424,13 +441,19 @@ Model({
if (this.messaging.publicLivechatGlobal.isTestChatbot) {
return true;
}
if (this.messaging.publicLivechatGlobal.rule && this.messaging.publicLivechatGlobal.rule.chatbot) {
if (
this.messaging.publicLivechatGlobal.rule &&
this.messaging.publicLivechatGlobal.rule.chatbot
) {
return true;
}
if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
if (
this.messaging.publicLivechatGlobal.livechatInit &&
this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot
) {
return true;
}
if (this.state === 'welcome') {
if (this.state === "welcome") {
return true;
}
if (this.localStorageState) {
@@ -446,10 +469,10 @@ Model({
return clear();
}
return [
'question_phone',
'question_email',
'free_input_single',
'free_input_multi',
"question_phone",
"question_email",
"free_input_single",
"free_input_multi",
].includes(this.currentStep.data.chatbot_step_type);
},
default: false,
@@ -492,9 +515,9 @@ Model({
},
default: 3500, // in milliseconds
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
identifying: true,
inverse: 'chatbot',
inverse: "chatbot",
}),
scriptId: attr({
compute() {
@@ -510,7 +533,10 @@ Model({
if (!this.messaging.publicLivechatGlobal.sessionCookie) {
return clear();
}
return 'im_livechat.chatbot.state.uuid_' + JSON.parse(this.messaging.publicLivechatGlobal.sessionCookie).uuid;
return (
"im_livechat.chatbot.state.uuid_" +
JSON.parse(this.messaging.publicLivechatGlobal.sessionCookie).uuid
);
},
}),
shouldEndScript: attr({
@@ -532,21 +558,26 @@ Model({
if (this.currentStep.data.conversation_closed) {
return true;
}
if (this.currentStep.data.chatbot_step_is_last &&
(this.currentStep.data.chatbot_step_type !== 'forward_operator' ||
!this.currentStep.data.chatbot_operator_found)
if (
this.currentStep.data.chatbot_step_is_last &&
(this.currentStep.data.chatbot_step_type !== "forward_operator" ||
!this.currentStep.data.chatbot_operator_found)
) {
if (this.currentStep.data.chatbot_step_type === 'question_email'
&& !this.currentStep.data.is_email_valid
if (
this.currentStep.data.chatbot_step_type === "question_email" &&
!this.currentStep.data.is_email_valid
) {
// email is not (yet) valid, let the user answer / try again
return false;
} else if (
(this.isExpectingUserInput ||
this.currentStep.data.chatbot_step_type === 'question_selection') &&
this.currentStep.data.chatbot_step_type === "question_selection") &&
this.messaging.publicLivechatGlobal.messages.length !== 0
) {
if (this.messaging.publicLivechatGlobal.lastMessage.authorId !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id) {
if (
this.messaging.publicLivechatGlobal.lastMessage.authorId !==
this.messaging.publicLivechatGlobal.publicLivechat.operator.id
) {
// we are on the last step of the script, expect a user input and the user has
// already answered
// -> end the script
@@ -564,11 +595,17 @@ Model({
}),
state: attr({
compute() {
if (this.messaging.publicLivechatGlobal.rule && !!this.messaging.publicLivechatGlobal.rule.chatbot) {
return 'init';
if (
this.messaging.publicLivechatGlobal.rule &&
!!this.messaging.publicLivechatGlobal.rule.chatbot
) {
return "init";
}
if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
return 'welcome';
if (
this.messaging.publicLivechatGlobal.livechatInit &&
this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot
) {
return "welcome";
}
if (
!this.messaging.publicLivechatGlobal.rule &&
@@ -577,7 +614,7 @@ Model({
this.sessionCookieKey &&
localStorage.getItem(this.sessionCookieKey)
) {
return 'restore_session';
return "restore_session";
}
return clear();
},
@@ -1,13 +1,13 @@
/** @odoo-module **/
import { attr, one, Model } from '@mail/model';
import { attr, one, Model } from "@mail/model";
Model({
name: 'ChatbotStep',
name: "ChatbotStep",
fields: {
chabotOwner: one('Chatbot', {
chabotOwner: one("Chatbot", {
identifying: true,
inverse: 'currentStep',
inverse: "currentStep",
}),
data: attr(),
},
@@ -1,14 +1,16 @@
/** @odoo-module **/
import { attr, clear, one, Model } from '@mail/model';
import { attr, clear, one, Model } from "@mail/model";
import {getCookie, deleteCookie} from 'web.utils.cookies';
import { getCookie, deleteCookie } from "web.utils.cookies";
Model({
name: 'LivechatButtonView',
name: "LivechatButtonView",
lifecycleHooks: {
_created() {
this.update({ widget: this.env.services.public_livechat_service.mountLivechatButton() });
this.update({
widget: this.env.services.public_livechat_service.mountLivechatButton(),
});
},
_willDelete() {
this.widget.destroy();
@@ -20,19 +22,27 @@ Model({
* @param {Object} [options={}]
*/
addMessage(data, options) {
const hasAlreadyMessage = _.some(this.messaging.publicLivechatGlobal.messages, function (msg) {
return data.id === msg.id;
});
const hasAlreadyMessage = _.some(
this.messaging.publicLivechatGlobal.messages,
function (msg) {
return data.id === msg.id;
}
);
if (hasAlreadyMessage) {
return;
}
const message = this.messaging.models['PublicLivechatMessage'].insert({
const message = this.messaging.models["PublicLivechatMessage"].insert({
data,
id: data.id,
});
if (this.messaging.publicLivechatGlobal.publicLivechat && this.messaging.publicLivechatGlobal.publicLivechat.widget) {
this.messaging.publicLivechatGlobal.publicLivechat.widget.addMessage(message.widget);
if (
this.messaging.publicLivechatGlobal.publicLivechat &&
this.messaging.publicLivechatGlobal.publicLivechat.widget
) {
this.messaging.publicLivechatGlobal.publicLivechat.widget.addMessage(
message.widget
);
}
if (options && options.prepend) {
@@ -46,7 +56,9 @@ Model({
}
},
askFeedback() {
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_thread_composer input').prop('disabled', true);
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_thread_composer input")
.prop("disabled", true);
this.messaging.publicLivechatGlobal.update({ feedbackView: {} });
/**
* When we enter the "ask feedback" process of the chat, we hide some elements that become
@@ -59,19 +71,28 @@ Model({
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.conversation_closed = true;
this.messaging.publicLivechatGlobal.chatbot.saveSession();
}
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_main_restart').addClass('d-none');
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_end').hide();
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_composer_text_field')
.removeClass('d-none')
.val('');
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_livechat_chatbot_main_restart")
.addClass("d-none");
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_livechat_chatbot_end")
.hide();
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_composer_text_field")
.removeClass("d-none")
.val("");
},
/**
* Restart the script and then trigger the "next step" (which will be the first of the script
* in this case).
*/
async onChatbotRestartScript(ev) {
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_composer_text_field').removeClass('d-none');
this.messaging.publicLivechatGlobal.chatWindow.widget.$('.o_livechat_chatbot_end').hide();
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_composer_text_field")
.removeClass("d-none");
this.messaging.publicLivechatGlobal.chatWindow.widget
.$(".o_livechat_chatbot_end")
.hide();
if (this.messaging.publicLivechatGlobal.chatbot.nextStepTimeout) {
clearTimeout(this.messaging.publicLivechatGlobal.chatbot.nextStepTimeout);
@@ -82,7 +103,7 @@ Model({
}
const postedMessage = await this.messaging.rpc({
route: '/chatbot/restart',
route: "/chatbot/restart",
params: {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId,
@@ -98,24 +119,27 @@ Model({
this.messaging.publicLivechatGlobal.chatbot.update({
nextStepTimeout: setTimeout(
this.messaging.publicLivechatGlobal.chatbot.triggerNextStep,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.messageDelay
),
});
},
closeChat() {
this.messaging.publicLivechatGlobal.update({ chatWindow: clear() });
deleteCookie('im_livechat_session');
deleteCookie("im_livechat_session");
},
/**
* Called when the visitor leaves the livechat chatter the first time (first click on X button)
* this will deactivate the mail_channel, notify operator that visitor has left the channel.
*/
leaveSession() {
const cookie = getCookie('im_livechat_session');
const cookie = getCookie("im_livechat_session");
if (cookie) {
const channel = JSON.parse(cookie);
this.messaging.rpc({ route: '/im_livechat/visitor_leave_session', params: { uuid: channel.uuid } });
deleteCookie('im_livechat_session');
this.messaging.rpc({
route: "/im_livechat/visitor_leave_session",
params: { uuid: channel.uuid },
});
deleteCookie("im_livechat_session");
}
},
openChat() {
@@ -127,7 +151,7 @@ Model({
},
async openChatWindow() {
this.messaging.publicLivechatGlobal.update({ chatWindow: {} });
await this.messaging.publicLivechatGlobal.chatWindow.widget.appendTo($('body'));
await this.messaging.publicLivechatGlobal.chatWindow.widget.appendTo($("body"));
this.messaging.publicLivechatGlobal.chatWindow.widget.adjustPosition();
this.widget.$el.hide();
this._openChatWindowChatbot();
@@ -156,27 +180,30 @@ Model({
this.addMessage(m);
}
this.openChat();
} else if (!this.messaging.device.isSmall && this.messaging.publicLivechatGlobal.rule.action === 'auto_popup') {
const autoPopupCookie = getCookie('im_livechat_auto_popup');
} else if (
!this.messaging.device.isSmall &&
this.messaging.publicLivechatGlobal.rule.action === "auto_popup"
) {
const autoPopupCookie = getCookie("im_livechat_auto_popup");
if (!autoPopupCookie || JSON.parse(autoPopupCookie)) {
this.update({
autoOpenChatTimeout: setTimeout(
this.openChat,
this.messaging.publicLivechatGlobal.rule.auto_popup_timer * 1000,
this.messaging.publicLivechatGlobal.rule.auto_popup_timer * 1000
),
});
}
}
if (this.buttonBackgroundColor) {
this.widget.$el.css('background-color', this.buttonBackgroundColor);
this.widget.$el.css("background-color", this.buttonBackgroundColor);
}
if (this.buttonTextColor) {
this.widget.$el.css('color', this.buttonTextColor);
this.widget.$el.css("color", this.buttonTextColor);
}
// If website_event_track installed, put the livechat banner above the PWA banner.
const pwaBannerHeight = $('.o_pwa_install_banner').outerHeight(true);
const pwaBannerHeight = $(".o_pwa_install_banner").outerHeight(true);
if (pwaBannerHeight) {
this.widget.$el.css('bottom', pwaBannerHeight + 'px');
this.widget.$el.css("bottom", pwaBannerHeight + "px");
}
},
/**
@@ -186,7 +213,7 @@ Model({
if (this.isOpeningChat) {
return;
}
const cookie = getCookie('im_livechat_session');
const cookie = getCookie("im_livechat_session");
let def;
this.update({ isOpeningChat: true });
clearTimeout(this.autoOpenChatTimeout);
@@ -195,19 +222,24 @@ Model({
} else {
// re-initialize messages cache
this.messaging.publicLivechatGlobal.update({ messages: clear() });
def = this.messaging.rpc({
route: '/im_livechat/get_session',
params: {
...this.widget._prepareGetSessionParameters(),
persisted: false,
def = this.messaging.rpc(
{
route: "/im_livechat/get_session",
params: {
...this.widget._prepareGetSessionParameters(),
persisted: false,
},
},
}, { silent: true });
{ silent: true }
);
}
def.then((livechatData) => {
if (!livechatData || !livechatData.operator_pid) {
try {
this.widget.displayNotification({
message: this.env._t("No available collaborator, please try again later."),
message: this.env._t(
"No available collaborator, please try again later."
),
sticky: true,
});
} catch {
@@ -218,7 +250,9 @@ Model({
* lib at the moment because they use bootstrap toast and
* we don't want to include boostrap in external lib.
*/
console.warn(this.env._t("No available collaborator, please try again later."));
console.warn(
this.env._t("No available collaborator, please try again later.")
);
}
} else {
this.messaging.publicLivechatGlobal.update({
@@ -232,11 +266,13 @@ Model({
this.messaging.publicLivechatGlobal.publicLivechat.updateSessionCookie();
});
}
}).then(() => {
this.update({ isOpeningChat: false });
}).guardedCatch(() => {
this.update({ isOpeningChat: false });
});
})
.then(() => {
this.update({ isOpeningChat: false });
})
.guardedCatch(() => {
this.update({ isOpeningChat: false });
});
},
/**
* Resuming the chatbot script if we are currently running one.
@@ -250,7 +286,7 @@ Model({
* @override
*/
_openChatWindowChatbot() {
window.addEventListener('resize', () => {
window.addEventListener("resize", () => {
if (this.messaging.publicLivechatGlobal.chatWindow) {
this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
}
@@ -270,10 +306,15 @@ Model({
* @param {Object} message
*/
async _sendMessage(message) {
this.messaging.publicLivechatGlobal.publicLivechat.widget._notifyMyselfTyping({ typing: false });
this.messaging.publicLivechatGlobal.publicLivechat.widget._notifyMyselfTyping({
typing: false,
});
const messageId = await this.messaging.rpc({
route: '/mail/chat_post',
params: { uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid, message_content: message.content },
route: "/mail/chat_post",
params: {
uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
message_content: message.content,
},
});
if (!messageId) {
try {
@@ -309,18 +350,23 @@ Model({
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
if (
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_operator_found
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
.chatbot_step_type === "forward_operator" &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
.chatbot_operator_found
) {
return; // operator has taken over the conversation, let them speak
} else if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
} else if (
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
.chatbot_step_type === "free_input_multi"
) {
this.messaging.publicLivechatGlobal.chatbot.debouncedAwaitUserInput();
} else if (!this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) {
this.messaging.publicLivechatGlobal.chatbot.setIsTyping();
this.messaging.publicLivechatGlobal.chatbot.update({
nextStepTimeout: setTimeout(
this.messaging.publicLivechatGlobal.chatbot.triggerNextStep,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.messageDelay
),
});
} else {
@@ -420,7 +466,7 @@ Model({
}
return this.env._t("Ask something ...");
},
default: '',
default: "",
}),
isOpenChatDebounced: attr({
compute() {
@@ -440,9 +486,9 @@ Model({
return _.debounce(this._openChat, 200, true);
},
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
identifying: true,
inverse: 'livechatButtonView',
inverse: "livechatButtonView",
}),
serverUrl: attr({
compute() {
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { attr, Model } from '@mail/model';
import { attr, Model } from "@mail/model";
Model({
name: 'LivechatOperator',
name: "LivechatOperator",
fields: {
id: attr({
identifying: true,
@@ -1,11 +1,11 @@
/** @odoo-module **/
import { one, Patch } from '@mail/model';
import { one, Patch } from "@mail/model";
Patch({
name: 'Messaging',
name: "Messaging",
fields: {
publicLivechatGlobal: one('PublicLivechatGlobal', {
publicLivechatGlobal: one("PublicLivechatGlobal", {
isCausal: true,
}),
},
@@ -1,14 +1,14 @@
/** @odoo-module **/
import PublicLivechat from '@im_livechat/legacy/models/public_livechat';
import PublicLivechat from "@im_livechat/legacy/models/public_livechat";
import { attr, clear, one, Model } from '@mail/model';
import { attr, clear, one, Model } from "@mail/model";
import { unaccent } from 'web.utils';
import { deleteCookie, setCookie } from 'web.utils.cookies';
import { unaccent } from "web.utils";
import { deleteCookie, setCookie } from "web.utils.cookies";
Model({
name: 'PublicLivechat',
name: "PublicLivechat",
lifecycleHooks: {
_created() {
this.update({
@@ -75,9 +75,9 @@ Model({
return false;
},
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
identifying: true,
inverse: 'publicLivechat',
inverse: "publicLivechat",
}),
name: attr({
compute() {
@@ -87,7 +87,7 @@ Model({
return this.data.name;
},
}),
operator: one('LivechatOperator', {
operator: one("LivechatOperator", {
compute() {
if (!this.data) {
return clear();
@@ -109,7 +109,7 @@ Model({
if (!this.data) {
return clear();
}
return this.data.status || '';
return this.data.status || "";
},
}),
// amount of messages that have not yet been read on this chat
@@ -1,23 +1,23 @@
/** @odoo-module **/
import Feedback from '@im_livechat/legacy/widgets/feedback/feedback';
import Feedback from "@im_livechat/legacy/widgets/feedback/feedback";
import { attr, one, Model } from '@mail/model';
import { attr, one, Model } from "@mail/model";
Model({
name: 'PublicLivechatFeedbackView',
name: "PublicLivechatFeedbackView",
lifecycleHooks: {
_created() {
this.update({
widget: new Feedback(
this.messaging.publicLivechatGlobal.livechatButtonView.widget,
this.messaging,
this.messaging.publicLivechatGlobal.publicLivechat.widget,
this.messaging.publicLivechatGlobal.publicLivechat.widget
),
});
this.messaging.publicLivechatGlobal.chatWindow.widget.replaceContentWith(this.widget);
this.widget.on('feedback_sent', null, this._onFeedbackSent);
this.widget.on('send_message', null, this._onSendMessage);
this.widget.on("feedback_sent", null, this._onFeedbackSent);
this.widget.on("send_message", null, this._onSendMessage);
},
_willDelete() {
this.widget.destroy();
@@ -32,9 +32,9 @@ Model({
},
},
fields: {
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
identifying: true,
inverse: 'feedbackView',
inverse: "feedbackView",
}),
widget: attr(),
},
@@ -1,17 +1,17 @@
/** @odoo-module **/
import { attr, clear, many, one, Model } from '@mail/model';
import { attr, clear, many, one, Model } from "@mail/model";
import { qweb } from 'web.core';
import { Markup } from 'web.utils';
import {getCookie, setCookie, deleteCookie} from 'web.utils.cookies';
import { qweb } from "web.core";
import { Markup } from "web.utils";
import { getCookie, setCookie, deleteCookie } from "web.utils.cookies";
Model({
name: 'PublicLivechatGlobal',
name: "PublicLivechatGlobal",
lifecycleHooks: {
_created() {
// History tracking
const page = window.location.href.replace(/^.*\/\/[^/]+/, '');
const page = window.location.href.replace(/^.*\/\/[^/]+/, "");
const pageHistory = getCookie(this.LIVECHAT_COOKIE_HISTORY);
let urlHistory = [];
if (pageHistory) {
@@ -22,7 +22,12 @@ Model({
while (urlHistory.length > this.HISTORY_LIMIT) {
urlHistory.shift();
}
setCookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24, 'optional'); // 1 day cookie
setCookie(
this.LIVECHAT_COOKIE_HISTORY,
JSON.stringify(urlHistory),
60 * 60 * 24,
"optional"
); // 1 day cookie
}
if (this.isAvailable) {
this.willStart();
@@ -31,7 +36,7 @@ Model({
},
recordMethods: {
async loadQWebTemplate() {
const templates = await this.messaging.rpc({ route: '/im_livechat/load_templates' });
const templates = await this.messaging.rpc({ route: "/im_livechat/load_templates" });
for (const template of templates) {
qweb.add_template(template);
}
@@ -42,12 +47,12 @@ Model({
await this._willStartChatbot();
},
async _willStart() {
const strCookie = getCookie('im_livechat_session');
const strCookie = getCookie("im_livechat_session");
const isSessionCookieAvailable = Boolean(strCookie);
const cookie = JSON.parse(strCookie || '{}');
const cookie = JSON.parse(strCookie || "{}");
if (cookie.id) {
const history = await this.messaging.rpc({
route: '/mail/chat_history',
route: "/mail/chat_history",
params: { uuid: cookie.uuid, limit: 100 },
});
history.reverse();
@@ -60,7 +65,7 @@ Model({
this.update({ history: [], isAvailableForMe: true });
} else {
const result = await this.messaging.rpc({
route: '/im_livechat/init',
route: "/im_livechat/init",
params: { channel_id: this.channelId },
});
if (result.available_for_me) {
@@ -94,37 +99,40 @@ Model({
} else if (this.history !== null && this.history.length === 0) {
this.update({
livechatInit: await this.messaging.rpc({
route: '/im_livechat/init',
route: "/im_livechat/init",
params: { channel_id: this.channelId },
}),
});
} else if (this.history !== null && this.history.length !== 0) {
const sessionCookie = getCookie('im_livechat_session');
const sessionCookie = getCookie("im_livechat_session");
if (sessionCookie) {
this.update({ sessionCookie });
}
}
if (this.chatbot.state === 'init') {
if (this.chatbot.state === "init") {
// we landed on a website page where a channel rule is configured to run a chatbot.script
// -> initialize necessary state
if (this.rule.chatbot_welcome_steps && this.rule.chatbot_welcome_steps.length !== 0) {
if (
this.rule.chatbot_welcome_steps &&
this.rule.chatbot_welcome_steps.length !== 0
) {
this.chatbot.update({
currentStep: {
data: this.chatbot.lastWelcomeStep,
},
});
}
} else if (this.chatbot.state === 'welcome') {
} else if (this.chatbot.state === "welcome") {
// we landed on a website page and a chatbot script was initialized on a previous one
// however the end-user did not interact with the bot ( :( )
// -> remove cookie to force opening the popup again
// -> initialize necessary state
// -> batch welcome message (see '_sendWelcomeChatbotMessage')
deleteCookie('im_livechat_auto_popup');
deleteCookie("im_livechat_auto_popup");
this.update({ history: clear() });
this.update({ rule: this.livechatInit.rule });
} else if (this.chatbot.state === 'restore_session') {
} else if (this.chatbot.state === "restore_session") {
// we landed on a website page and a chatbot script is currently running
// -> restore the user's session (see 'Chatbot/restoreSession')
this.chatbot.restoreSession();
@@ -136,7 +144,7 @@ Model({
default: 15,
}),
LIVECHAT_COOKIE_HISTORY: attr({
default: 'im_livechat_history',
default: "im_livechat_history",
}),
RATING_TO_EMOJI: attr({
default: {
@@ -150,15 +158,15 @@ Model({
return this.options.channel_id;
},
}),
chatbot: one('Chatbot', {
chatbot: one("Chatbot", {
default: {},
inverse: 'publicLivechatGlobalOwner',
inverse: "publicLivechatGlobalOwner",
}),
chatWindow: one('PublicLivechatWindow', {
inverse: 'publicLivechatGlobalOwner',
chatWindow: one("PublicLivechatWindow", {
inverse: "publicLivechatGlobalOwner",
}),
feedbackView: one('PublicLivechatFeedbackView', {
inverse: 'publicLivechatGlobalOwner',
feedbackView: one("PublicLivechatFeedbackView", {
inverse: "publicLivechatGlobalOwner",
}),
hasLoadedQWebTemplate: attr({
default: false,
@@ -204,7 +212,7 @@ Model({
},
default: false,
}),
lastMessage: one('PublicLivechatMessage', {
lastMessage: one("PublicLivechatMessage", {
compute() {
if (this.messages.length === 0) {
return clear();
@@ -212,35 +220,40 @@ Model({
return this.messages[this.messages.length - 1];
},
}),
livechatButtonView: one('LivechatButtonView', {
livechatButtonView: one("LivechatButtonView", {
compute() {
if (this.isAvailable && (this.isAvailableForMe || this.isTestChatbot) && this.hasLoadedQWebTemplate && this.env.services.public_livechat_service) {
if (
this.isAvailable &&
(this.isAvailableForMe || this.isTestChatbot) &&
this.hasLoadedQWebTemplate &&
this.env.services.public_livechat_service
) {
return {};
}
return clear();
},
inverse: 'publicLivechatGlobalOwner',
inverse: "publicLivechatGlobalOwner",
}),
livechatInit: attr(),
messages: many('PublicLivechatMessage'),
notificationHandler: one('PublicLivechatGlobalNotificationHandler', {
inverse: 'publicLivechatGlobalOwner',
messages: many("PublicLivechatMessage"),
notificationHandler: one("PublicLivechatGlobalNotificationHandler", {
inverse: "publicLivechatGlobalOwner",
compute() {
if (this.publicLivechat && !this.publicLivechat.isTemporary) {
return {};
}
return clear();
}
},
}),
options: attr({
default: {},
}),
publicLivechat: one('PublicLivechat', {
inverse: 'publicLivechatGlobalOwner',
publicLivechat: one("PublicLivechat", {
inverse: "publicLivechatGlobalOwner",
}),
rule: attr(),
serverUrl: attr({
default: '',
default: "",
}),
sessionCookie: attr(),
testChatbotData: attr({
@@ -251,10 +264,14 @@ Model({
return this.options.testChatbotData;
},
}),
welcomeMessages: many('PublicLivechatMessage', {
welcomeMessages: many("PublicLivechatMessage", {
compute() {
return this.messages.filter((message) => {
return message.id && typeof message.id === 'string' && message.id.startsWith('_welcome_');
return (
message.id &&
typeof message.id === "string" &&
message.id.startsWith("_welcome_")
);
});
},
}),
@@ -1,17 +1,19 @@
/** @odoo-module **/
import { increment, one, Model } from '@mail/model';
import { increment, one, Model } from "@mail/model";
import session from 'web.session';
import utils from 'web.utils';
import {getCookie} from 'web.utils.cookies';
import session from "web.session";
import utils from "web.utils";
import { getCookie } from "web.utils.cookies";
Model({
name: 'PublicLivechatGlobalNotificationHandler',
name: "PublicLivechatGlobalNotificationHandler",
lifecycleHooks: {
_created() {
this.env.services['bus_service'].addChannel(this.messaging.publicLivechatGlobal.publicLivechat.uuid);
this.env.services['bus_service'].addEventListener('notification', this._onNotification);
this.env.services["bus_service"].addChannel(
this.messaging.publicLivechatGlobal.publicLivechat.uuid
);
this.env.services["bus_service"].addEventListener("notification", this._onNotification);
},
},
recordMethods: {
@@ -23,43 +25,61 @@ Model({
*/
_handleNotification({ payload: notifPayload, type }) {
switch (type) {
case 'im_livechat.history_command': {
case "im_livechat.history_command": {
if (notifPayload.id !== this.messaging.publicLivechatGlobal.publicLivechat.id) {
return;
}
const cookie = getCookie(this.messaging.publicLivechatGlobal.LIVECHAT_COOKIE_HISTORY);
const cookie = getCookie(
this.messaging.publicLivechatGlobal.LIVECHAT_COOKIE_HISTORY
);
const history = cookie ? JSON.parse(cookie) : [];
session.rpc('/im_livechat/history', {
session.rpc("/im_livechat/history", {
pid: this.messaging.publicLivechatGlobal.publicLivechat.operator.id,
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
page_history: history,
});
return;
}
case 'mail.channel.member/typing_status': {
if (!this.messaging.publicLivechatGlobal.chatWindow || !this.messaging.publicLivechatGlobal.chatWindow.exists()) {
case "mail.channel.member/typing_status": {
if (
!this.messaging.publicLivechatGlobal.chatWindow ||
!this.messaging.publicLivechatGlobal.chatWindow.exists()
) {
return;
}
const channelMemberData = notifPayload;
if (channelMemberData.channel.id !== this.messaging.publicLivechatGlobal.publicLivechat.id) {
if (
channelMemberData.channel.id !==
this.messaging.publicLivechatGlobal.publicLivechat.id
) {
return;
}
if (!channelMemberData.persona.partner) {
return;
}
if (channelMemberData.persona.partner.id === this.messaging.publicLivechatGlobal.livechatButtonView.currentPartnerId) {
if (
channelMemberData.persona.partner.id ===
this.messaging.publicLivechatGlobal.livechatButtonView.currentPartnerId
) {
// ignore typing display of current partner.
return;
}
if (channelMemberData.isTyping) {
this.messaging.publicLivechatGlobal.publicLivechat.widget.registerTyping({ partnerID: channelMemberData.persona.partner.id });
this.messaging.publicLivechatGlobal.publicLivechat.widget.registerTyping({
partnerID: channelMemberData.persona.partner.id,
});
} else {
this.messaging.publicLivechatGlobal.publicLivechat.widget.unregisterTyping({ partnerID: channelMemberData.persona.partner.id });
this.messaging.publicLivechatGlobal.publicLivechat.widget.unregisterTyping({
partnerID: channelMemberData.persona.partner.id,
});
}
return;
}
case 'mail.channel/new_message': {
if (!this.messaging.publicLivechatGlobal.chatWindow || !this.messaging.publicLivechatGlobal.chatWindow.exists()) {
case "mail.channel/new_message": {
if (
!this.messaging.publicLivechatGlobal.chatWindow ||
!this.messaging.publicLivechatGlobal.chatWindow.exists()
) {
return;
}
if (notifPayload.id !== this.messaging.publicLivechatGlobal.publicLivechat.id) {
@@ -67,26 +87,42 @@ Model({
}
const notificationData = notifPayload.message;
// If message from notif is already in chatter messages, stop handling
if (this.messaging.publicLivechatGlobal.messages.some(message => message.id === notificationData.id)) {
if (
this.messaging.publicLivechatGlobal.messages.some(
(message) => message.id === notificationData.id
)
) {
return;
}
notificationData.body = utils.Markup(notificationData.body);
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(notificationData);
if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.isAtBottom()) {
this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() });
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(
notificationData
);
if (
this.messaging.publicLivechatGlobal.publicLivechat.isFolded ||
!this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.isAtBottom()
) {
this.messaging.publicLivechatGlobal.publicLivechat.update({
unreadCounter: increment(),
});
}
this.messaging.publicLivechatGlobal.chatWindow.renderMessages();
return;
}
case 'mail.record/insert': {
case "mail.record/insert": {
const { Message: payload } = notifPayload;
if (!payload) {
return;
}
if (!this.messaging.publicLivechatGlobal.chatWindow || !this.messaging.publicLivechatGlobal.chatWindow.exists()) {
if (
!this.messaging.publicLivechatGlobal.chatWindow ||
!this.messaging.publicLivechatGlobal.chatWindow.exists()
) {
return;
}
const message = this.messaging.publicLivechatGlobal.messages.find(message => message.id === payload.id);
const message = this.messaging.publicLivechatGlobal.messages.find(
(message) => message.id === payload.id
);
if (!message) {
return;
}
@@ -108,9 +144,9 @@ Model({
},
},
fields: {
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
identifying: true,
inverse: 'notificationHandler',
inverse: "notificationHandler",
}),
},
});
@@ -1,14 +1,20 @@
/** @odoo-module **/
import { attr, clear, Model } from '@mail/model';
import { attr, clear, Model } from "@mail/model";
import PublicLivechatMessage from '@im_livechat/legacy/models/public_livechat_message';
import PublicLivechatMessage from "@im_livechat/legacy/models/public_livechat_message";
Model({
name: 'PublicLivechatMessage',
name: "PublicLivechatMessage",
lifecycleHooks: {
_created() {
this.update({ widget: new PublicLivechatMessage(this.messaging.publicLivechatGlobal.livechatButtonView.widget, this.messaging, this.data) });
this.update({
widget: new PublicLivechatMessage(
this.messaging.publicLivechatGlobal.livechatButtonView.widget,
this.messaging,
this.data
),
});
},
_willDelete() {
this.widget.destroy();
@@ -1,11 +1,11 @@
/** @odoo-module **/
import PublicLivechatView from '@im_livechat/legacy/widgets/public_livechat_view/public_livechat_view';
import PublicLivechatView from "@im_livechat/legacy/widgets/public_livechat_view/public_livechat_view";
import { attr, one, Model } from '@mail/model';
import { attr, one, Model } from "@mail/model";
Model({
name: 'PublicLivechatView',
name: "PublicLivechatView",
lifecycleHooks: {
_created() {
this.update({
@@ -17,9 +17,9 @@ Model({
},
},
fields: {
publicLivechatWindowOwner: one('PublicLivechatWindow', {
publicLivechatWindowOwner: one("PublicLivechatWindow", {
identifying: true,
inverse: 'publicLivechatView',
inverse: "publicLivechatView",
}),
widget: attr(),
},
@@ -1,18 +1,18 @@
/** @odoo-module **/
import PublicLivechatWindow from '@im_livechat/legacy/widgets/public_livechat_window/public_livechat_window';
import PublicLivechatWindow from "@im_livechat/legacy/widgets/public_livechat_window/public_livechat_window";
import { attr, one, Model } from '@mail/model';
import { attr, one, Model } from "@mail/model";
Model({
name: 'PublicLivechatWindow',
name: "PublicLivechatWindow",
lifecycleHooks: {
_created() {
this.update({
widget: new PublicLivechatWindow(
this.messaging.publicLivechatGlobal.livechatButtonView.widget,
this.messaging,
this.messaging.publicLivechatGlobal.publicLivechat.widget,
this.messaging.publicLivechatGlobal.publicLivechat.widget
),
});
},
@@ -22,16 +22,25 @@ Model({
},
recordMethods: {
enableInput() {
const $composerTextField = this.widget.$('.o_composer_text_field');
const $composerTextField = this.widget.$(".o_composer_text_field");
$composerTextField
.prop('disabled', false)
.removeClass('text-center fst-italic bg-200')
.val('')
.prop("disabled", false)
.removeClass("text-center fst-italic bg-200")
.val("")
.focus();
$composerTextField.off('keydown', this.messaging.publicLivechatGlobal.chatbot.onKeydownInput);
if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
$composerTextField.on('keydown', this.messaging.publicLivechatGlobal.chatbot.onKeydownInput);
$composerTextField.off(
"keydown",
this.messaging.publicLivechatGlobal.chatbot.onKeydownInput
);
if (
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type ===
"free_input_multi"
) {
$composerTextField.on(
"keydown",
this.messaging.publicLivechatGlobal.chatbot.onKeydownInput
);
}
},
/**
@@ -41,9 +50,10 @@ Model({
* @private
*/
disableInput(disableText) {
this.widget.$('.o_composer_text_field')
.prop('disabled', true)
.addClass('text-center fst-italic bg-200')
this.widget
.$(".o_composer_text_field")
.prop("disabled", true)
.addClass("text-center fst-italic bg-200")
.val(disableText);
},
renderMessages() {
@@ -54,11 +64,18 @@ Model({
}
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_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', (ev) => {
this.widget.$(".o_livechat_chatbot_main_restart").on("click", (ev) => {
ev.stopPropagation(); // prevent fold behaviour
this.messaging.publicLivechatGlobal.livechatButtonView.onChatbotRestartScript(ev);
});
@@ -66,7 +83,11 @@ Model({
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()) {
if (
stepAnswers &&
stepAnswers.length !== 0 &&
!lastMessage.widget.getChatbotStepAnswerId()
) {
this.disableInput(this.env._t("Select an option above"));
}
}
@@ -81,13 +102,13 @@ Model({
return this.env._t("Say something");
},
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
identifying: true,
inverse: 'chatWindow',
inverse: "chatWindow",
}),
publicLivechatView: one('PublicLivechatView', {
publicLivechatView: one("PublicLivechatView", {
default: {},
inverse: 'publicLivechatWindowOwner',
inverse: "publicLivechatWindowOwner",
}),
widget: attr(),
},
@@ -1,35 +1,36 @@
.text-muted {
color: map-get($grays, '600');
color: map-get($grays, "600");
}
.text-start {
text-align: left;
text-align: left;
}
.text-center {
text-align: center;
text-align: center;
}
.o_thread_window {
&,* {
box-sizing: border-box;
}
.o_thread_window_header {
height: 35px;
.fa-close {
text-decoration: none;
font-weight: bold;
&:before {
content: "\00d7";
font-size: initial;
}
&,
* {
box-sizing: border-box;
}
> span {
margin: auto 0;
.o_thread_window_header {
height: 35px;
.fa-close {
text-decoration: none;
font-weight: bold;
&:before {
content: "\00d7";
font-size: initial;
}
}
> span {
margin: auto 0;
}
}
}
.o_email_chat_button:after {
content:' \27A4';
}
.o_email_chat_button:after {
content: " \27A4";
}
}
@@ -13,4 +13,3 @@
margin: 0 5px 0 0;
}
}
@@ -1,19 +1,19 @@
.o_history_container{
.o_history_container {
table-layout: fixed;
width: 100% !important;
> tbody > tr > td {
padding: 0px !important;
}
.o_history_kanban_container {
text-align:center;
text-align: center;
.o_history_kanban_sub_container {
.o_kanban_ungrouped {
flex-flow: column;
max-height:500px;
overflow:auto;
padding:2px 0px;
max-height: 500px;
overflow: auto;
padding: 2px 0px;
.rounded-circle {
width:32px;
width: 32px;
height: 32px;
}
.o_kanban_record {
@@ -25,7 +25,7 @@
}
}
.oe_module_vignette {
text-align:left;
text-align: left;
}
.o_kanban_image {
padding-top: 8px;
@@ -34,6 +34,5 @@
padding: 8px 8px 0px 64px;
}
}
}
}
@@ -1,17 +1,17 @@
/** @odoo-module **/
import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
import LivechatButton from "@im_livechat/legacy/widgets/livechat_button";
import rootWidget from 'root.widget';
import rootWidget from "root.widget";
export const publicLivechatService = {
dependencies: ['messaging'],
dependencies: ["messaging"],
async start(env, { messaging: messagingService }) {
const messaging = await messagingService.get();
return {
mountLivechatButton() {
const livechatButton = new LivechatButton(rootWidget, messaging);
livechatButton.appendTo(document.body).catch(error => {
livechatButton.appendTo(document.body).catch((error) => {
console.info("Can't load 'LivechatButton' because:", error);
});
return livechatButton;
@@ -3,19 +3,24 @@
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'im_livechat/controllers/main', {
patch(MockServer.prototype, "im_livechat/controllers/main", {
/**
* @override
*/
async _performRPC(route, args) {
if (route === '/im_livechat/get_session') {
if (route === "/im_livechat/get_session") {
const channel_id = args.channel_id;
const anonymous_name = args.anonymous_name;
const previous_operator_id = args.previous_operator_id;
const context = args.context;
return this._mockRouteImLivechatGetSession(channel_id, anonymous_name, previous_operator_id, context);
return this._mockRouteImLivechatGetSession(
channel_id,
anonymous_name,
previous_operator_id,
context
);
}
if (route === '/im_livechat/notify_typing') {
if (route === "/im_livechat/notify_typing") {
const uuid = args.uuid;
const is_typing = args.is_typing;
const context = args.context;
@@ -33,10 +38,10 @@ patch(MockServer.prototype, 'im_livechat/controllers/main', {
* @param {Object} [context={}]
* @returns {Object}
*/
_mockRouteImLivechatGetSession(channel_id, anonymous_name, previous_operator_id, context = {}) {
_mockRouteImLivechatGetSession(channel_id, anonymous_name, previous_operator_id, context = {}) {
let user_id;
let country_id;
if ('mockedUserId' in context) {
if ("mockedUserId" in context) {
// can be falsy to simulate not being logged in
user_id = context.mockedUserId;
} else {
@@ -44,18 +49,24 @@ patch(MockServer.prototype, 'im_livechat/controllers/main', {
}
// don't use the anonymous name if the user is logged in
if (user_id) {
const user = this.getRecords('res.users', [['id', '=', user_id]])[0];
const user = this.getRecords("res.users", [["id", "=", user_id]])[0];
country_id = user.country_id;
} else {
// simulate geoip
const countryCode = context.mockedCountryCode;
const country = this.getRecords('res.country', [['code', '=', countryCode]])[0];
const country = this.getRecords("res.country", [["code", "=", countryCode]])[0];
if (country) {
country_id = country.id;
anonymous_name = anonymous_name + ' (' + country.name + ')';
anonymous_name = anonymous_name + " (" + country.name + ")";
}
}
return this._mockImLivechatChannel_openLivechatMailChannel(channel_id, anonymous_name, previous_operator_id, user_id, country_id);
return this._mockImLivechatChannel_openLivechatMailChannel(
channel_id,
anonymous_name,
previous_operator_id,
user_id,
country_id
);
},
/**
* Simulates the `/im_livechat/notify_typing` route.
@@ -66,9 +77,12 @@ patch(MockServer.prototype, 'im_livechat/controllers/main', {
* @param {Object} [context={}]
*/
_mockRouteImLivechatNotifyTyping(uuid, is_typing, context = {}) {
const [mailChannel] = this.getRecords('mail.channel', [['uuid', '=', uuid]]);
const [mailChannel] = this.getRecords("mail.channel", [["uuid", "=", uuid]]);
const partnerId = context.mockedPartnerId || this.currentPartnerId;
const [memberOfCurrentUser] = this.getRecords('mail.channel.member', [['channel_id', '=', mailChannel.id], ['partner_id', '=', partnerId]]);
const [memberOfCurrentUser] = this.getRecords("mail.channel.member", [
["channel_id", "=", mailChannel.id],
["partner_id", "=", partnerId],
]);
this._mockMailChannelMember_NotifyTyping([memberOfCurrentUser.id], is_typing);
},
});
@@ -3,7 +3,7 @@
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'im_livechat/models/im_livechat_channel', {
patch(MockServer.prototype, "im_livechat/models/im_livechat_channel", {
/**
* Simulates `_get_available_users` on `im_livechat.channel`.
*
@@ -12,9 +12,9 @@ patch(MockServer.prototype, 'im_livechat/models/im_livechat_channel', {
* @returns {Object}
*/
_mockImLivechatChannel_getAvailableUsers(id) {
const livechatChannel = this.getRecords('im_livechat.channel', [['id', '=', id]])[0];
const users = this.getRecords('res.users', [['id', 'in', livechatChannel.user_ids]]);
return users.filter(user => user.im_status === 'online');
const livechatChannel = this.getRecords("im_livechat.channel", [["id", "=", id]])[0];
const users = this.getRecords("res.users", [["id", "in", livechatChannel.user_ids]]);
return users.filter((user) => user.im_status === "online");
},
/**
* Simulates `_get_livechat_mail_channel_vals` on `im_livechat.channel`.
@@ -23,16 +23,28 @@ patch(MockServer.prototype, 'im_livechat/models/im_livechat_channel', {
* @param {integer} id
* @returns {Object}
*/
_mockImLivechatChannel_getLivechatMailChannelVals(id, anonymous_name, operator, user_id, country_id) {
_mockImLivechatChannel_getLivechatMailChannelVals(
id,
anonymous_name,
operator,
user_id,
country_id
) {
// partner to add to the mail.channel
const operator_partner_id = operator.partner_id;
const membersToAdd = [[0, 0, {
is_pinned: false,
partner_id: operator_partner_id,
}]];
const membersToAdd = [
[
0,
0,
{
is_pinned: false,
partner_id: operator_partner_id,
},
],
];
let visitor_user;
if (user_id) {
const visitor_user = this.getRecords('res.users', [['id', '=', user_id]])[0];
const visitor_user = this.getRecords("res.users", [["id", "=", user_id]])[0];
if (visitor_user && visitor_user.active && visitor_user !== operator) {
// valid session user (not public)
membersToAdd.push([0, 0, { partner_id: visitor_user.partner_id.id }]);
@@ -45,14 +57,14 @@ patch(MockServer.prototype, 'im_livechat/models/im_livechat_channel', {
operator.livechat_username ? operator.livechat_username : operator.name,
];
return {
'channel_member_ids': membersToAdd,
'livechat_active': true,
'livechat_operator_id': operator_partner_id,
'livechat_channel_id': id,
'anonymous_name': user_id ? false : anonymous_name,
'country_id': country_id,
'channel_type': 'livechat',
'name': membersName.join(' '),
channel_member_ids: membersToAdd,
livechat_active: true,
livechat_operator_id: operator_partner_id,
livechat_channel_id: id,
anonymous_name: user_id ? false : anonymous_name,
country_id: country_id,
channel_type: "livechat",
name: membersName.join(" "),
};
},
/**
@@ -78,11 +90,17 @@ patch(MockServer.prototype, 'im_livechat/models/im_livechat_channel', {
* @param {integer} [country_id]
* @returns {Object}
*/
_mockImLivechatChannel_openLivechatMailChannel(id, anonymous_name, previous_operator_id, user_id, country_id) {
_mockImLivechatChannel_openLivechatMailChannel(
id,
anonymous_name,
previous_operator_id,
user_id,
country_id
) {
let operator;
if (previous_operator_id) {
const availableUsers = this._mockImLivechatChannel_getAvailableUsers(id);
operator = availableUsers.find(user => user.partner_id === previous_operator_id);
operator = availableUsers.find((user) => user.partner_id === previous_operator_id);
}
if (!operator) {
operator = this._mockImLivechatChannel_getRandomOperator(id);
@@ -92,8 +110,14 @@ patch(MockServer.prototype, 'im_livechat/models/im_livechat_channel', {
return false;
}
// create the session, and add the link with the given channel
const mailChannelVals = this._mockImLivechatChannel_getLivechatMailChannelVals(id, anonymous_name, operator, user_id, country_id);
const mailChannelId = this.pyEnv['mail.channel'].create(mailChannelVals);
const mailChannelVals = this._mockImLivechatChannel_getLivechatMailChannelVals(
id,
anonymous_name,
operator,
user_id,
country_id
);
const mailChannelId = this.pyEnv["mail.channel"].create(mailChannelVals);
this._mockMailChannel_broadcast([mailChannelId], [operator.partner_id]);
return this._mockMailChannelChannelInfo([mailChannelId])[0];
},
@@ -1,26 +1,31 @@
/** @odoo-module **/
import '@mail/../tests/helpers/mock_server/models/mail_channel'; // ensure mail overrides are applied first
import "@mail/../tests/helpers/mock_server/models/mail_channel"; // ensure mail overrides are applied first
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'im_livechat/models/mail_channel', {
patch(MockServer.prototype, "im_livechat/models/mail_channel", {
/**
* @override
*/
_mockMailChannelChannelInfo(ids) {
const channelInfos = this._super(...arguments);
for (const channelInfo of channelInfos) {
const channel = this.getRecords('mail.channel', [['id', '=', channelInfo.id]])[0];
channelInfo['channel']['anonymous_name'] = channel.anonymous_name;
const channel = this.getRecords("mail.channel", [["id", "=", channelInfo.id]])[0];
channelInfo["channel"]["anonymous_name"] = channel.anonymous_name;
// add the last message date
if (channel.channel_type === 'livechat') {
if (channel.channel_type === "livechat") {
// add the operator id
if (channel.livechat_operator_id) {
const operator = this.getRecords('res.partner', [['id', '=', channel.livechat_operator_id]])[0];
const operator = this.getRecords("res.partner", [
["id", "=", channel.livechat_operator_id],
])[0];
// livechat_username ignored for simplicity
channelInfo.operator_pid = [operator.id, operator.display_name.replace(',', '')];
channelInfo.operator_pid = [
operator.id,
operator.display_name.replace(",", ""),
];
}
}
}
@@ -1,37 +1,39 @@
/** @odoo-module **/
import '@mail/../tests/helpers/mock_server/models/mail_channel_member'; // ensure mail overrides are applied first
import "@mail/../tests/helpers/mock_server/models/mail_channel_member"; // ensure mail overrides are applied first
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'im_livechat/models/mail_channel_member', {
patch(MockServer.prototype, "im_livechat/models/mail_channel_member", {
/**
* @override
*/
_mockMailChannelMember_GetPartnerData(ids) {
const [member] = this.getRecords('mail.channel.member', [['id', 'in', ids]]);
const [channel] = this.getRecords('mail.channel', [['id', '=', member.channel_id]]);
const [partner] = this.getRecords('res.partner', [['id', '=', member.partner_id]], { active_test: false });
if (channel.channel_type === 'livechat') {
const [member] = this.getRecords("mail.channel.member", [["id", "in", ids]]);
const [channel] = this.getRecords("mail.channel", [["id", "=", member.channel_id]]);
const [partner] = this.getRecords("res.partner", [["id", "=", member.partner_id]], {
active_test: false,
});
if (channel.channel_type === "livechat") {
const data = {
'id': partner.id,
'is_public': partner.is_public,
id: partner.id,
is_public: partner.is_public,
};
if (partner.user_livechat_username) {
data['user_livechat_username'] = partner.user_livechat_username;
data["user_livechat_username"] = partner.user_livechat_username;
} else {
data['name'] = partner.name;
data["name"] = partner.name;
}
if (!partner.is_public) {
const [country] = this.getRecords('res.country', [['id', '=', partner.country_id]]);
data['country'] = country
const [country] = this.getRecords("res.country", [["id", "=", partner.country_id]]);
data["country"] = country
? {
'code': country.code,
'id': country.id,
'name': country.name,
}
: [['clear']];
code: country.code,
id: country.id,
name: country.name,
}
: [["clear"]];
}
return data;
}
@@ -1,24 +1,24 @@
/** @odoo-module **/
import '@mail/../tests/helpers/mock_server/models/res_partner'; // ensure mail overrides are applied first
import "@mail/../tests/helpers/mock_server/models/res_partner"; // ensure mail overrides are applied first
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'im_livechat/models/res_partner', {
patch(MockServer.prototype, "im_livechat/models/res_partner", {
/**
* @override
*/
_mockResPartner_GetChannelsAsMember(ids) {
const partner = this.getRecords('res.partner', [['id', 'in', ids]])[0];
const members = this.getRecords('mail.channel.member', [['partner_id', '=', partner.id], ['is_pinned', '=', true]]);
const livechats = this.getRecords('mail.channel', [
['channel_type', '=', 'livechat'],
['channel_member_ids', 'in', members.map(member => member.id)],
const partner = this.getRecords("res.partner", [["id", "in", ids]])[0];
const members = this.getRecords("mail.channel.member", [
["partner_id", "=", partner.id],
["is_pinned", "=", true],
]);
return [
...this._super(ids),
...livechats,
];
const livechats = this.getRecords("mail.channel", [
["channel_type", "=", "livechat"],
["channel_member_ids", "in", members.map((member) => member.id)],
]);
return [...this._super(ids), ...livechats];
},
});
@@ -1,8 +1,11 @@
/** @odoo-module **/
import { addModelNamesToFetch, insertModelFields } from '@bus/../tests/helpers/model_definitions_helpers';
import {
addModelNamesToFetch,
insertModelFields,
} from "@bus/../tests/helpers/model_definitions_helpers";
addModelNamesToFetch(['im_livechat.channel']);
insertModelFields('res.users.settings', {
addModelNamesToFetch(["im_livechat.channel"]);
insertModelFields("res.users.settings", {
is_discuss_sidebar_category_livechat_open: { default: true },
});
@@ -1,52 +1,61 @@
/** @odoo-module **/
import {
afterNextRender,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
import { afterNextRender, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('chat_window_manager', {}, function () {
QUnit.module('chat_window_manager_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("chat_window_manager", {}, function () {
QUnit.module("chat_window_manager_tests.js");
QUnit.test('closing a chat window with no message from admin side unpins it', async function (assert) {
assert.expect(1);
QUnit.test(
"closing a chat window with no message from admin side unpins it",
async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo" });
pyEnv['res.users'].create({ partner_id: resPartnerId1 });
const mailChannelId1 = pyEnv['mail.channel'].create(
{
channel_member_ids: [
[0, 0, {
is_pinned: true,
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: "livechat",
uuid: 'channel-10-uuid',
},
);
const { messaging } = await start();
const pyEnv = await startServer();
const resPartnerId1 = pyEnv["res.partner"].create({ name: "Demo" });
pyEnv["res.users"].create({ partner_id: resPartnerId1 });
const mailChannelId1 = pyEnv["mail.channel"].create({
channel_member_ids: [
[
0,
0,
{
is_pinned: true,
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: "livechat",
uuid: "channel-10-uuid",
});
const { messaging } = await start();
await afterNextRender(() => document.querySelector(`.o_MessagingMenu_toggler`).click());
await afterNextRender(() => document.querySelector(`.o_NotificationListView_preview`).click());
await afterNextRender(() => document.querySelector(`.o_ChatWindowHeaderView_commandClose`).click());
const channels = await messaging.rpc({
model: 'mail.channel',
method: 'channel_info',
args: [mailChannelId1],
}, { shadow: true });
assert.strictEqual(
channels[0].is_pinned,
false,
'Livechat channel should not be pinned',
);
});
});
});
await afterNextRender(() =>
document.querySelector(`.o_MessagingMenu_toggler`).click()
);
await afterNextRender(() =>
document.querySelector(`.o_NotificationListView_preview`).click()
);
await afterNextRender(() =>
document.querySelector(`.o_ChatWindowHeaderView_commandClose`).click()
);
const channels = await messaging.rpc(
{
model: "mail.channel",
method: "channel_info",
args: [mailChannelId1],
},
{ shadow: true }
);
assert.strictEqual(
channels[0].is_pinned,
false,
"Livechat channel should not be pinned"
);
}
);
});
});
});
@@ -1,50 +1,52 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
import { start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('composer_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("composer_tests.js");
QUnit.test('livechat: no add attachment button', async function (assert) {
// Attachments are not yet supported in livechat, especially from livechat
// visitor PoV. This may likely change in the future with task-2029065.
assert.expect(2);
QUnit.test("livechat: no add attachment button", async function (assert) {
// Attachments are not yet supported in livechat, especially from livechat
// visitor PoV. This may likely change in the future with task-2029065.
assert.expect(2);
const pyEnv = await startServer();
const livechatId = pyEnv['mail.channel'].create({ channel_type: 'livechat' });
const { openDiscuss } = await start({
discuss: {
context: { active_id: livechatId },
},
const pyEnv = await startServer();
const livechatId = pyEnv["mail.channel"].create({ channel_type: "livechat" });
const { openDiscuss } = await start({
discuss: {
context: { active_id: livechatId },
},
});
await openDiscuss();
assert.containsOnce(document.body, ".o_ComposerView", "should have a composer");
assert.containsNone(
document.body,
".o_ComposerView_buttonAttachment",
"composer linked to livechat should not have a 'Add attachment' button"
);
});
QUnit.test(
"livechat: disable attachment upload via drag and drop",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const livechatId = pyEnv["mail.channel"].create({ channel_type: "livechat" });
const { openDiscuss } = await start({
discuss: {
context: { active_id: livechatId },
},
});
await openDiscuss();
assert.containsOnce(document.body, ".o_ComposerView", "should have a composer");
assert.containsNone(
document.body,
".o_ComposerView_dropZone",
"composer linked to livechat should not have a dropzone"
);
}
);
});
await openDiscuss();
assert.containsOnce(document.body, '.o_ComposerView', "should have a composer");
assert.containsNone(
document.body,
'.o_ComposerView_buttonAttachment',
"composer linked to livechat should not have a 'Add attachment' button"
);
});
QUnit.test('livechat: disable attachment upload via drag and drop', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const livechatId = pyEnv['mail.channel'].create({ channel_type: 'livechat' });
const { openDiscuss } = await start({
discuss: {
context: { active_id: livechatId },
},
});
await openDiscuss();
assert.containsOnce(document.body, '.o_ComposerView', "should have a composer");
assert.containsNone(
document.body,
'.o_ComposerView_dropZone',
"composer linked to livechat should not have a dropzone"
);
});
});
});
@@ -1,77 +1,81 @@
/** @odoo-module **/
import {
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
import { start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('discuss_sidebar_category_item_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("discuss_sidebar_category_item_tests.js");
QUnit.test('livechat - avatar: should have a smiley face avatar for an anonymous livechat item', async function (assert) {
assert.expect(2);
QUnit.test(
"livechat - avatar: should have a smiley face avatar for an anonymous livechat item",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
const livechatItem = document.querySelector(`
const livechatItem = document.querySelector(`
.o_DiscussSidebarCategoryItem[data-channel-id="${mailChannelId1}"]
`);
assert.containsOnce(
livechatItem,
`.o_DiscussSidebarCategoryItem_image`,
"should have an avatar"
);
assert.strictEqual(
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
'/mail/static/src/img/smiley/avatar.jpg',
'should have the smiley face as the avatar for anonymous users'
);
});
assert.containsOnce(
livechatItem,
`.o_DiscussSidebarCategoryItem_image`,
"should have an avatar"
);
assert.strictEqual(
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset
.src,
"/mail/static/src/img/smiley/avatar.jpg",
"should have the smiley face as the avatar for anonymous users"
);
}
);
QUnit.test('livechat - avatar: should have a partner profile picture for a livechat item linked with a partner', async function (assert) {
assert.expect(2);
QUnit.test(
"livechat - avatar: should have a partner profile picture for a livechat item linked with a partner",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
name: "Jean",
});
const mailChannelId1 = pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const resPartnerId1 = pyEnv["res.partner"].create({
name: "Jean",
});
const mailChannelId1 = pyEnv["mail.channel"].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
const livechatItem = document.querySelector(`
const livechatItem = document.querySelector(`
.o_DiscussSidebarCategoryItem[data-channel-id="${mailChannelId1}"]
`);
assert.containsOnce(
livechatItem,
`.o_DiscussSidebarCategoryItem_image`,
"should have an avatar"
);
assert.strictEqual(
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
`/web/image/res.partner/${resPartnerId1}/avatar_128`,
'should have the partner profile picture as the avatar for partners'
);
});
});
assert.containsOnce(
livechatItem,
`.o_DiscussSidebarCategoryItem_image`,
"should have an avatar"
);
assert.strictEqual(
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset
.src,
`/web/image/res.partner/${resPartnerId1}/avatar_128`,
"should have the partner profile picture as the avatar for partners"
);
}
);
});
});
@@ -1,453 +1,507 @@
/** @odoo-module **/
import {
afterNextRender,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
import { afterNextRender, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('discuss_sidebar_category_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("discuss_sidebar_category_tests.js");
QUnit.test('livechat - counter: should not have a counter if the category is unfolded and without unread messages', async function (assert) {
assert.expect(1);
QUnit.test(
"livechat - counter: should not have a counter if the category is unfolded and without unread messages",
async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`,
"should not have a counter if the category is unfolded and without unread messages",
);
});
const pyEnv = await startServer();
pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`,
"should not have a counter if the category is unfolded and without unread messages"
);
}
);
QUnit.test('livechat - counter: should not have a counter if the category is unfolded and with unread messages', async function (assert) {
assert.expect(1);
QUnit.test(
"livechat - counter: should not have a counter if the category is unfolded and with unread messages",
async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, {
message_unread_counter: 10,
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`,
"should not have a counter if the category is unfolded and with unread messages",
);
});
const pyEnv = await startServer();
pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[
0,
0,
{
message_unread_counter: 10,
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`,
"should not have a counter if the category is unfolded and with unread messages"
);
}
);
QUnit.test('livechat - counter: should not have a counter if category is folded and without unread messages', async function (assert) {
assert.expect(1);
QUnit.test(
"livechat - counter: should not have a counter if category is folded and without unread messages",
async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`,
"should not have a counter if the category is folded and without unread messages"
);
});
assert.containsNone(
document.body,
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`,
"should not have a counter if the category is folded and without unread messages"
);
}
);
QUnit.test('livechat - counter: should have correct value of unread threads if category is folded and with unread messages', async function (assert) {
assert.expect(1);
QUnit.test(
"livechat - counter: should have correct value of unread threads if category is folded and with unread messages",
async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, {
message_unread_counter: 10,
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[
0,
0,
{
message_unread_counter: 10,
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.strictEqual(
document.querySelector(`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`).textContent,
"1",
"should have correct value of unread threads if category is folded and with unread messages"
);
});
assert.strictEqual(
document.querySelector(
`.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_counter`
).textContent,
"1",
"should have correct value of unread threads if category is folded and with unread messages"
);
}
);
QUnit.test('livechat - states: close manually by clicking the title', async function (assert) {
assert.expect(2);
QUnit.test(
"livechat - states: close manually by clicking the title",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: true,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: true,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
// fold the livechat category
await afterNextRender(() =>
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
messaging.discuss.categoryLivechat.localId}"]
// fold the livechat category
await afterNextRender(() =>
document
.querySelector(
`.o_DiscussSidebarCategory[data-category-local-id="${messaging.discuss.categoryLivechat.localId}"]
.o_DiscussSidebarCategory_title
`).click()
);
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be closed and the content should be invisible"
);
});
`
)
.click()
);
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be closed and the content should be invisible"
);
}
);
QUnit.test('livechat - states: open manually by clicking the title', async function (assert) {
assert.expect(2);
QUnit.test(
"livechat - states: open manually by clicking the title",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
// open the livechat category
await afterNextRender(() =>
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
messaging.discuss.categoryLivechat.localId}"]
// open the livechat category
await afterNextRender(() =>
document
.querySelector(
`.o_DiscussSidebarCategory[data-category-local-id="${messaging.discuss.categoryLivechat.localId}"]
.o_DiscussSidebarCategory_title
`).click()
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be open and the content should be visible"
);
});
`
)
.click()
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be open and the content should be visible"
);
}
);
QUnit.test('livechat - states: close should update the value on the server', async function (assert) {
assert.expect(2);
QUnit.test(
"livechat - states: close should update the value on the server",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: true,
});
const currentUserId = pyEnv.currentUserId;
const { messaging, openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: true,
});
const currentUserId = pyEnv.currentUserId;
const { messaging, openDiscuss } = await start();
await openDiscuss();
const initalSettings = await messaging.rpc({
model: 'res.users.settings',
method: '_find_or_create_for_user',
args: [[currentUserId]],
});
assert.strictEqual(
initalSettings.is_discuss_sidebar_category_livechat_open,
true,
"the value in server side should be true"
);
const initalSettings = await messaging.rpc({
model: "res.users.settings",
method: "_find_or_create_for_user",
args: [[currentUserId]],
});
assert.strictEqual(
initalSettings.is_discuss_sidebar_category_livechat_open,
true,
"the value in server side should be true"
);
await afterNextRender(() =>
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
messaging.discuss.categoryLivechat.localId}"]
await afterNextRender(() =>
document
.querySelector(
`.o_DiscussSidebarCategory[data-category-local-id="${messaging.discuss.categoryLivechat.localId}"]
.o_DiscussSidebarCategory_title
`).click()
);
const newSettings = await messaging.rpc({
model: 'res.users.settings',
method: '_find_or_create_for_user',
args: [[currentUserId]],
});
assert.strictEqual(
newSettings.is_discuss_sidebar_category_livechat_open,
false,
"the value in server side should be false"
);
});
`
)
.click()
);
const newSettings = await messaging.rpc({
model: "res.users.settings",
method: "_find_or_create_for_user",
args: [[currentUserId]],
});
assert.strictEqual(
newSettings.is_discuss_sidebar_category_livechat_open,
false,
"the value in server side should be false"
);
}
);
QUnit.test('livechat - states: open should update the value on the server', async function (assert) {
assert.expect(2);
QUnit.test(
"livechat - states: open should update the value on the server",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const currentUserId = pyEnv.currentUserId;
const { messaging, openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const currentUserId = pyEnv.currentUserId;
const { messaging, openDiscuss } = await start();
await openDiscuss();
const initalSettings = await messaging.rpc({
model: 'res.users.settings',
method: '_find_or_create_for_user',
args: [[currentUserId]],
});
assert.strictEqual(
initalSettings.is_discuss_sidebar_category_livechat_open,
false,
"the value in server side should be false"
);
const initalSettings = await messaging.rpc({
model: "res.users.settings",
method: "_find_or_create_for_user",
args: [[currentUserId]],
});
assert.strictEqual(
initalSettings.is_discuss_sidebar_category_livechat_open,
false,
"the value in server side should be false"
);
await afterNextRender(() =>
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
messaging.discuss.categoryLivechat.localId}"]
await afterNextRender(() =>
document
.querySelector(
`.o_DiscussSidebarCategory[data-category-local-id="${messaging.discuss.categoryLivechat.localId}"]
.o_DiscussSidebarCategory_title
`).click()
);
const newSettings = await messaging.rpc({
model: 'res.users.settings',
method: '_find_or_create_for_user',
args: [[currentUserId]],
});
assert.strictEqual(
newSettings.is_discuss_sidebar_category_livechat_open,
true,
"the value in server side should be true"
);
});
`
)
.click()
);
const newSettings = await messaging.rpc({
model: "res.users.settings",
method: "_find_or_create_for_user",
args: [[currentUserId]],
});
assert.strictEqual(
newSettings.is_discuss_sidebar_category_livechat_open,
true,
"the value in server side should be true"
);
}
);
QUnit.test('livechat - states: close from the bus', async function (assert) {
assert.expect(2);
QUnit.test("livechat - states: close from the bus", async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const resUsersSettingsId1 = pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: true,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const resUsersSettingsId1 = pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: true,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
await afterNextRender(() => {
pyEnv['bus.bus']._sendone(pyEnv.currentPartner, 'mail.record/insert', {
'res.users.settings': {
'id': resUsersSettingsId1,
'is_discuss_sidebar_category_livechat_open': false,
},
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
"res.users.settings": {
id: resUsersSettingsId1,
is_discuss_sidebar_category_livechat_open: false,
},
});
});
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be closed and the content should be invisible"
);
});
});
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be closed and the content should be invisible"
);
});
QUnit.test('livechat - states: open from the bus', async function (assert) {
assert.expect(2);
QUnit.test("livechat - states: open from the bus", async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const resUsersSettingsId1 = pyEnv['res.users.settings'].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const resUsersSettingsId1 = pyEnv["res.users.settings"].create({
user_id: pyEnv.currentUserId,
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
await afterNextRender(() => {
pyEnv['bus.bus']._sendone(pyEnv.currentPartner, 'mail.record/insert', {
'res.users.settings': {
id: resUsersSettingsId1,
'is_discuss_sidebar_category_livechat_open': true,
},
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
"res.users.settings": {
id: resUsersSettingsId1,
is_discuss_sidebar_category_livechat_open: true,
},
});
});
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be open and the content should be visible"
);
});
});
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"Category livechat should be open and the content should be visible"
);
});
QUnit.test(
"livechat - states: category item should be invisible if the category is closed",
async function (assert) {
assert.expect(2);
QUnit.test('livechat - states: category item should be invisible if the category is closed', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
await afterNextRender(() =>
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
messaging.discuss.categoryLivechat.localId}"]
await afterNextRender(() =>
document
.querySelector(
`.o_DiscussSidebarCategory[data-category-local-id="${messaging.discuss.categoryLivechat.localId}"]
.o_DiscussSidebarCategory_title
`).click()
);
`
)
.click()
);
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"inactive item should be invisible if the category is folded"
);
});
assert.containsNone(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"inactive item should be invisible if the category is folded"
);
}
);
QUnit.test('livechat - states: the active category item should be visble even if the category is closed', async function (assert) {
assert.expect(3);
QUnit.test(
"livechat - states: the active category item should be visble even if the category is closed",
async function (assert) {
assert.expect(3);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
const livechat = document.querySelector(`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`);
await afterNextRender(() => {
livechat.click();
});
assert.ok(livechat.classList.contains('o-active'));
const livechat = document.querySelector(
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
);
await afterNextRender(() => {
livechat.click();
});
assert.ok(livechat.classList.contains("o-active"));
await afterNextRender(() =>
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
messaging.discuss.categoryLivechat.localId}"]
await afterNextRender(() =>
document
.querySelector(
`.o_DiscussSidebarCategory[data-category-local-id="${messaging.discuss.categoryLivechat.localId}"]
.o_DiscussSidebarCategory_title
`).click()
);
`
)
.click()
);
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
'the active livechat item should remain open even if the category is folded'
);
});
});
assert.containsOnce(
document.body,
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
"the active livechat item should remain open even if the category is folded"
);
}
);
});
});
@@ -5,431 +5,449 @@ import {
nextAnimationFrame,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
} from "@mail/../tests/helpers/test_utils";
import { datetime_to_str } from 'web.time';
import { datetime_to_str } from "web.time";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('discuss_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("discuss_tests.js");
QUnit.test('livechat in the sidebar: basic rendering', async function (assert) {
assert.expect(5);
QUnit.test("livechat in the sidebar: basic rendering", async function (assert) {
assert.expect(5);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(document.body, '.o_DiscussView_sidebar',
"should have a sidebar section"
);
const groupLivechat = document.querySelector('.o_DiscussSidebarView_categoryLivechat');
assert.ok(groupLivechat,
"should have a channel group livechat"
);
const titleText = groupLivechat.querySelector('.o_DiscussSidebarCategory_titleText');
assert.strictEqual(
titleText.textContent.trim(),
"Livechat",
"should have a channel group named 'Livechat'"
);
const livechat = groupLivechat.querySelector(`
assert.containsOnce(
document.body,
".o_DiscussView_sidebar",
"should have a sidebar section"
);
const groupLivechat = document.querySelector(".o_DiscussSidebarView_categoryLivechat");
assert.ok(groupLivechat, "should have a channel group livechat");
const titleText = groupLivechat.querySelector(".o_DiscussSidebarCategory_titleText");
assert.strictEqual(
titleText.textContent.trim(),
"Livechat",
"should have a channel group named 'Livechat'"
);
const livechat = groupLivechat.querySelector(`
.o_DiscussSidebarCategoryItem[data-channel-id="${mailChannelId1}"]
`);
assert.ok(
livechat,
"should have a livechat in sidebar"
);
assert.strictEqual(
livechat.textContent,
"Visitor 11",
"should have 'Visitor 11' as livechat name"
);
});
QUnit.test('livechat in the sidebar: existing user with country', async function (assert) {
assert.expect(3);
const pyEnv = await startServer();
const resCountryId1 = pyEnv['res.country'].create({
code: 'be',
name: "Belgium",
});
const resPartnerId1 = pyEnv['res.partner'].create({
country_id: resCountryId1,
name: "Jean",
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should have a channel group livechat in the side bar"
);
const livechat = document.querySelector('.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategoryItem');
assert.ok(
livechat,
"should have a livechat in sidebar"
);
assert.strictEqual(
livechat.textContent,
"Jean (Belgium)",
"should have user name and country as livechat name"
);
});
QUnit.test('do not add livechat in the sidebar on visitor opening his chat', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
pyEnv['res.users'].write([pyEnv.currentUserId], { im_status: 'online' });
const imLivechatChannelId1 = pyEnv['im_livechat.channel'].create({
user_ids: [pyEnv.currentUserId],
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should not have any livechat in the sidebar initially"
);
// simulate livechat visitor opening his chat
await messaging.rpc({
route: '/im_livechat/get_session',
params: {
context: {
mockedUserId: false,
},
channel_id: imLivechatChannelId1,
},
});
await nextAnimationFrame();
assert.containsNone(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should still not have any livechat in the sidebar after visitor opened his chat"
);
});
QUnit.test('do not add livechat in the sidebar on visitor typing', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
pyEnv['res.users'].write([pyEnv.currentUserId], { im_status: 'online' });
const imLivechatChannelId1 = pyEnv['im_livechat.channel'].create({
user_ids: [pyEnv.currentUserId],
});
const mailChannelId1 = pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, {
is_pinned: false,
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_channel_id: imLivechatChannelId1,
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should not have any livechat in the sidebar initially"
);
// simulate livechat visitor typing
const channel = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
await messaging.rpc({
route: '/im_livechat/notify_typing',
params: {
context: {
mockedPartnerId: pyEnv.publicPartnerId,
},
is_typing: true,
uuid: channel.uuid,
},
});
await nextAnimationFrame();
assert.containsNone(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should still not have any livechat in the sidebar after visitor started typing"
);
});
QUnit.test('add livechat in the sidebar on visitor sending first message', async function (assert) {
assert.expect(4);
const pyEnv = await startServer();
pyEnv['res.users'].write([pyEnv.currentUserId], { im_status: 'online' });
const resCountryId1 = pyEnv['res.country'].create({
code: 'be',
name: "Belgium",
});
const imLivechatChannelId1 = pyEnv['im_livechat.channel'].create({
user_ids: [pyEnv.currentUserId],
});
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor (Belgium)",
channel_member_ids: [
[0, 0, {
is_pinned: false,
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
country_id: resCountryId1,
livechat_channel_id: imLivechatChannelId1,
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should not have any livechat in the sidebar initially"
);
// simulate livechat visitor sending a message
const channel = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
await afterNextRender(async () => messaging.rpc({
route: '/mail/chat_post',
params: {
context: {
mockedUserId: false,
},
uuid: channel.uuid,
message_content: "new message",
},
}));
assert.containsOnce(
document.body,
'.o_DiscussSidebarView_categoryLivechat',
"should have a channel group livechat in the side bar after receiving first message"
);
assert.containsOnce(
document.body,
'.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategoryItem',
"should have a livechat in the sidebar after receiving first message"
);
assert.strictEqual(
document.querySelector('.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategoryItem .o_DiscussSidebarCategoryItem_name').textContent,
"Visitor (Belgium)",
"should have visitor name and country as livechat name"
);
});
QUnit.test('livechats are sorted by last activity time in the sidebar: most recent at the top', async function (assert) {
assert.expect(6);
const pyEnv = await startServer();
const [mailChannelId1, mailChannelId2] = pyEnv['mail.channel'].create([
{
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, {
last_interest_dt: datetime_to_str(new Date(2021, 0, 1)),
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
},
{
anonymous_name: "Visitor 12",
channel_member_ids: [
[0, 0, {
last_interest_dt: datetime_to_str(new Date(2021, 0, 2)),
partner_id: pyEnv.currentPartnerId,
}],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
},
]);
const { openDiscuss } = await start();
await openDiscuss();
const initialLivechats = document.querySelectorAll('.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_item');
assert.strictEqual(
initialLivechats.length,
2,
"should have 2 livechat items"
);
assert.strictEqual(
Number(initialLivechats[0].dataset.channelId),
mailChannelId2,
"first livechat should be the one with the more recent last activity time"
);
assert.strictEqual(
Number(initialLivechats[1].dataset.channelId),
mailChannelId1,
"second livechat should be the one with the less recent last activity time"
);
// post a new message on the last channel
await afterNextRender(() => initialLivechats[1].click());
await afterNextRender(() => document.execCommand('insertText', false, "Blabla"));
await afterNextRender(() => document.querySelector('.o_ComposerView_buttonSend').click());
const newLivechats = document.querySelectorAll('.o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_item');
assert.strictEqual(
newLivechats.length,
2,
"should have 2 livechat items"
);
assert.strictEqual(
Number(newLivechats[0].dataset.channelId),
mailChannelId1,
"first livechat should be the one with the more recent last activity time"
);
assert.strictEqual(
Number(newLivechats[1].dataset.channelId),
mailChannelId2,
"second livechat should be the one with the less recent last activity time"
);
});
QUnit.test('invite button should be present on livechat', async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create(
{
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
},
);
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadViewTopbar_inviteButton',
"Invite button should be visible in top bar when livechat is active thread"
);
});
QUnit.test('call buttons should not be present on livechat', async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create(
{
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
},
);
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsNone(
document.body,
'.o_ThreadViewTopbar_callButton',
"Call buttons should not be visible in top bar when livechat is active thread"
);
});
QUnit.test('reaction button should not be present on livechat', async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
channel_partner_ids: [pyEnv.currentPartnerId, pyEnv.publicPartnerId],
});
const { click, insertText, openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
await insertText('.o_ComposerTextInputView_textarea', "Test");
await click('.o_ComposerView_buttonSend');
await click('.o_MessageView');
assert.containsNone(
document.body,
'.o_MessageActionView_actionReaction',
"should not have action to add a reaction"
);
});
QUnit.test('reply button should not be present on livechat', async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
channel_partner_ids: [pyEnv.currentPartnerId, pyEnv.publicPartnerId],
});
const { click, insertText, openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
await insertText('.o_ComposerTextInputView_textarea', "Test");
await click('.o_ComposerView_buttonSend');
await click('.o_MessageView');
assert.containsNone(
document.body,
'.o_MessageActionView_actionReplyTo',
"should not have reply action"
);
});
});
assert.ok(livechat, "should have a livechat in sidebar");
assert.strictEqual(
livechat.textContent,
"Visitor 11",
"should have 'Visitor 11' as livechat name"
);
});
QUnit.test("livechat in the sidebar: existing user with country", async function (assert) {
assert.expect(3);
const pyEnv = await startServer();
const resCountryId1 = pyEnv["res.country"].create({
code: "be",
name: "Belgium",
});
const resPartnerId1 = pyEnv["res.partner"].create({
country_id: resCountryId1,
name: "Jean",
});
pyEnv["mail.channel"].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should have a channel group livechat in the side bar"
);
const livechat = document.querySelector(
".o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategoryItem"
);
assert.ok(livechat, "should have a livechat in sidebar");
assert.strictEqual(
livechat.textContent,
"Jean (Belgium)",
"should have user name and country as livechat name"
);
});
QUnit.test(
"do not add livechat in the sidebar on visitor opening his chat",
async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
pyEnv["res.users"].write([pyEnv.currentUserId], { im_status: "online" });
const imLivechatChannelId1 = pyEnv["im_livechat.channel"].create({
user_ids: [pyEnv.currentUserId],
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should not have any livechat in the sidebar initially"
);
// simulate livechat visitor opening his chat
await messaging.rpc({
route: "/im_livechat/get_session",
params: {
context: {
mockedUserId: false,
},
channel_id: imLivechatChannelId1,
},
});
await nextAnimationFrame();
assert.containsNone(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should still not have any livechat in the sidebar after visitor opened his chat"
);
}
);
QUnit.test("do not add livechat in the sidebar on visitor typing", async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
pyEnv["res.users"].write([pyEnv.currentUserId], { im_status: "online" });
const imLivechatChannelId1 = pyEnv["im_livechat.channel"].create({
user_ids: [pyEnv.currentUserId],
});
const mailChannelId1 = pyEnv["mail.channel"].create({
channel_member_ids: [
[
0,
0,
{
is_pinned: false,
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_channel_id: imLivechatChannelId1,
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should not have any livechat in the sidebar initially"
);
// simulate livechat visitor typing
const channel = pyEnv["mail.channel"].searchRead([["id", "=", mailChannelId1]])[0];
await messaging.rpc({
route: "/im_livechat/notify_typing",
params: {
context: {
mockedPartnerId: pyEnv.publicPartnerId,
},
is_typing: true,
uuid: channel.uuid,
},
});
await nextAnimationFrame();
assert.containsNone(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should still not have any livechat in the sidebar after visitor started typing"
);
});
QUnit.test(
"add livechat in the sidebar on visitor sending first message",
async function (assert) {
assert.expect(4);
const pyEnv = await startServer();
pyEnv["res.users"].write([pyEnv.currentUserId], { im_status: "online" });
const resCountryId1 = pyEnv["res.country"].create({
code: "be",
name: "Belgium",
});
const imLivechatChannelId1 = pyEnv["im_livechat.channel"].create({
user_ids: [pyEnv.currentUserId],
});
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor (Belgium)",
channel_member_ids: [
[
0,
0,
{
is_pinned: false,
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
country_id: resCountryId1,
livechat_channel_id: imLivechatChannelId1,
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should not have any livechat in the sidebar initially"
);
// simulate livechat visitor sending a message
const channel = pyEnv["mail.channel"].searchRead([["id", "=", mailChannelId1]])[0];
await afterNextRender(async () =>
messaging.rpc({
route: "/mail/chat_post",
params: {
context: {
mockedUserId: false,
},
uuid: channel.uuid,
message_content: "new message",
},
})
);
assert.containsOnce(
document.body,
".o_DiscussSidebarView_categoryLivechat",
"should have a channel group livechat in the side bar after receiving first message"
);
assert.containsOnce(
document.body,
".o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategoryItem",
"should have a livechat in the sidebar after receiving first message"
);
assert.strictEqual(
document.querySelector(
".o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategoryItem .o_DiscussSidebarCategoryItem_name"
).textContent,
"Visitor (Belgium)",
"should have visitor name and country as livechat name"
);
}
);
QUnit.test(
"livechats are sorted by last activity time in the sidebar: most recent at the top",
async function (assert) {
assert.expect(6);
const pyEnv = await startServer();
const [mailChannelId1, mailChannelId2] = pyEnv["mail.channel"].create([
{
anonymous_name: "Visitor 11",
channel_member_ids: [
[
0,
0,
{
last_interest_dt: datetime_to_str(new Date(2021, 0, 1)),
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
},
{
anonymous_name: "Visitor 12",
channel_member_ids: [
[
0,
0,
{
last_interest_dt: datetime_to_str(new Date(2021, 0, 2)),
partner_id: pyEnv.currentPartnerId,
},
],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
},
]);
const { openDiscuss } = await start();
await openDiscuss();
const initialLivechats = document.querySelectorAll(
".o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_item"
);
assert.strictEqual(initialLivechats.length, 2, "should have 2 livechat items");
assert.strictEqual(
Number(initialLivechats[0].dataset.channelId),
mailChannelId2,
"first livechat should be the one with the more recent last activity time"
);
assert.strictEqual(
Number(initialLivechats[1].dataset.channelId),
mailChannelId1,
"second livechat should be the one with the less recent last activity time"
);
// post a new message on the last channel
await afterNextRender(() => initialLivechats[1].click());
await afterNextRender(() => document.execCommand("insertText", false, "Blabla"));
await afterNextRender(() =>
document.querySelector(".o_ComposerView_buttonSend").click()
);
const newLivechats = document.querySelectorAll(
".o_DiscussSidebarView_categoryLivechat .o_DiscussSidebarCategory_item"
);
assert.strictEqual(newLivechats.length, 2, "should have 2 livechat items");
assert.strictEqual(
Number(newLivechats[0].dataset.channelId),
mailChannelId1,
"first livechat should be the one with the more recent last activity time"
);
assert.strictEqual(
Number(newLivechats[1].dataset.channelId),
mailChannelId2,
"second livechat should be the one with the less recent last activity time"
);
}
);
QUnit.test("invite button should be present on livechat", async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsOnce(
document.body,
".o_ThreadViewTopbar_inviteButton",
"Invite button should be visible in top bar when livechat is active thread"
);
});
QUnit.test("call buttons should not be present on livechat", async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsNone(
document.body,
".o_ThreadViewTopbar_callButton",
"Call buttons should not be visible in top bar when livechat is active thread"
);
});
QUnit.test("reaction button should not be present on livechat", async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
channel_partner_ids: [pyEnv.currentPartnerId, pyEnv.publicPartnerId],
});
const { click, insertText, openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
await insertText(".o_ComposerTextInputView_textarea", "Test");
await click(".o_ComposerView_buttonSend");
await click(".o_MessageView");
assert.containsNone(
document.body,
".o_MessageActionView_actionReaction",
"should not have action to add a reaction"
);
});
QUnit.test("reply button should not be present on livechat", async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
channel_partner_ids: [pyEnv.currentPartnerId, pyEnv.publicPartnerId],
});
const { click, insertText, openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
await insertText(".o_ComposerTextInputView_textarea", "Test");
await click(".o_ComposerView_buttonSend");
await click(".o_MessageView");
assert.containsNone(
document.body,
".o_MessageActionView_actionReplyTo",
"should not have reply action"
);
});
});
});
@@ -1,71 +1,62 @@
/** @odoo-module **/
import {
afterNextRender,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
import { afterNextRender, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('messaging_menu_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("messaging_menu_tests.js");
QUnit.test('livechats should be in "chat" filter', async function (assert) {
assert.expect(7);
QUnit.test('livechats should be in "chat" filter', async function (assert) {
assert.expect(7);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
await start();
assert.containsOnce(document.body, ".o_MessagingMenu", "should have messaging menu");
await afterNextRender(() => document.querySelector(".o_MessagingMenu_toggler").click());
assert.containsOnce(
document.body,
'.o_MessagingMenuTabView[data-tab-id="all"]',
"should have a tab/filter 'all' in messaging menu"
);
assert.containsOnce(
document.body,
'.o_MessagingMenuTabView[data-tab-id="chat"]',
"should have a tab/filter 'chat' in messaging menu"
);
assert.hasClass(
document.querySelector('.o_MessagingMenuTabView[data-tab-id="all"]'),
"o-active",
"tab/filter 'all' of messaging menu should be active initially"
);
assert.containsOnce(
document.body,
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
"livechat should be listed in 'all' tab/filter of messaging menu"
);
await afterNextRender(() =>
document.querySelector('.o_MessagingMenuTabView[data-tab-id="chat"]').click()
);
assert.hasClass(
document.querySelector('.o_MessagingMenuTabView[data-tab-id="chat"]'),
"o-active",
"tab/filter 'chat' of messaging menu should become active after click"
);
assert.containsOnce(
document.body,
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
"livechat should be listed in 'chat' tab/filter of messaging menu"
);
});
});
await start();
assert.containsOnce(
document.body,
'.o_MessagingMenu',
"should have messaging menu"
);
await afterNextRender(() => document.querySelector('.o_MessagingMenu_toggler').click());
assert.containsOnce(
document.body,
'.o_MessagingMenuTabView[data-tab-id="all"]',
"should have a tab/filter 'all' in messaging menu"
);
assert.containsOnce(
document.body,
'.o_MessagingMenuTabView[data-tab-id="chat"]',
"should have a tab/filter 'chat' in messaging menu"
);
assert.hasClass(
document.querySelector('.o_MessagingMenuTabView[data-tab-id="all"]'),
'o-active',
"tab/filter 'all' of messaging menu should be active initially"
);
assert.containsOnce(
document.body,
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
"livechat should be listed in 'all' tab/filter of messaging menu"
);
await afterNextRender(() =>
document.querySelector('.o_MessagingMenuTabView[data-tab-id="chat"]').click()
);
assert.hasClass(
document.querySelector('.o_MessagingMenuTabView[data-tab-id="chat"]'),
'o-active',
"tab/filter 'chat' of messaging menu should become active after click"
);
assert.containsOnce(
document.body,
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
"livechat should be listed in 'chat' tab/filter of messaging menu"
);
});
});
});
@@ -1,68 +1,65 @@
/** @odoo-module **/
import {
afterNextRender,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
import { afterNextRender, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_icon_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("thread_icon_tests.js");
QUnit.test('livechat: public website visitor is typing', async function (assert) {
assert.expect(4);
QUnit.test("livechat: public website visitor is typing", async function (assert) {
assert.expect(4);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 20",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 20",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start({
discuss: {
context: { active_id: mailChannelId1 },
},
});
await openDiscuss();
assert.containsOnce(
document.body,
".o_ThreadViewTopbar .o_ThreadIconView",
"should have thread icon"
);
assert.containsOnce(
document.body,
".o_ThreadIconView .fa.fa-comments",
"should have default livechat icon"
);
const mailChannel1 = pyEnv["mail.channel"].searchRead([["id", "=", mailChannelId1]])[0];
// simulate receive typing notification from livechat visitor "is typing"
await afterNextRender(() =>
messaging.rpc({
route: "/im_livechat/notify_typing",
params: {
context: {
mockedPartnerId: pyEnv.publicPartnerId,
},
is_typing: true,
uuid: mailChannel1.uuid,
},
})
);
assert.containsOnce(
document.body,
".o_ThreadIconView_typing",
"should have thread icon with visitor currently typing"
);
assert.strictEqual(
document.querySelector(".o_ThreadIconView_typing").title,
"Visitor 20 is typing...",
"title of icon should tell visitor is currently typing"
);
});
});
const { messaging, openDiscuss } = await start({
discuss: {
context: { active_id: mailChannelId1 },
},
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadViewTopbar .o_ThreadIconView',
"should have thread icon"
);
assert.containsOnce(
document.body,
'.o_ThreadIconView .fa.fa-comments',
"should have default livechat icon"
);
const mailChannel1 = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
// simulate receive typing notification from livechat visitor "is typing"
await afterNextRender(() => messaging.rpc({
route: '/im_livechat/notify_typing',
params: {
context: {
mockedPartnerId: pyEnv.publicPartnerId,
},
is_typing: true,
uuid: mailChannel1.uuid,
},
}));
assert.containsOnce(
document.body,
'.o_ThreadIconView_typing',
"should have thread icon with visitor currently typing"
);
assert.strictEqual(
document.querySelector('.o_ThreadIconView_typing').title,
"Visitor 20 is typing...",
"title of icon should tell visitor is currently typing"
);
});
});
});
@@ -1,59 +1,56 @@
/** @odoo-module **/
import {
afterNextRender,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
import { afterNextRender, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module('im_livechat', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_textual_typing_status_tests.js');
QUnit.module("im_livechat", {}, function () {
QUnit.module("components", {}, function () {
QUnit.module("thread_textual_typing_status_tests.js");
QUnit.test('receive visitor typing status "is typing"', async function (assert) {
assert.expect(2);
QUnit.test('receive visitor typing status "is typing"', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({
anonymous_name: "Visitor 20",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: 'livechat',
livechat_operator_id: pyEnv.currentPartnerId,
const pyEnv = await startServer();
const mailChannelId1 = pyEnv["mail.channel"].create({
anonymous_name: "Visitor 20",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { messaging, openDiscuss } = await start({
discuss: {
context: { active_id: mailChannelId1 },
},
});
await openDiscuss();
assert.strictEqual(
document.querySelector(".o_ThreadTextualTypingStatusView").textContent,
"",
"Should display no one is currently typing"
);
const mailChannel1 = pyEnv["mail.channel"].searchRead([["id", "=", mailChannelId1]])[0];
// simulate receive typing notification from livechat visitor "is typing"
await afterNextRender(() =>
messaging.rpc({
route: "/im_livechat/notify_typing",
params: {
context: {
mockedPartnerId: pyEnv.publicPartnerId,
},
is_typing: true,
uuid: mailChannel1.uuid,
},
})
);
assert.strictEqual(
document.querySelector(".o_ThreadTextualTypingStatusView").textContent,
"Visitor 20 is typing...",
"Should display that visitor is typing"
);
});
});
const { messaging, openDiscuss } = await start({
discuss: {
context: { active_id: mailChannelId1 },
},
});
await openDiscuss();
assert.strictEqual(
document.querySelector('.o_ThreadTextualTypingStatusView').textContent,
"",
"Should display no one is currently typing"
);
const mailChannel1 = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
// simulate receive typing notification from livechat visitor "is typing"
await afterNextRender(() => messaging.rpc({
route: '/im_livechat/notify_typing',
params: {
context: {
mockedPartnerId: pyEnv.publicPartnerId,
},
is_typing: true,
uuid: mailChannel1.uuid,
},
}));
assert.strictEqual(
document.querySelector('.o_ThreadTextualTypingStatusView').textContent,
"Visitor 20 is typing...",
"Should display that visitor is typing"
);
});
});
});
@@ -12,21 +12,26 @@ const requestChatSteps = [
},
];
registry.category("web_tour.tours").add("im_livechat_request_chat", { test: true, steps: requestChatSteps});
registry
.category("web_tour.tours")
.add("im_livechat_request_chat", { test: true, steps: requestChatSteps });
registry.category("web_tour.tours").add("im_livechat_request_chat_and_send_message", { test: true, steps: [
...requestChatSteps,
{
trigger: ".o_composer_text_field",
run: "text Hello, I need help please !",
},
{
trigger: '.o_composer_text_field',
run() {
$(".o_composer_text_field").trigger($.Event("keydown", { which: 13 }));
registry.category("web_tour.tours").add("im_livechat_request_chat_and_send_message", {
test: true,
steps: [
...requestChatSteps,
{
trigger: ".o_composer_text_field",
run: "text Hello, I need help please !",
},
},
{
trigger: ".o_thread_message:contains('Hello, I need help')",
},
]});
{
trigger: ".o_composer_text_field",
run() {
$(".o_composer_text_field").trigger($.Event("keydown", { which: 13 }));
},
},
{
trigger: ".o_thread_message:contains('Hello, I need help')",
},
],
});
@@ -3,122 +3,163 @@
import { registry } from "@web/core/registry";
import { stepUtils } from "@web_tour/js/tour_step_utils";
const commonSteps = [stepUtils.showAppsMenuItem(), {
trigger: '.o_app[data-menu-xmlid="im_livechat.menu_livechat_root"]',
}, {
trigger: 'button[data-menu-xmlid="im_livechat.livechat_config"]',
}, {
trigger: 'a[data-menu-xmlid="im_livechat.chatbot_config"]',
}, {
trigger: '.o_list_button_add',
}, {
trigger: 'input[id="title"]',
run: 'text Test Chatbot Sequence'
}, {
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a'
}, {
trigger: 'textarea#message',
run: 'text Step 1'
}, {
trigger: 'button:contains("Save & New")'
}, {
trigger: 'tr:contains("Step 1")',
in_modal: false,
run: () => {}
}, {
trigger: 'textarea#message',
run: 'text Step 2'
}, {
trigger: 'button:contains("Save & New")'
}, {
trigger: 'tr:contains("Step 2")',
in_modal: false,
run: () => {}
}, {
trigger: 'textarea#message',
run: 'text Step 3'
}];
const commonSteps = [
stepUtils.showAppsMenuItem(),
{
trigger: '.o_app[data-menu-xmlid="im_livechat.menu_livechat_root"]',
},
{
trigger: 'button[data-menu-xmlid="im_livechat.livechat_config"]',
},
{
trigger: 'a[data-menu-xmlid="im_livechat.chatbot_config"]',
},
{
trigger: ".o_list_button_add",
},
{
trigger: 'input[id="title"]',
run: "text Test Chatbot Sequence",
},
{
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a',
},
{
trigger: "textarea#message",
run: "text Step 1",
},
{
trigger: 'button:contains("Save & New")',
},
{
trigger: 'tr:contains("Step 1")',
in_modal: false,
run: () => {},
},
{
trigger: "textarea#message",
run: "text Step 2",
},
{
trigger: 'button:contains("Save & New")',
},
{
trigger: 'tr:contains("Step 2")',
in_modal: false,
run: () => {},
},
{
trigger: "textarea#message",
run: "text Step 3",
},
];
/**
* Simply create a few steps in order to check the sequences.
*/
registry.category("web_tour.tours").add('im_livechat_chatbot_steps_sequence_tour', {
registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_tour", {
test: true,
url: '/web',
url: "/web",
steps: [
...commonSteps, {
trigger: 'button:contains("Save & Close")'
}, {
trigger: 'body.o_web_client:not(.modal-open)',
run() {},
}, ...stepUtils.discardForm()
]});
...commonSteps,
{
trigger: 'button:contains("Save & Close")',
},
{
trigger: "body.o_web_client:not(.modal-open)",
run() {},
},
...stepUtils.discardForm(),
],
});
/**
* Same as above, with an extra drag&drop at the end.
*/
registry.category("web_tour.tours").add('im_livechat_chatbot_steps_sequence_with_move_tour', {
registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_with_move_tour", {
test: true,
url: '/web',
url: "/web",
steps: [
...commonSteps, {
trigger: 'button:contains("Save & New")'
}, {
trigger: 'tr:contains("Step 3")',
in_modal: false,
run: () => {}
}, {
trigger: 'textarea#message',
run: 'text Step 4'
}, {
trigger: 'button:contains("Save & New")'
}, {
trigger: 'tr:contains("Step 4")',
in_modal: false,
run: () => {}
}, {
trigger: 'textarea#message',
run: 'text Step 5'
}, {
trigger: 'button:contains("Save & Close")'
}, {
trigger: 'body.o_web_client:not(.modal-open)',
run: () => {}
}, {
trigger: 'tr:contains("Step 5") .o_row_handle',
run: () => {
// move 'step 5' between 'step 1' and 'step 2'
const from = document.querySelector('div[name="script_step_ids"] tr:nth-child(5) .o_row_handle');
const fromPosition = from.getBoundingClientRect();
fromPosition.x += from.offsetWidth / 2;
fromPosition.y += from.offsetHeight / 2;
...commonSteps,
{
trigger: 'button:contains("Save & New")',
},
{
trigger: 'tr:contains("Step 3")',
in_modal: false,
run: () => {},
},
{
trigger: "textarea#message",
run: "text Step 4",
},
{
trigger: 'button:contains("Save & New")',
},
{
trigger: 'tr:contains("Step 4")',
in_modal: false,
run: () => {},
},
{
trigger: "textarea#message",
run: "text Step 5",
},
{
trigger: 'button:contains("Save & Close")',
},
{
trigger: "body.o_web_client:not(.modal-open)",
run: () => {},
},
{
trigger: 'tr:contains("Step 5") .o_row_handle',
run: () => {
// move 'step 5' between 'step 1' and 'step 2'
const from = document.querySelector(
'div[name="script_step_ids"] tr:nth-child(5) .o_row_handle'
);
const fromPosition = from.getBoundingClientRect();
fromPosition.x += from.offsetWidth / 2;
fromPosition.y += from.offsetHeight / 2;
const to = document.querySelector('div[name="script_step_ids"] tr:nth-child(2) .o_row_handle');
from.dispatchEvent(new Event("mouseenter", { bubbles: true }));
from.dispatchEvent(new MouseEvent("mousedown", {
bubbles: true,
which: 1,
button: 0,
clientX: fromPosition.x,
clientY: fromPosition.y}));
from.dispatchEvent(new Event("mousemove", { bubbles: true }));
to.dispatchEvent(new Event("mouseenter", { bubbles: true }));
from.dispatchEvent(new Event("mouseup", { bubbles: true }));
}
}, {
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a'
}, {
trigger: 'textarea#message',
run: 'text Step 6'
}, {
trigger: 'button:contains("Save & Close")'
}, {
trigger: 'body.o_web_client:not(.modal-open)',
run: () => {}
}, {
trigger: 'tr:contains("Step 6")',
in_modal: false,
run: () => {}
}, ...stepUtils.discardForm(),
]});
const to = document.querySelector(
'div[name="script_step_ids"] tr:nth-child(2) .o_row_handle'
);
from.dispatchEvent(new Event("mouseenter", { bubbles: true }));
from.dispatchEvent(
new MouseEvent("mousedown", {
bubbles: true,
which: 1,
button: 0,
clientX: fromPosition.x,
clientY: fromPosition.y,
})
);
from.dispatchEvent(new Event("mousemove", { bubbles: true }));
to.dispatchEvent(new Event("mouseenter", { bubbles: true }));
from.dispatchEvent(new Event("mouseup", { bubbles: true }));
},
},
{
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a',
},
{
trigger: "textarea#message",
run: "text Step 6",
},
{
trigger: 'button:contains("Save & Close")',
},
{
trigger: "body.o_web_client:not(.modal-open)",
run: () => {},
},
{
trigger: 'tr:contains("Step 6")',
in_modal: false,
run: () => {},
},
...stepUtils.discardForm(),
],
});
+6 -2
View File
@@ -152,6 +152,10 @@ addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
!addons/mail/
!addons/mail/**/*
# Whitelist im_livechat
!addons/im_livechat/
!addons/im_livechat/**/*
# Whitelist point_of_sale
!addons/point_of_sale/
!addons/point_of_sale/**/*
@@ -195,7 +199,7 @@ addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
!addons/pos_six/**/*
!addons/pos_stripe
!addons/pos_stripe/**/*
# Whitelist enterprise pos modules
!l10n_de_pos_cert
!l10n_de_pos_cert/**/*
@@ -210,4 +214,4 @@ addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
!pos_restaurant_iot
!pos_restaurant_iot/**/*
!pos_settle_due
!pos_settle_due/**/*
!pos_settle_due/**/*