diff options
| author | David Beguin <dbe@odoo.com> | 2019-07-08 08:13:29 +0000 |
|---|---|---|
| committer | David Beguin <dbe@odoo.com> | 2019-08-19 06:33:37 +0000 |
| commit | 78e6ce3c3fe565c488b956450ed875629cdd2de0 (patch) | |
| tree | 989fc02ac9592d3c7bc950c5fd9e31e755019d4f /addons/website_livechat/static/src | |
| parent | a8ce2886be2e02315fa3526257a941c91c5385b9 (diff) | |
[IMP] website_livechat : allow to send chat request to a website_visitor
This commit allows a livechat operator to send a chat request to a
connected and available website_visitor.
A visitor is considered as connected if his last tracked website.page request
was within the last 5 minutes.
A visitor is available if he doesn't have an active livechat conversation
(mail_channel with type = livechat).
- If another operator sent him a chat request
- Or if the visitor asked himself to speak with an operator
(via the normal and existing flow)
A livechat conversation is active while the visitor haven't left the conversation.
An operator cannot leave a livechat conversation, only the visitor can.
The flow to send a chat request:
- On the visitor view (tree or form), operator click on 'send chat request'
(button or livechat icon)
- A empty conversation with the visitor pops up at operator side.
- While the operator didn't send a message, the visitor won't see the conversation.
- The operator can type a message to the user
- If the operator close the chat without sending any messages,
the chat request AND the mail_channel are both deleted.
In this case, the visitor is then available to send him a new chat request.
- If the operator send a message, at the visitor's next action
(page navigation on pages that allows livechat, based on livechat rules),
the conversation will pop up at visitor side, using the livechat button widget.
The visitor won't be able to request a livechat conversation with an operator until
he leaves the chat requested by the operator.
- Once the visitor leaves (with or without rating) the conversation :
- the operator is notified that the visitor has left the conversation
- the chat request is deleted to keep the chat request table clean and minimal
- the livechat conversation if set to inactive.
- The visitor is now available again to send him a chat request.
This feature uses the already existing livechat_session cookie mechanism,
so no further code modification was needed to make this work.
It's directly integrated is existing livechat flow.
The chat_request model is only useful to quickly check if a visitor has a chat request
and to send the livechat conversation info to the visitor via the livechat_session cookie.
This commit also add a website_visitor banner info on discuss view :
To be able to quickly see all the relevant information of a website visitor
while talking with in discuss view, a fixed banner have been added.
Only the discuss view will benefit from this because detached chatter window
is to small to display such banner.
Task ID: 34624
PR #2028059
Diffstat (limited to 'addons/website_livechat/static/src')
5 files changed, 236 insertions, 0 deletions
diff --git a/addons/website_livechat/static/src/js/channel.js b/addons/website_livechat/static/src/js/channel.js new file mode 100644 index 00000000000..24d679e8593 --- /dev/null +++ b/addons/website_livechat/static/src/js/channel.js @@ -0,0 +1,34 @@ +odoo.define('website.livechat.mail.model.Channel', function (require) { +"use strict"; + +var Channel = require('mail.model.Channel'); +var session = require('web.session'); + +/** + * This class represent channels in JS. In this context, the word channel + * has the same meaning of channel on the server, meaning that direct messages + * (DM) and livechats are also channels. + * + * Any piece of code in JS that make use of channels must ideally interact with + * such objects, instead of direct data from the server. + */ +Channel.include({ + /** + * @override + * Add the visitor if is set on the channel + * @param {Object} params + * @param {Object} params.data + * @param {string} params.data.visitor + */ + init: function (params) { + var self = this; + var data = params.data; + this._visitor = data.visitor; + this._super.apply(this, arguments); + }, + +}); + +return Channel; + +}); diff --git a/addons/website_livechat/static/src/js/im_livechat.js b/addons/website_livechat/static/src/js/im_livechat.js new file mode 100644 index 00000000000..d5ce27af2e1 --- /dev/null +++ b/addons/website_livechat/static/src/js/im_livechat.js @@ -0,0 +1,69 @@ +odoo.define('website_livechat.livechat_request', function (require) { +"use strict"; + +var utils = require('web.utils'); +var session = require('web.session'); +var LivechatButton = require('im_livechat.im_livechat').LivechatButton; + + +LivechatButton.include({ + + /** + * @override + * This will will correctly format the livechat session cookie + * that comes from server side (and that is not properly formatted) + * This is used for chat request mechanism, when an operator send a chat request + * from backend to a website visitor. + */ + willStart: function () { + var self = this; + var cookie = utils.get_cookie('im_livechat_session'); + var ready; + if (cookie) { + var cleanedLivechatSessionCookie = this.decode_server_cookie(cookie); + utils.set_cookie('im_livechat_session', cleanedLivechatSessionCookie, 60*60); + } + return this._super(); + }, + + /** + * @override + * Called when the visitor closes the livechat chatter + * (no matter the way : close, send feedback, ..) + * this will deactivate the mail_channel, clean the chat request if any + * and allow the operators to send the visitor a new chat request + */ + _closeChat: function () { + var self = this; + var cookie = utils.get_cookie('im_livechat_session'); + if (cookie) { + var channel = JSON.parse(cookie); + var ready = session.rpc('/im_livechat/visitor_leave_session', {uuid: channel.uuid}); + ready.then(self._super()); + } + else { + this._super(); + } + }, + + /** + * Utils to correctly re-encode json string sent by server. + * Copied from StackOverflow. + */ + decode_server_cookie: function (val) { + if (val.indexOf('\\') === -1) { + return val; // not encoded + } + val = val.slice(1, -1).replace(/\\"/g, '"'); + val = val.replace(/\\(\d{3})/g, function(match, octal) { + return String.fromCharCode(parseInt(octal, 8)); + }); + return val.replace(/\\\\/g, '\\'); + }, +}); + +return { + LivechatButton: LivechatButton, +}; + +}); diff --git a/addons/website_livechat/static/src/js/thread_window.js b/addons/website_livechat/static/src/js/thread_window.js new file mode 100644 index 00000000000..a53d2bcddd5 --- /dev/null +++ b/addons/website_livechat/static/src/js/thread_window.js @@ -0,0 +1,34 @@ +odoo.define('website_livechat.ThreadWindow', function (require) { +"use strict"; + +var ThreadWindow = require('mail.ThreadWindow'); +var session = require('web.session'); + +/** + * This is the main widget for rendering small windows for mail.model.Thread. + * Almost all instances of this class are linked to a thread. The sole + * exception is the "blank" thread window. This window let us open another + * thread window, using this "blank" thread window. + */ +ThreadWindow.include({ + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + close: function () { + var self = this; + if (this.hasThread() && this._thread._type === "livechat" && this._threadWidget._messages.length == 0) { + session.rpc('/im_livechat/close_empty_livechat', {uuid: this._thread._uuid}); + } + else { + this._super(); + } + }, +}); + +return ThreadWindow; + +}); diff --git a/addons/website_livechat/static/src/scss/mail_thread_visitor_info.scss b/addons/website_livechat/static/src/scss/mail_thread_visitor_info.scss new file mode 100644 index 00000000000..2520b54fde7 --- /dev/null +++ b/addons/website_livechat/static/src/scss/mail_thread_visitor_info.scss @@ -0,0 +1,39 @@ +.o_web_client { + &:not(.o_no_thread_window) { + .o_w_visitor_info { + display:none !important; + } + } + &.o_no_thread_window { + .o_mail_thread { + position: sticky; + } + .o_w_visitor_info { + height: 87px; + border-bottom: 1px solid #ced4da; + position: fixed; + background: white; + width: 100%; + z-index: 1; + .o_w_visitor_status { + position: absolute; + bottom: 0; + right: 0; + &.o_w_visitor_disconnected { + color: red; + } + &.o_w_visitor_connected { + color: #00A09D; + } + } + } + .o_sub_w_visitor_info { + padding-top: 80px; + } + } +} + +.o_country_flag { + width:24px; + height: 16px; +} diff --git a/addons/website_livechat/static/src/xml/thread.xml b/addons/website_livechat/static/src/xml/thread.xml new file mode 100644 index 00000000000..a8872290b2a --- /dev/null +++ b/addons/website_livechat/static/src/xml/thread.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + <t t-extend="mail.widget.Thread.Content.ASC"> + <t t-jquery=".o_mail_thread_content" t-operation="replace"> + <t t-call="mail.widget.Thread.website.visitor.banner"/> + <div class="o_mail_thread_content o_sub_w_visitor_info"> + <t t-if="options.displayLoadMore" t-call="mail.widget.Thread.LoadMore"/> + <t t-call="mail.widget.Thread.Messages"/> + <t t-if="options.displayBottomThreadFreeSpace"> + <div class="o_thread_bottom_free_space"/> + </t> + </div> + </t> + </t> + + <t t-extend="mail.widget.Thread.Content.DESC"> + <t t-jquery=".o_mail_thread_content" t-operation="replace"> + <t t-call="mail.widget.Thread.website.visitor.banner"/> + <div class="o_mail_thread_content"> + <t t-if="options.messagesSeparatorPosition == 'top'" t-call="mail.MessagesSeparator"/> + <t t-set="messages" t-value="messages.slice().reverse()"/> + <t t-call="mail.widget.Thread.Messages"/> + <t t-if="options.displayLoadMore" t-call="mail.widget.Thread.LoadMore"/> + </div> + </t> + </t> + + <t t-name="mail.widget.Thread.website.visitor.banner"> + <t t-set="visitor" t-value="thread._visitor"/> + <t t-if="visitor"> + <div class="o_w_visitor_info o_thread_message o_mail_discussion d-flex"> + <div class="o_thread_message_sidebar align-self-center"> + <div class="o_thread_message_sidebar_image"> + <img alt="" + t-att-src="visitor.partner_id ? '/web/image/res.partner/' + visitor.partner_id + '/image_small' : '/mail/static/src/img/smiley/avatar.jpg'" + class="o_thread_message_avatar rounded-circle"/> + <span> + <i t-if="visitor.is_connected" aria-label="Visitor is connected" class="o_w_visitor_status o_w_visitor_connected fa fa-circle" role="img" title="Online"></i> + <i t-else="" aria-label="Visitor is offline" class="o_w_visitor_status o_w_visitor_disconnected fa fa-circle" role="img" title="Offline"></i> + </span> + </div> + </div> + <div class="o_thread_message_core pt-2 align-self-center"> + <div class="d-flex"> + <img t-if="visitor.country_code" t-attf-src='base/static/img/country_flags/#{visitor.country_code}.png' class="o_country_flag mr-2 mb-1"/> + <strong class="o_thread_author" t-esc="visitor.name"/> + <span><i class="fa fa-comment-o ml-4 mr-2" aria-label="Lang"/><t t-esc="visitor.lang"/></span> + <span t-if="visitor.website"><i class="fa fa-globe ml-4 mr-2" aria-label="Website"/><span t-esc="visitor.website"/></span> + </div> + <div class="o_thread_message_content"> + <div class="d-flex"> + <span><i class="fa fa-history mr-2" aria-label="History"/></span> + <p t-esc="visitor.history"/> + </div> + </div> + </div> + </div> + </t> + </t> +</templates> |
