[ADD] im_support
This rev. adds a new module enabling the livechat support from the webclient. It allows employee users to communicate with livechat operators of another database, assuming that a counterpart addon is installed on that database. Task 26762 Co-authored-by: Adrien Dieudonne <adr@odoo.com>
This commit is contained in:
co-authored by
Adrien Dieudonne
parent
10161f9d61
commit
2aeb763768
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Livechat Support',
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'description': """
|
||||
This module allows employee users to communicate with livechat operators
|
||||
of another database on which a counterpart addon is installed.
|
||||
""",
|
||||
'author': 'Odoo SA',
|
||||
'website': 'https://www.odoo.com',
|
||||
'depends': [
|
||||
'mail',
|
||||
],
|
||||
'data': [
|
||||
'views/assets.xml',
|
||||
],
|
||||
'qweb': [
|
||||
"static/src/xml/discuss.xml",
|
||||
"static/src/xml/systray.xml",
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import ir_http
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import hmac
|
||||
from hashlib import sha256
|
||||
|
||||
from odoo import models
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def session_info(self):
|
||||
result = super(Http, self).session_info()
|
||||
|
||||
if self.env.user.has_group('base.group_user'):
|
||||
icp = request.env['ir.config_parameter'].sudo()
|
||||
db_uuid = icp.get_param('database.uuid')
|
||||
db_secret = icp.get_param('database.secret')
|
||||
message = db_uuid + str(request.uid)
|
||||
token = hmac.new(message.encode('utf-8'), db_secret.encode('utf-8'), sha256).hexdigest()
|
||||
|
||||
result['db_uuid'] = db_uuid
|
||||
result['support_token'] = token
|
||||
result['support_origin'] = False # must be overridden to specify the correct origin
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,455 @@
|
||||
odoo.define('im_support.ChatManager', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module includes the ChatManager to handle the case of the Support
|
||||
* channel, allowing the users of the current database to communicate with
|
||||
* livechat operators from another database (the Support database).
|
||||
*/
|
||||
|
||||
var ChatManager = require('mail.ChatManager');
|
||||
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
var WebClient = require('web.WebClient');
|
||||
|
||||
var supportSession = require('im_support.SupportSession');
|
||||
var supportBus = require('im_support.SupportBus');
|
||||
|
||||
var _t = core._t;
|
||||
var CHANNEL_STATES = {
|
||||
CLOSED: 'closed',
|
||||
FOLDED: 'folded',
|
||||
OPEN: 'open',
|
||||
};
|
||||
var ODOOBOT_ID = "ODOOBOT"; // FIXME: should be exported by the ChatManager
|
||||
var POLL_TIMEOUT_DELAY = 1000 * 60 * 30; // 30 minutes
|
||||
var POLL_TIMEOUT_KEY = 'im_support.poll_timeout';
|
||||
var SUPPORT_CHANNEL_LIMIT = 30; // limit of messages to fetch
|
||||
var SUPPORT_CHANNEL_STATE_KEY = 'im_support.channel_state';
|
||||
|
||||
ChatManager.include({
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Overrides to prevent from calling the server with the Support channel
|
||||
* (which is a record on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
closeChatSession: function (channelID) {
|
||||
if (channelID === this.supportChannelUUID) {
|
||||
this.call('local_storage', 'setItem', SUPPORT_CHANNEL_STATE_KEY, CHANNEL_STATES.CLOSED);
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Overrides to prevent from calling the server with the Support channel
|
||||
* (which is a record on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
detachChannel: function (channelID) {
|
||||
if (channelID === this.supportChannelUUID) {
|
||||
this.call('local_storage', 'setItem', SUPPORT_CHANNEL_STATE_KEY, CHANNEL_STATES.OPEN);
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Overrides to prevent from calling the server with the Support channel
|
||||
* (which is a record on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
foldChannel: function (channelID, folded) {
|
||||
if (channelID === this.supportChannelUUID) {
|
||||
var value = folded ? CHANNEL_STATES.FOLDED : CHANNEL_STATES.OPEN;
|
||||
this.call('local_storage', 'setItem', SUPPORT_CHANNEL_STATE_KEY, value);
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Overrides to filter out the Support channel from the previews.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
getChannelsPreview: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function (channelsPreview) {
|
||||
return _.reject(channelsPreview, {id: self.supportChannelUUID});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Overrides to prevent from calling the server with the Support channel
|
||||
* (which is a record on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
getMentionPartnerSuggestions: function (channel) {
|
||||
if (channel && channel.id === this.supportChannelUUID) {
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Initialises the Support: checks if there is a pending chat session
|
||||
* between the user and Support, and if so, re-opens it.
|
||||
* Note: we can't directly override init(), because it is already called
|
||||
* when the include is applied, so we use this hook instead (called at
|
||||
* webclient startup)
|
||||
*/
|
||||
initSupport: function () {
|
||||
var self = this;
|
||||
this.supportChannelDef = null;
|
||||
this.supportChannelUUID = null;
|
||||
this.pollTimeout = null;
|
||||
this.pollingSupport = false;
|
||||
|
||||
// listen to notifications coming from Support longpolling
|
||||
supportBus.on('notification', this, this._onSupportNotification);
|
||||
|
||||
// check if there is a pending chat session with the Support
|
||||
var timeoutTimestamp = this.call('local_storage', 'getItem', POLL_TIMEOUT_KEY);
|
||||
var pollingDelay = timeoutTimestamp && (JSON.parse(timeoutTimestamp) - Date.now());
|
||||
if (pollingDelay && pollingDelay > 0) {
|
||||
var channelState = this.call('local_storage', 'getItem', SUPPORT_CHANNEL_STATE_KEY);
|
||||
this.startSupportLivechat(channelState).then(function () {
|
||||
var supportChannel = self.getChannel(self.supportChannelUUID);
|
||||
if (supportChannel.available) {
|
||||
self._startPollingSupport(pollingDelay);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Overrides to prevent from calling the server with the Support channel
|
||||
* (which is a record on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
markChannelAsSeen: function (channel) {
|
||||
if (channel.id === this.supportChannelUUID) {
|
||||
this._updateChannelUnreadCounter(channel, 0);
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Overrides to call a specific route for messages posted in the Support
|
||||
* channel.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
postMessage: function (data, options) {
|
||||
if (options.channelID && options.channelID === this.supportChannelUUID) {
|
||||
// ensure that the poll is active before posting the message
|
||||
if (!this.pollingSupport) {
|
||||
this._startPollingSupport();
|
||||
}
|
||||
return supportSession.rpc("/odoo_im_support/chat_post", {
|
||||
uuid: this.supportChannelUUID,
|
||||
message_content: data.content,
|
||||
});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Opens the support channel between a livechat operator from the Support
|
||||
* database and the current user (if there is an available operator).
|
||||
* Ensures to perform only once the request to create/retrieve the Support
|
||||
* channel.
|
||||
*
|
||||
* @param {string} [channelState=CHANNEL_STATES.OPEN] state of the Support
|
||||
* channel (see CHANNEL_STATES for accepted values)
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
startSupportLivechat: function (channelState) {
|
||||
var self = this;
|
||||
if (!this.supportChannelDef) {
|
||||
// retrieve or create the channel
|
||||
this.supportChannelDef = supportSession.rpc('/odoo_im_support/get_support_channel', {
|
||||
channel_uuid: session.support_token,
|
||||
db_uuid: session.db_uuid,
|
||||
user_name: session.name,
|
||||
});
|
||||
}
|
||||
return this.supportChannelDef.then(function (channel) {
|
||||
if (!channel) {
|
||||
// there is no channel (because there is no online operator, and
|
||||
// no support channel has been created yet), so create one to
|
||||
// open in a chat window
|
||||
channel = {
|
||||
available: false,
|
||||
support_channel: true,
|
||||
type: 'livechat',
|
||||
uuid: "support_unavailable",
|
||||
};
|
||||
}
|
||||
if (!channelState) {
|
||||
channelState = self.discussOpen ? CHANNEL_STATES.CLOSED : CHANNEL_STATES.OPEN;
|
||||
self.call('local_storage', 'setItem', SUPPORT_CHANNEL_STATE_KEY, channelState);
|
||||
}
|
||||
if (!self.supportChannelUUID) {
|
||||
// this part is only executed the first time the RPC is resolved
|
||||
self.supportChannelUUID = channel.uuid;
|
||||
|
||||
// add the channel to the ChatManager
|
||||
self._addChannel(_.extend(channel, {
|
||||
id: channel.uuid,
|
||||
is_minimized: _.contains([CHANNEL_STATES.OPEN, CHANNEL_STATES.FOLDED], channelState),
|
||||
state: channelState,
|
||||
}));
|
||||
|
||||
// display automatic messages in the channel
|
||||
if (!channel.available) {
|
||||
self._addSupportNotAvailableMessage();
|
||||
} else {
|
||||
self._addSupportWelcomeMessage();
|
||||
}
|
||||
} else {
|
||||
// the channel has already been added to the ChatManager, so
|
||||
// simply re-open it
|
||||
channel = self.getChannel(self.supportChannelUUID);
|
||||
if (self.discussOpen) {
|
||||
self.openChannel(channel);
|
||||
} else {
|
||||
channel.is_folded = channelState === CHANNEL_STATES.FOLDED;
|
||||
self.call('chat_window_manager', 'openChat', channel);
|
||||
}
|
||||
}
|
||||
}).fail(function () {
|
||||
self.do_warn(_t("The Support server can't be reached."));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Overrides to prevent from calling the server for messages of the Support
|
||||
* channel (which are records on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
toggleStarStatus: function (msgID) {
|
||||
var message = this.getMessage(msgID);
|
||||
if (_.contains(message.channel_ids, this.supportChannelUUID)) {
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Overrides to prevent from calling the server with the Support channel
|
||||
* (which is a record on the Support database).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
unsubscribe: function (channel) {
|
||||
if (channel.id === this.supportChannelUUID) {
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Adds a message in the Support channel indicating that this channel is
|
||||
* not available for now.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_addSupportNotAvailableMessage: function () {
|
||||
var msg = {
|
||||
author_id: ODOOBOT_ID,
|
||||
body: _t("None of our operators are available. <a href='https://www.odoo.com/help' " +
|
||||
"target='_blank'>Submit a ticket</a> to ask your question now."),
|
||||
channel_ids: [this.supportChannelUUID],
|
||||
id: Number.MAX_SAFE_INTEGER, // last message in the channel
|
||||
};
|
||||
this._addMessage(msg, {silent: true});
|
||||
},
|
||||
/**
|
||||
* Adds a welcome message as first message of the Support channel.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_addSupportWelcomeMessage: function () {
|
||||
var channel = this.getChannel(this.supportChannelUUID);
|
||||
if (channel.welcome_message) {
|
||||
var msg = {
|
||||
author_id: channel.operator,
|
||||
body: channel.welcome_message,
|
||||
channel_ids: [this.supportChannelUUID],
|
||||
id: -1, // first message of the channel
|
||||
};
|
||||
this._addMessage(msg, {silent: true});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Overrides to handle the Support channel case: fetches the messages from
|
||||
* the Support database in that case.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_fetchFromChannel: function (channel, options) {
|
||||
var self = this;
|
||||
if (channel.id === this.supportChannelUUID) {
|
||||
var domain = options && options.domain || [];
|
||||
var cache = this._getChannelCache(channel, domain);
|
||||
if (options && options.loadMore) {
|
||||
var minMessageID = cache.messages[0].id;
|
||||
domain = [['id', '<', minMessageID]].concat(domain);
|
||||
}
|
||||
|
||||
return supportSession.rpc('/odoo_im_support/fetch_messages', {
|
||||
domain: domain,
|
||||
channel_uuid: session.support_token,
|
||||
limit: SUPPORT_CHANNEL_LIMIT,
|
||||
}).then(function (msgs) {
|
||||
if (!cache.all_history_loaded) {
|
||||
cache.all_history_loaded = msgs.length < SUPPORT_CHANNEL_LIMIT;
|
||||
}
|
||||
cache.loaded = true;
|
||||
|
||||
_.each(msgs, function (msg) {
|
||||
_.extend(msg, {channel_ids: [self.supportChannelUUID]});
|
||||
self._addMessage(msg, {
|
||||
channel_id: self.supportChannelUUID,
|
||||
silent: true,
|
||||
domain: domain,
|
||||
});
|
||||
});
|
||||
var channelCache = self._getChannelCache(channel, domain);
|
||||
return channelCache.messages;
|
||||
});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_makeChannel: function (data) {
|
||||
var channel = this._super.apply(this, arguments);
|
||||
if (channel.id === this.supportChannelUUID) {
|
||||
channel.supportChannel = true;
|
||||
channel.name = _t('Support');
|
||||
channel.available = data.available;
|
||||
if (!channel.available) {
|
||||
channel.name += _t(' (offline)');
|
||||
}
|
||||
channel.welcome_message = data.welcome_message;
|
||||
channel.operator = data.operator;
|
||||
}
|
||||
return channel;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_makeMessage: function (data) {
|
||||
if (_.contains(data.channel_ids, this.supportChannelUUID) && data.author_id !== ODOOBOT_ID) {
|
||||
if (!data.author_id[0]) { // the author is the client
|
||||
data.author_id = [session.partner_id, session.name];
|
||||
} else { // the author is the operator
|
||||
data.author_id[0] = -1; // prevent from conflicting with partners of this instance
|
||||
var msg = this._super.apply(this, arguments);
|
||||
msg.author_redirect = false;
|
||||
msg.avatar_src = "/mail/static/src/img/odoo_o.png";
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Automatically stop polling for Support messages after a given delay of
|
||||
* inactivity.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} [pollingDelay=POLL_TIMEOUT_DELAY] the longpolling
|
||||
* timeout delay to set
|
||||
*/
|
||||
_setPollTimeout: function (pollingDelay) {
|
||||
pollingDelay = pollingDelay || POLL_TIMEOUT_DELAY;
|
||||
clearTimeout(this.pollTimeout);
|
||||
this.pollTimeout = setTimeout(this._stopPollingSupport.bind(this), pollingDelay);
|
||||
// save the timeout expiration datetime into the LocalStorage so that
|
||||
// we can re-open the Support channel if necessary on F5
|
||||
var timeoutTimestamp = Date.now() + pollingDelay;
|
||||
this.call('local_storage', 'setItem', POLL_TIMEOUT_KEY, timeoutTimestamp);
|
||||
},
|
||||
/**
|
||||
* Initiates a longpoll with the server hosting the Support channel.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} [pollingDelay=POLL_TIMEOUT_DELAY] the longpolling
|
||||
* timeout delay to set
|
||||
*/
|
||||
_startPollingSupport: function (pollingDelay) {
|
||||
this.pollingSupport = true;
|
||||
supportBus.add_channel(this.supportChannelUUID);
|
||||
supportBus.start_polling();
|
||||
this._setPollTimeout(pollingDelay);
|
||||
},
|
||||
/**
|
||||
* Stops the longpoll with the server hosting the Support channel.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_stopPollingSupport: function () {
|
||||
this.pollingSupport = false;
|
||||
supportBus.stop_polling();
|
||||
this.call('local_storage', 'removeItem', POLL_TIMEOUT_KEY);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handles poll notifications from the Support server.
|
||||
*
|
||||
* @private
|
||||
* @param {Object[]} notifications
|
||||
*/
|
||||
_onSupportNotification: function (notifications) {
|
||||
var self = this;
|
||||
if (notifications && notifications.length) {
|
||||
this._setPollTimeout();
|
||||
}
|
||||
_.each(notifications, function (notification) {
|
||||
if (notification[1]._type === 'history_command') {
|
||||
// ignore history requests
|
||||
return;
|
||||
}
|
||||
var msg = _.extend(notification[1], {
|
||||
channel_ids: [self.supportChannelUUID],
|
||||
});
|
||||
self._manageChannelNotification(msg);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Unfortunately, we can't override init() of ChatManager because it is called
|
||||
// before the include is applied, so we override the WebClient instead to call
|
||||
// an initialization hook for Livechat Support in the ChatManager service.
|
||||
WebClient.include({
|
||||
/**
|
||||
* Overrides to ask the ChatManager service to check whether there is a
|
||||
* pending chat session with Support, and if so, to re-open it.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
show_application: function () {
|
||||
this.call('chat_manager', 'initSupport');
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
odoo.define('im_support.chat_discuss', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module includes Discuss to handle the case of the Support channel.
|
||||
*/
|
||||
|
||||
var Discuss = require('mail.chat_discuss');
|
||||
|
||||
Discuss.include({
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Overrides to hide stars in the Support channel, as this feature does not
|
||||
* work in that channel.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_getThreadRenderingOptions: function () {
|
||||
var options = this._super.apply(this, arguments);
|
||||
if (this.channel.supportChannel) {
|
||||
options.display_stars = false;
|
||||
}
|
||||
return options;
|
||||
},
|
||||
/**
|
||||
* Overrides to hide the attachment button in the composer of the Support
|
||||
* channel, as this feature does not work in that channel. Also hides the
|
||||
* composer when no operator is available.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_setChannel: function (channel) {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
var $buttonAddAttachment = self.basicComposer.$('.o_composer_button_add_attachment');
|
||||
if (channel.supportChannel) {
|
||||
if (!channel.available) {
|
||||
self.basicComposer.do_hide();
|
||||
}
|
||||
$buttonAddAttachment.hide();
|
||||
} else {
|
||||
$buttonAddAttachment.show();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Overrides to handle the Support channel case (hide all buttons).
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_updateControlPanelButtons: function (channel) {
|
||||
this._super.apply(this, arguments);
|
||||
if (channel.supportChannel) {
|
||||
this.$buttons.find('button').hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
odoo.define('im_support.ExtendedChatWindow', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module includes ExtendedChatWindow to handle the case of the Support
|
||||
* channel.
|
||||
*/
|
||||
|
||||
|
||||
var ExtendedChatWindow = require('mail.ExtendedChatWindow');
|
||||
|
||||
ExtendedChatWindow.include({
|
||||
/**
|
||||
* Overrides to tweak the options in the case of the Support channel (no
|
||||
* star, and no input if no operator available).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var channel = this.call('chat_manager', 'getChannel', this.channel_id);
|
||||
if (channel && channel.supportChannel) {
|
||||
this.options.display_stars = false;
|
||||
if (!channel.available) {
|
||||
this.options.input_less = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Overrides to remove the attachment button in the Support channel as this
|
||||
* feature does not work in that channel.
|
||||
*
|
||||
* @note: this feature could be enabled by storing the attachments on the
|
||||
* Support server.
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var def = this._super.apply(this, arguments);
|
||||
var channel = this.call('chat_manager', 'getChannel', this.channel_id);
|
||||
if (channel && channel.supportChannel) {
|
||||
this.$('.o_composer .o_composer_button_add_attachment').remove();
|
||||
}
|
||||
return def;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
odoo.define('im_support.SupportBus', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module instantiates and exports the instance of the Bus, parameterized
|
||||
* to poll the Support server.
|
||||
*/
|
||||
|
||||
var supportSession = require('im_support.SupportSession');
|
||||
|
||||
var bus = require('bus.bus');
|
||||
|
||||
var Bus;
|
||||
if(typeof Storage !== "undefined"){
|
||||
Bus = bus.CrossTabBus;
|
||||
} else {
|
||||
Bus = bus.Bus;
|
||||
}
|
||||
|
||||
var params = {
|
||||
session: supportSession,
|
||||
pollRoute: '/longpolling/support_poll',
|
||||
};
|
||||
|
||||
return new Bus(params);
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
odoo.define('im_support.SupportSession', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module returns an instance of Session which is linked to the Support
|
||||
* server, allowing the current instance to communicate with the Support
|
||||
* server (CORS).
|
||||
*/
|
||||
|
||||
var session = require('web.session');
|
||||
var Session = require('web.Session');
|
||||
|
||||
return new Session(null, session.support_origin, {
|
||||
modules: odoo._modules,
|
||||
use_cors: true,
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
odoo.define('im_support.systray', function (require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This module adds a fake Support channel in the messaging menu of the systray.
|
||||
*/
|
||||
|
||||
var mailSystray = require('mail.systray');
|
||||
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
|
||||
var _t = core._t;
|
||||
var MessagingMenu = mailSystray.MessagingMenu;
|
||||
var SUPPORT_CHANNEL_ID = 'SupportChannel';
|
||||
|
||||
// Disable Support in mobile for design purposes (for now at least), and don't
|
||||
// add it to the messaging dropdown if it isn't available
|
||||
if (config.device.isMobile || !session.support_token || !session.support_origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessagingMenu.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.supportChannel = {
|
||||
id: SUPPORT_CHANNEL_ID,
|
||||
name: _t('Support'),
|
||||
image_src: '/mail/static/src/img/odoo_o.png',
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Overrides to add a className to the bottom part of the dropdown
|
||||
* (containing the Support channel), so that the css rules apply. This
|
||||
* className can't be added directly in the template, otherwise
|
||||
* this.$channels_preview would be a nodeset containing the bottom part as
|
||||
* well, and it will cause rendering issues when the dropdown is rerendered.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self.$('.o_mail_navbar_dropdown_bottom').addClass('o_mail_navbar_dropdown_channels');
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Overrides to handle the click on the fake Support channel.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onClickChannel: function (ev) {
|
||||
var channelID = $(ev.currentTarget).data('channel_id');
|
||||
if (channelID === SUPPORT_CHANNEL_ID) {
|
||||
this.call('chat_manager', 'startSupportLivechat');
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
.o_mail_navbar_item {
|
||||
.o_mail_navbar_dropdown {
|
||||
.o_mail_navbar_dropdown_bottom {
|
||||
border-top: 1px solid @gray-lighter-darker;
|
||||
box-sizing: content-box;
|
||||
|
||||
.o_channel_info {
|
||||
.o-flex-display();
|
||||
.o-align-items(center);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
|
||||
<t t-extend="mail.chat.Sidebar">
|
||||
<t t-jquery="hr" t-operation="before">
|
||||
<t t-foreach="channels" t-as="channel">
|
||||
<t t-if="channel.type == 'livechat'">
|
||||
<div t-attf-class="o_mail_chat_title_main o_mail_chat_channel_item #{(active_channel_id == channel.id) ? 'o_active': ''}"
|
||||
t-att-data-channel-id="channel.id">
|
||||
<span class="o_channel_name"><i class="fa fa-question-circle mr8"/>Odoo Support</span>
|
||||
<t t-set="counter" t-value="0"/>
|
||||
<t t-call="mail.chat.SidebarNeedaction"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-extend="mail.chat.MessagingMenu">
|
||||
<t t-jquery=".o_mail_navbar_dropdown" t-operation="append">
|
||||
<li t-if="widget.supportChannel" class="o_mail_navbar_dropdown_bottom">
|
||||
<t t-set="channel" t-value="widget.supportChannel"/>
|
||||
<t t-call="mail.chat.ChannelPreview"/>
|
||||
</li>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,16 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="assets_backend" name="im_support_backend assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/im_support/static/src/less/systray.less" type="text/less"/>
|
||||
|
||||
<script type="text/javascript" src="/im_support/static/src/js/chat_manager.js"></script>
|
||||
<script type="text/javascript" src="/im_support/static/src/js/discuss.js"></script>
|
||||
<script type="text/javascript" src="/im_support/static/src/js/extended_chat_window.js"></script>
|
||||
<script type="text/javascript" src="/im_support/static/src/js/support_bus.js"></script>
|
||||
<script type="text/javascript" src="/im_support/static/src/js/support_session.js"></script>
|
||||
<script type="text/javascript" src="/im_support/static/src/js/systray.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
Reference in New Issue
Block a user