[FIX] im_chat, im_livechat : split backend/frontend part of im_chat, refactor action binding for dropdown menu, redefine livechat bundle using new js module files, and correct the 'session' in im_livechat namespace.

This commit is contained in:
Jérome Maes
2015-03-24 11:48:33 +01:00
parent ebc75e1395
commit 48971f57f4
7 changed files with 653 additions and 538 deletions
+24 -28
View File
@@ -64,67 +64,63 @@
.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_disconnected {
display: block;
}
.oe_im_chatview .oe_im_chatview_header {
/* ####### Chat View : Header ####### */
.oe_im_chatview_header {
padding: 3px 6px 2px;
background: #DEDEDE;
background: -moz-linear-gradient(#FCFCFC, #DEDEDE);
background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE));
border-radius: 3px 3px 0 0;
-moz-border-radius: 3px 3px 0 0;
-webkit-border-radius: 3px 3px 0 0;
border-radius: 3px 3px 0 0;
border-bottom: 1px solid #AEB9BD;
cursor: pointer;
}
.oe_im_chatview .oe_im_chatview_header_name{
.oe_im_chatview_header .header_name{
max-width: 75%;
word-wrap: break-word;
display: inline-block;
height: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.oe_im_chatview .oe_im_chatview_right {
padding: 0;
background: transparent;
border: 0;
.oe_im_chatview_header .right_buttons {
float: right;
}
.oe_im_chatview_header .right_buttons > * {
display: inline-block;
border: 0;
cursor: pointer;
color: gray ;
background: transparent;
font-size: 16px;
line-height: 16px;
margin-left: 5px;
-webkit-appearance: none;
}
/* options */
.oe_im_chatview .oe_im_chatview_option_group{
z-index: 10;
}
.oe_im_chatview .oe_im_chatview_option_list{
.oe_im_chatview_header .right_buttons .dropdown-menu{
right: 0;
left: auto;
}
.oe_im_chatview .oe_im_chatview_option_list li{
.oe_im_chatview_header .right_buttons .dropdown-menu > li{
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.428571429;
color: #333;
color: #4c4c4c;
white-space: nowrap;
}
.oe_im_chatview .oe_im_chatview_right div, .oe_im_chatview .oe_im_chatview_right button{
cursor: pointer;
background: transparent;
border: 0;
margin-left: 5px;
-webkit-appearance: none;
font-size: 18px;
line-height: 16px;
font-weight: bold;
text-shadow: 0 1px 0 white;
opacity: 0.9;
display: inline-block;
}
.oe_im_chatview .oe_im_chatview_content {
overflow: auto;
height: 287px;
+108 -451
View File
@@ -9,423 +9,47 @@ var data = require('web.data');
var Model = require('web.Model');
var session = require('web.session');
var SystrayMenu = require('web.SystrayMenu');
var time = require('web.time');
var utils = require('web.utils');
var web_client = require('web.web_client');
var Widget = require('web.Widget');
var im_chat_common = require('im_chat.im_chat_common');
var _t = core._t;
var QWeb = core.qweb;
var NBR_LIMIT_HISTORY = 20;
var USERS_LIMIT = 20;
var ConversationManager = Widget.extend({
init: function(parent, options) {
var self = this;
this._super(parent);
this.options = _.clone(options) || {};
_.defaults(this.options, {
inputPlaceholder: _t("Say something..."),
defaultMessage: null,
defaultUsername: _t("Visitor"),
});
// business
this.sessions = {};
this.bus = bus.bus;
this.bus.on("notification", this, this.on_notification);
this.bus.options.im_presence = true;
// ui
this.set("right_offset", 0);
this.set("bottom_offset", 0);
this.on("change:right_offset", this, this.calc_positions);
this.on("change:bottom_offset", this, this.calc_positions);
// ########## CONVERSATION extentions ###############
this.set("window_focus", true);
this.on("change:window_focus", self, function(e) {
self.bus.options.im_presence = self.get("window_focus");
});
this.set("waiting_messages", 0);
this.on("change:waiting_messages", this, this.window_title_change);
$(window).on("focus", _.bind(this.window_focus, this));
$(window).on("blur", _.bind(this.window_blur, this));
this.window_title_change();
},
on_notification: function(notification) {
var self = this;
var channel = notification[0];
var message = notification[1];
var regex_uuid = new RegExp(/(\w{8}(-\w{4}){3}-\w{12}?)/g);
// Concern im_chat : if the channel is the im_chat.session or im_chat.status, or a 'private' channel (aka the UUID of a session)
if((Array.isArray(channel) && (channel[1] === 'im_chat.session' || channel[1] === 'im_chat.presence')) || (regex_uuid.test(channel))){
// message to display in the chatview
if (message.type === "message" || message.type === "meta") {
self.received_message(message);
}
// activate the received session
if(message.uuid){
this.apply_session(message);
}
// user status notification
if(message.im_status){
self.trigger("im_new_user_status", [message]);
}
}
},
// window focus unfocus beep and title
window_focus: function() {
this.set("window_focus", true);
this.set("waiting_messages", 0);
},
window_blur: function() {
this.set("window_focus", false);
},
window_beep: function() {
if (typeof(Audio) === "undefined") {
return;
}
var audio = new Audio();
var ext = audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3";
audio.src = session.url("/im_chat/static/src/audio/ting") + ext;
audio.play();
},
window_title_change: function() {
var title = undefined;
if (this.get("waiting_messages") !== 0) {
title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"));
this.window_beep();
}
if (! web_client || !web_client.set_title_part)
return;
web_client.set_title_part("im_messages", title);
},
apply_session: function(session, focus){
var self = this;
var conv = this.sessions[session.uuid];
if (! conv) {
if(session.state !== 'closed'){
conv = new Conversation(this, this, session, this.options);
conv.appendTo($("body"));
conv.on("destroyed", this, _.bind(this.delete_session, this));
this.sessions[session.uuid] = conv;
this.calc_positions();
}
}else{
conv.set("session", session);
}
conv && this.trigger("im_session_activated", conv);
if (focus)
conv.focus();
return conv;
},
activate_session: function(session, focus) {
var self = this;
var active_session = _.clone(session);
active_session.state = 'open';
var conv = this.apply_session(active_session, focus);
if(session.state !== 'open'){
conv.update_fold_state('open');
}
return conv;
},
delete_session: function(uuid){
delete this.sessions[uuid];
this.calc_positions();
},
received_message: function(message) {
im_chat_common.ConversationManager.include({
_message_receive: function(message){
var self = this;
var session_id = message.to_id[0];
var uuid = message.to_id[1];
var from_id = message.from_id ? message.from_id[0] : false;
var current_user = session ? session.uid : false;
if (! this.get("window_focus") && from_id != current_user) {
this.set("waiting_messages", this.get("waiting_messages") + 1);
}
var conv = this.sessions[uuid];
var conv = this.sessions[message.to_id[1]];
if(!conv){
// fetch the session, and init it with the message
var def_session = new Model("im_chat.session").call("session_info", [], {"ids" : [session_id]}).then(function(session){
conv = self.activate_session(session, false);
conv.received_message(message);
conv = self.session_apply(session, {'force_open': true});
conv.message_receive(message);
});
}else{
conv.received_message(message);
this._super(message);
}
},
calc_positions: function() {
var self = this;
var current = this.get("right_offset");
_.each(this.sessions, function(s) {
s.set("bottom_position", self.get("bottom_offset"));
s.set("right_position", current);
current += s.$().outerWidth(true);
});
},
destroy: function() {
$(window).off("unload", this.unload);
$(window).off("focus", this.window_focus);
$(window).off("blur", this.window_blur);
return this._super();
}
});
var Conversation = Widget.extend({
className: "openerp_style oe_im_chatview",
events: {
"keydown input": "keydown",
"click .oe_im_chatview_options" : "click_options",
"click .oe_im_chatview_close": "click_close",
"click .oe_im_chatview_header": "click_header"
im_chat_common.Conversation.include({
// user actions
prepare_action_menu: function(){
this._super();
this._add_action(_t('Shortcuts'), 'im_chat_option_shortcut', 'fa fa-info-circle', this.action_shorcode);
this._add_action(_t('Quit discussion'), 'im_chat_option_quit', 'fa fa-minus-square', this.action_quit_session);
},
init: function(parent, c_manager, session, options) {
this._super(parent);
this.c_manager = c_manager;
this.options = options || {};
this.loading_history = true;
this.set("messages", []);
this.set("session", session);
this.set("right_position", 0);
this.set("bottom_position", 0);
this.set("pending", 0);
this.inputPlaceholder = this.options.defaultInputPlaceholder;
},
start: function() {
var self = this;
self.$().append(QWeb.render("im_chat.Conversation", {widget: self}));
self.$().hide();
self.on("change:session", self, self.update_session);
self.on("change:right_position", self, self.calc_pos);
self.on("change:bottom_position", self, self.calc_pos);
self.full_height = self.$().height();
self.calc_pos();
self.on("change:pending", self, _.bind(function() {
if (self.get("pending") === 0) {
self.$(".oe_im_chatview_nbr_messages").text("");
} else {
self.$(".oe_im_chatview_nbr_messages").text("(" + self.get("pending") + ")");
}
}, self));
// messages business
self.on("change:messages", this, this.render_messages);
self.$('.oe_im_chatview_content').on('scroll',function(){
if($(this).scrollTop() === 0){
self.load_history();
}
});
self.load_history();
self.$().show();
// prepare the header and the correct state
self.update_session();
self.init_options();
},
init_options: function(){
this.define_options();
if(this.$('.oe_im_chatview_option_list li').length === 0){
this.$('.oe_im_chatview_option_group').hide();
}
},
define_options: function(){
// add here the option to put in the dropdown menu
this._add_option("Shortcuts", "option_shortcut", "fa fa-info-circle");
this.$('.oe_im_chatview_option_list .option_shortcut').on('click', _.bind(this.click_option_shortcut, this));
// quit the conversation
this._add_option('Quit discussion', 'im_chat_option_quit', 'fa fa-minus-square');
this.$('.oe_im_chatview_option_list .im_chat_option_quit').on('click', this, _.bind(this.action_quit_conversation, this));
},
_add_option: function(label, style_class, icon_fa_class){
if(icon_fa_class){
label = '<i class="'+icon_fa_class+'"></i> ' + label;
}
this.$('.oe_im_chatview_option_list').append('<li class="'+style_class+'">'+label+'</li>');
},
action_quit_conversation: function(){
var self = this;
var Session = new Model("im_chat.session");
return Session.call("quit_user", [this.get("session").uuid]).then(function(res) {
if(! res){
core.bus.trigger('display_notification_warning', _t("Warning"), _t("You are only 2 identified users. Just close the conversation to leave."));
}
});
},
show: function(){
this.$().animate({
height: this.full_height
});
this.set("pending", 0);
},
hide: function(){
this.$().animate({
height: this.$(".oe_im_chatview_header").outerHeight()
});
},
calc_pos: function() {
this.$().css("right", this.get("right_position"));
this.$().css("bottom", this.get("bottom_position"));
},
update_fold_state: function(state){
return new Model("im_chat.session").call("update_state", [], {"uuid" : this.get("session").uuid, "state" : state});
},
update_session: function(){
// built the name
var names = [];
_.each(this.get("session").users, function(user){
if( (session.uid !== user.id) && !(_.isUndefined(session.uid) && !user.id) ){
names.push(user.name);
}
});
this.$(".oe_im_chatview_header_name").text(names.join(", "));
this.$(".oe_im_chatview_header_name").attr('title', names.join(", "));
// update the fold state
if(this.get("session").state){
if(this.get("session").state === 'closed'){
this.destroy();
}else{
if(this.get("session").state === 'open'){
this.show();
}else{
this.hide();
}
}
}
},
load_history: function(){
var self = this;
if(this.loading_history){
var data = {uuid: self.get("session").uuid, limit: NBR_LIMIT_HISTORY};
var lastid = _.first(this.get("messages")) ? _.first(this.get("messages")).id : false;
if(lastid){
data.last_id = lastid;
}
session.rpc("/im_chat/history", data).then(function(messages){
if(messages){
self.insert_messages(messages);
if(messages.length != NBR_LIMIT_HISTORY){
self.loading_history = false;
}
}else{
self.loading_history = false;
}
});
}
},
received_message: function(message) {
if (this.get('session').state === 'open') {
this.set("pending", 0);
} else {
this.set("pending", this.get("pending") + 1);
}
this.insert_messages([message]);
this._go_bottom();
},
send_message: function(message, type) {
var self = this;
var send_it = function() {
return session.rpc("/im_chat/post", {uuid: self.get("session").uuid, message_type: type, message_content: message});
};
var tries = 0;
send_it().fail(function(error, e) {
e.preventDefault();
tries += 1;
if (tries < 3)
return send_it();
});
},
insert_messages: function(messages){
var self = this;
var get_anonymous_name = function(){
var name = self.options.defaultUsername;
_.each(self.get('session').users, function(u){
if(!u.id){
name = u.name;
}
});
return name;
};
// avoid duplicated messages
messages = _.filter(messages, function(m){ return !_.contains(_.pluck(self.get("messages"), 'id'), m.id) ; });
// escape the message content and set the timezone
_.map(messages, function(m){
if(!m.from_id){
m.from_id = [false, get_anonymous_name()];
}
m.create_date = moment(time.str_to_datetime(m.create_date)).format('YYYY-MM-DD HH:mm:ss');
return m;
});
this.set("messages", _.sortBy(this.get("messages").concat(messages), function(m){ return m.id; }));
},
render_messages: function(){
var self = this;
var res = {};
var last_date_day, last_user_id = -1;
_.each(this.get("messages"), function(current){
// add the url of the avatar for all users in the conversation
current.from_id[2] = session.url(_.str.sprintf("/im_chat/image/%s/%s", self.get('session').uuid, current.from_id[0]));
var date_day = current.create_date.split(" ")[0];
if(date_day !== last_date_day){
res[date_day] = [];
last_user_id = -1;
}
last_date_day = date_day;
if(current.type == "message"){ // traditionnal message
if(last_user_id === current.from_id[0]){
_.last(res[date_day]).push(current);
}else{
res[date_day].push([current]);
}
last_user_id = current.from_id[0];
}else{ // meta message
res[date_day].push([current]);
last_user_id = -1;
}
});
// render and set the content of the chatview
// TODO jem : when refactoring this, don't forget to pre-process date in this function before render quweb template
// since, moment will not be define in qweb on the website pages, because the helper (see csn) is in core.js and cannot be
// imported in the frontend.
this.$('.oe_im_chatview_content_bubbles').html($(QWeb.render("im_chat.Conversation_content", {"list": res})));
this._go_bottom();
},
keydown: function(e) {
if(e && e.which == 27) {
if(this.$el.prev().find('.oe_im_chatview_input').length > 0){
this.$el.prev().find('.oe_im_chatview_input').focus();
}else{
this.$el.next().find('.oe_im_chatview_input').focus();
}
e.stopPropagation();
this.update_fold_state('closed');
}
if(e && e.which !== 13) {
return;
}
var mes = this.$("input").val();
if (! mes.trim()) {
return;
}
this.$("input").val("");
this.send_message(mes, "message");
},
_go_bottom: function() {
this.$(".oe_im_chatview_content").scrollTop(this.$(".oe_im_chatview_content").get(0).scrollHeight);
},
add_user: function(user){
return new Model("im_chat.session").call("add_user", [this.get("session").uuid , user.id]);
},
focus: function() {
this.$(".oe_im_chatview_input").focus();
},
click_options: function(e){
this.$('.oe_im_chatview_options').dropdown();
},
click_option_shortcut: function(){
web_client.action_manager.do_action({
action_shorcode: function(e){
return web_client.action_manager.do_action({
type: 'ir.actions.act_window',
name : 'Shortcode',
name : _t('Shortcode'),
res_model: 'im_chat.shortcode',
view_mode: 'tree,form',
view_type: 'tree',
@@ -438,23 +62,46 @@ var Conversation = Widget.extend({
}
});
},
click_header: function(event){
var classes = event.target.className.split(' ');
if(_.contains(classes, 'oe_im_chatview_header_name') || _.contains(classes, 'oe_im_chatview_header')){
this.update_fold_state();
action_quit_session: function(e){
var self = this;
var Session = new Model("im_chat.session");
return Session.call("quit_user", [this.get("session").uuid]).then(function(res) {
if(! res){
self.do_warn(_t("Warning"), _t("You are only 2 identified users. Just close the conversation to leave."));
}
});
},
// session
session_update_state: function(state){
var self = this;
var args = arguments;
var super_call = this._super;
// broadcast the state changing
return new Model("im_chat.session").call("update_state", [], {"uuid" : this.get("session").uuid, "state" : state}).then(function(){
super_call.apply(self, args);
});
},
// window title
window_title_change: function() {
this.super();
var title = undefined;
if (this.get("waiting_messages") !== 0) {
title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"))
}
web_client.set_title_part("im_messages", title);
},
click_close: function(event) {
this.update_fold_state('closed');
// TODO : change this way
add_user: function(user){
return new Model("im_chat.session").call("add_user", [this.get("session").uuid , user.id]);
},
destroy: function() {
this.trigger("destroyed", this.get('session').uuid);
return this._super();
}
});
// ###### BACKEND : contact panel, top menu button #########
var UserWidget = Widget.extend({
"template": "im_chat.UserWidget",
template: "im_chat.UserWidget",
events: {
"click": "activate_user",
},
@@ -477,7 +124,7 @@ var UserWidget = Widget.extend({
this.$(".oe_im_user_online").attr('src', img_src);
},
activate_user: function() {
this.trigger("activate_user", this.get("id"));
this.trigger("user_clicked", this.get("id"));
},
});
@@ -496,43 +143,55 @@ var InstantMessaging = Widget.extend({
this.users = [];
this.widgets = {};
this.c_manager = new ConversationManager(this);
this.on("change:right_offset", this.c_manager, _.bind(function() {
this.c_manager.set("right_offset", this.get("right_offset"));
}, this));
this.user_search_dm = new utils.DropMisordered();
// listen bus
this.bus = bus.bus;
this.bus.on("notification", this, this.on_notification);
},
start: function() {
var self = this;
// ui
this.conv_manager = new im_chat_common.ConversationManager(this);
this.on("change:right_offset", this.conv_manager, _.bind(function() {
this.conv_manager.set("right_offset", this.get("right_offset"));
}, this));
$(window).scroll(_.bind(this.position_compute, this));
$(window).resize(_.bind(this.position_compute, this));
this.$el.css("right", -this.$el.outerWidth());
$(window).scroll(_.bind(this.calc_box, this));
$(window).resize(_.bind(this.calc_box, this));
this.calc_box();
this.position_compute();
this.on("change:current_search", this, this.search_users_status);
// business
this.on("change:current_search", this, this.user_search);
// add a drag & drop listener
self.c_manager.on("im_session_activated", self, function(conv) {
self.conv_manager.on("im_session_activated", self, function(conv) {
conv.$el.droppable({
drop: function(event, ui) {
conv.add_user(ui.draggable.data("user"));
}
});
});
// add a listener for the update of users status
this.c_manager.on("im_new_user_status", this, this.update_users_status);
// fetch the unread message and the recent activity (e.i. to re-init in case of refreshing page)
session.rpc("/im_chat/init",{}).then(function(notifications) {
return session.rpc("/im_chat/init", {}).then(function(notifications) {
_.each(notifications, function(notif){
self.c_manager.on_notification(notif);
self.conv_manager.on_notification(notif, {'load_history': true});
});
// start polling
bus.bus.start_polling();
self.bus.start_polling();
});
return;
},
calc_box: function() {
on_notification: function(notification){
var channel = notification[0];
var message = notification[1];
// user status notification
if(channel[1] === 'im_chat.presence'){
if(message.im_status){
this.user_update_status([message]);
}
}
},
// ui
position_compute: function() {
var $topbar = window.$('#oe_main_menu_navbar'); // .oe_topbar is replaced with .navbar of bootstrap3
var top = $topbar.offset().top + $topbar.height();
top = Math.max(top - $(window).scrollTop(), 0);
@@ -542,30 +201,8 @@ var InstantMessaging = Widget.extend({
input_change: function() {
this.set("current_search", this.$(".oe_im_searchbox").val());
},
search_users_status: function(e) {
var user_model = new Model("res.users");
var self = this;
return this.user_search_dm.add(user_model.call("im_search", [this.get("current_search"),
USERS_LIMIT], {context:new data.CompoundContext()})).then(function(result) {
self.$(".oe_im_input").val("");
var old_widgets = self.widgets;
self.widgets = {};
self.users = [];
_.each(result, function(user) {
user.image_url = session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: user.id});
var widget = new UserWidget(self, user);
widget.appendTo(self.$(".oe_im_users"));
widget.on("activate_user", self, self.activate_user);
self.widgets[user.id] = widget;
self.users.push(user);
});
_.each(old_widgets, function(w) {
w.destroy();
});
});
},
switch_display: function() {
this.calc_box();
this.position_compute();
var fct = _.bind(function(place) {
this.set("right_offset", place + this.$el.outerWidth());
this.$(".oe_im_searchbox").focus();
@@ -583,21 +220,43 @@ var InstantMessaging = Widget.extend({
return;
}
// update the list of user status when show the IM
this.search_users_status();
this.user_search();
this.$el.animate({
right: 0,
}, opt);
}
this.shown = ! this.shown;
},
activate_user: function(user_id) {
// user methods
user_search: function(e) {
var self = this;
var user_model = new Model("res.users");
return user_model.call('im_search', [this.get("current_search"), USERS_LIMIT]).then(function(result){
self.$(".oe_im_input").val("");
var old_widgets = self.widgets;
self.widgets = {};
self.users = [];
_.each(result, function(user) {
user.image_url = session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: user.id});
var widget = new UserWidget(self, user);
widget.appendTo(self.$(".oe_im_users"));
widget.on("user_clicked", self, self.user_clicked);
self.widgets[user.id] = widget;
self.users.push(user);
});
_.each(old_widgets, function(w) {
w.destroy();
});
});
},
user_clicked: function(user_id) {
var self = this;
var sessions = new Model("im_chat.session");
return sessions.call("session_get", [user_id]).then(function(session) {
self.c_manager.activate_session(session, true);
self.conv_manager.session_apply(session, {'focus': true, 'force_open': true});
});
},
update_users_status: function(users_list){
user_update_status: function(users_list){
var self = this;
_.each(users_list, function(el) {
if (self.widgets[el.id]) {
@@ -637,8 +296,6 @@ Users.call('has_group', ['base.group_user']).done(function(is_employee) {
});
return {
Conversation: Conversation,
ConversationManager: ConversationManager,
InstantMessaging: InstantMessaging,
UserWidget: UserWidget,
};
@@ -0,0 +1,433 @@
odoo.define('im_chat.im_chat_common', function (require) {
"use strict";
// to do: make this work in website
var bus = require('bus.bus');
var core = require('web.core');
var session = require('web.session');
var time = require('web.time');
var Widget = require('web.Widget');
var _t = core._t;
var QWeb = core.qweb;
var NBR_LIMIT_HISTORY = 20;
var ConversationManager = Widget.extend({
init: function(parent, options) {
var self = this;
this._super(parent);
this.options = _.clone(options) || {};
_.defaults(this.options, {
inputPlaceholder: _t("Say something..."),
defaultMessage: null,
defaultUsername: _t("Visitor"),
force_open: false,
load_history: true,
focus: false,
});
// business
this.sessions = {};
this.bus = bus.bus;
this.bus.on("notification", this, this.on_notification);
this.bus.options.im_presence = true;
// ui
this.set("right_offset", 0);
this.set("bottom_offset", 0);
this.on("change:right_offset", this, this.calc_positions);
this.on("change:bottom_offset", this, this.calc_positions);
this.set("window_focus", true);
this.on("change:window_focus", self, function(e) {
self.bus.options.im_presence = self.get("window_focus");
});
this.set("waiting_messages", 0);
this.on("change:waiting_messages", this, this.window_title_change);
$(window).on("focus", _.bind(this.window_focus, this));
$(window).on("blur", _.bind(this.window_blur, this));
this.window_title_change();
},
on_notification: function(notification, options) {
var self = this;
var channel = notification[0];
var message = notification[1];
var regex_uuid = new RegExp(/(\w{8}(-\w{4}){3}-\w{12}?)/g);
// Concern im_chat : if the channel is the im_chat.session or im_chat.status, or a 'private' channel (aka the UUID of a session)
if((Array.isArray(channel) && (channel[1] === 'im_chat.session')) || (regex_uuid.test(channel))){
// message to display in the chatview
if (message.type === "message" || message.type === "meta") {
self.message_receive(message);
}
// activate the received session
if(message.uuid){
this.session_apply(message, options);
}
}
},
// window focus unfocus beep and title
window_focus: function() {
this.set("window_focus", true);
this.set("waiting_messages", 0);
},
window_blur: function() {
this.set("window_focus", false);
},
window_beep: function() {
if (typeof(Audio) === "undefined") {
return;
}
var audio = new Audio();
var ext = audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3";
audio.src = session.url("/im_chat/static/src/audio/ting") + ext;
audio.play();
},
window_title_change: function() {
var title = undefined;
if (this.get("waiting_messages") !== 0) {
title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"));
this.window_beep();
}
},
// sessions and messages
session_apply: function(active_session, options){
// options used by this function : force_open and focus (load_history can be usefull)
var self = this;
options = _.extend(_.clone(this.options), options || {});
// force open
var session = _.clone(active_session);
if(options['force_open']){
session.state = 'open';
}
// create/get the conversation widget
var conv = this.sessions[session.uuid];
if (! conv) {
if(session.state !== 'closed'){
conv = new Conversation(this, this, session, options);
conv.appendTo($("body"));
conv.on("destroyed", this, _.bind(this.session_delete, this));
this.sessions[session.uuid] = conv;
this.calc_positions();
}
}else{
conv.set("session", session);
}
// if force open, broadcast it
if(options['force_open'] && active_session.state !== 'open'){
conv.session_update_state('open');
}
// apply the focus
if (options['focus']){
conv.focus();
}
return conv;
},
session_delete: function(uuid){
delete this.sessions[uuid];
this.calc_positions();
},
message_receive: function(message) {
var self = this;
var session_id = message.to_id[0];
var uuid = message.to_id[1];
var from_id = message['from_id'] ? message['from_id'][0] : false;
if (! this.get("window_focus") && from_id != this.get_current_uid()) {
this.set("waiting_messages", this.get("waiting_messages") + 1);
}
this._message_receive(message);
},
_message_receive: function(message){
var uuid = message.to_id[1];
var conv = this.sessions[uuid];
conv.message_receive(message);
},
// others
get_current_uid: function(){
return session && !_.isUndefined(session.uid) ? session.uid : false;
},
calc_positions: function() {
var self = this;
var current = this.get("right_offset");
_.each(this.sessions, function(s) {
s.set("bottom_position", self.get("bottom_offset"));
s.set("right_position", current);
current += s.$().outerWidth(true);
});
},
destroy: function() {
$(window).off("unload", this.unload);
$(window).off("focus", this.window_focus);
$(window).off("blur", this.window_blur);
return this._super();
}
});
var Conversation = Widget.extend({
className: "openerp_style oe_im_chatview",
events: {
"keydown input": "keydown",
"click .oe_im_chatview_close": "click_close",
"click .oe_im_chatview_header": "click_header"
},
init: function(parent, c_manager, session, options) {
this._super(parent);
this.c_manager = c_manager;
this.options = options || {};
this.loading_history = true;
this.set("messages", []);
this.set("session", session);
this.set("right_position", 0);
this.set("bottom_position", 0);
this.set("pending", 0);
this.inputPlaceholder = this.options.defaultInputPlaceholder;
},
start: function() {
var self = this;
self._super.apply(this, arguments);
self.prepare_action_menu();
// ui
self.$().append(QWeb.render("im_chat.Conversation", {widget: self}));
self.on("change:right_position", self, self.calc_pos);
self.on("change:bottom_position", self, self.calc_pos);
self.on("change:pending", self, _.bind(function() {
if (self.get("pending") === 0) {
self.$(".nbr_messages").text("");
} else {
self.$(".nbr_messages").text("(" + self.get("pending") + ")");
}
}, self));
self.full_height = self.$().height();
self.calc_pos();
// business
self.bind_action_menu();
self.on("change:session", self, self.session_update);
self.on("change:messages", self, self.render_messages);
self.$('.oe_im_chatview_content').on('scroll',function(){
if($(this).scrollTop() === 0){
self.message_history();
}
});
if(self.options['load_history']){ // load history if asked
self.message_history();
}
// prepare the header and the correct state
self.session_update();
},
// action menu
_add_action: function(label, style_class, icon_fa_class, callback){
this.actions.push({
'label': label,
'class': style_class,
'icon_class': icon_fa_class,
'callback': callback,
});
},
prepare_action_menu: function(){
// override this method to add action with _add_action()
this.actions = [];
},
bind_action_menu: function(){
var self = this;
_.each(this.actions, function(action){
if(action.callback){
self.$('.button_option_group .' + action.class).on('click', _.bind(action.callback, self));
}
});
},
// ui
show: function(){
this.$().animate({
height: this.full_height
});
this.set("pending", 0);
},
hide: function(){
this.$().animate({
height: this.$(".oe_im_chatview_header").outerHeight()
});
},
calc_pos: function() {
this.$().css("right", this.get("right_position"));
this.$().css("bottom", this.get("bottom_position"));
},
// session
session_update_state: function(state){
var session = this.get('session');
session.state = state;
this.set('session', session);
},
session_update: function(){
// built the name
var names = [];
_.each(this.get("session").users, function(user){
if( (session.uid !== user.id) && !(_.isUndefined(session.uid) && !user.id) ){
names.push(user.name);
}
});
this.$(".header_name").text(names.join(", "));
this.$(".header_name").attr('title', names.join(", "));
// update the fold state
if(this.get("session").state){
if(this.get("session").state === 'closed'){
this.destroy();
}else{
if(this.get("session").state === 'open'){
this.show();
}else{
this.hide();
}
}
}
},
// messages
message_history: function(){
var self = this;
if(this.loading_history){
var data = {uuid: self.get("session").uuid, limit: NBR_LIMIT_HISTORY};
var lastid = _.first(this.get("messages")) ? _.first(this.get("messages")).id : false;
if(lastid){
data.last_id = lastid;
}
session.rpc("/im_chat/history", data).then(function(messages){
if(messages){
self.insert_messages(messages);
if(messages.length != NBR_LIMIT_HISTORY){
self.loading_history = false;
}
}else{
self.loading_history = false;
}
});
}
},
message_receive: function(message) {
if (this.get('session').state === 'open') {
this.set("pending", 0);
} else {
this.set("pending", this.get("pending") + 1);
}
this.insert_messages([message]);
this._go_bottom();
},
message_send: function(message, type) {
var self = this;
var send_it = function() {
return session.rpc("/im_chat/post", {uuid: self.get("session").uuid, message_type: type, message_content: message});
};
var tries = 0;
send_it().fail(function(error, e) {
e.preventDefault();
tries += 1;
if (tries < 3)
return send_it();
});
},
insert_messages: function(messages){
var self = this;
// avoid duplicated messages
messages = _.filter(messages, function(m){ return !_.contains(_.pluck(self.get("messages"), 'id'), m.id) ; });
// escape the message content and set the timezone
_.map(messages, function(m){
if(!m.from_id){
m.from_id = [false,self. get_anonymous_name()];
}
m.create_date = moment(time.str_to_datetime(m.create_date)).format('YYYY-MM-DD HH:mm:ss');
return m;
});
this.set("messages", _.sortBy(this.get("messages").concat(messages), function(m){ return m.id; }));
},
render_messages: function(){
var self = this;
var res = {};
var last_date_day, last_user_id = -1;
_.each(this.get("messages"), function(current){
// add the url of the avatar for all users in the conversation
current.from_id[2] = session.url(_.str.sprintf("/im_chat/image/%s/%s", self.get('session').uuid, current.from_id[0]));
var date_day = current.create_date.split(" ")[0];
if(date_day !== last_date_day){
res[date_day] = [];
last_user_id = -1;
}
last_date_day = date_day;
if(current.type == "message"){ // traditionnal message
if(last_user_id === current.from_id[0]){
_.last(res[date_day]).push(current);
}else{
res[date_day].push([current]);
}
last_user_id = current.from_id[0];
}else{ // meta message
res[date_day].push([current]);
last_user_id = -1;
}
});
// render and set the content of the chatview
// TODO jem : when refactoring this, don't forget to pre-process date in this function before render quweb template
// since, moment will not be define in qweb on the website pages, because the helper (see csn) is in core.js and cannot be
// imported in the frontend.
this.$('.oe_im_chatview_content_bubbles').html($(QWeb.render("im_chat.Conversation_content", {"list": res})));
this._go_bottom();
},
// utils and event
get_anonymous_name: function(){
var name = this.options["defaultUsername"];
_.each(this.get('session').users, function(u){
if(!u.id){
name = u.name;
}
});
return name;
},
keydown: function(e) {
if(e && e.which == 27) {
if(this.$el.prev().find('.oe_im_chatview_input').length > 0){
this.$el.prev().find('.oe_im_chatview_input').focus();
}else{
this.$el.next().find('.oe_im_chatview_input').focus();
}
e.stopPropagation();
this.session_update_state('closed');
}
if(e && e.which !== 13) {
return;
}
var mes = this.$("input").val();
if (! mes.trim()) {
return;
}
this.$("input").val("");
this.message_send(mes, "message");
},
_go_bottom: function() {
this.$(".oe_im_chatview_content").scrollTop(this.$(".oe_im_chatview_content").get(0).scrollHeight);
},
focus: function() {
this.$(".oe_im_chatview_input").focus();
},
click_header: function(event){
var classes = event.target.className.split(' ');
if(_.contains(classes, 'header_name') || _.contains(classes, 'oe_im_chatview_header')){
this.session_update_state();
}
},
click_close: function(event) {
this.session_update_state('closed');
},
destroy: function() {
this.trigger("destroyed", this.get('session').uuid);
return this._super();
}
});
return {
Conversation: Conversation,
ConversationManager: ConversationManager,
};
});
+15 -10
View File
@@ -2,16 +2,21 @@
<templates xml:space="preserve">
<t t-name="im_chat.Conversation">
<div class="oe_im_chatview_header">
<span class="oe_im_chatview_header_name"></span>
<span class="oe_im_chatview_nbr_messages"/>
<span class="oe_im_chatview_right">
<div class="btn-group oe_im_chatview_option_group">
<span class="oe_im_chatview_options dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cogs"></i>
</span>
<ul class="dropdown-menu oe_im_chatview_option_list" role="menu">
<!-- options -->
</ul>
<span class="header_name"></span>
<span class="nbr_messages"/>
<span class="right_buttons">
<!-- options -->
<div t-if="widget.actions.length !== 0" class="btn-group button_option_group">
<span class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cogs"></i>
</span>
<ul class="dropdown-menu" role="menu">
<t t-foreach="widget.actions" t-as="action">
<li t-att-class="action.class">
<i t-att-class="action.icon_class"></i> <t t-esc="action.label"/>
</li>
</t>
</ul>
</div>
<div class="oe_im_chatview_close">×</div>
</span>
+1
View File
@@ -6,6 +6,7 @@
<xpath expr="." position="inside">
<link rel="stylesheet" href="/im_chat/static/src/css/im_common.css"/>
<link rel="stylesheet" href="/im_chat/static/src/css/im_chat.css"/>
<script type="text/javascript" src="/im_chat/static/src/js/im_chat_common.js"></script>
<script type="text/javascript" src="/im_chat/static/src/js/im_chat.js"></script>
</xpath>
</template>
+25 -31
View File
@@ -2,12 +2,12 @@ odoo.define('im_livechat.im_livechat', function (require) {
"use strict";
var bus = require('bus.bus');
var im_chat = require('im_chat.im_chat');
var core = require('web.core');
var session = require('web.session');
var user_session = require('web.session');
var time = require('web.time');
var utils = require('web.utils');
var Widget = require('web.Widget');
var im_chat_common = require('im_chat.im_chat_common');
var _t = core._t;
var QWeb = core.qweb;
@@ -19,16 +19,12 @@ var COOKIE_NAME = 'livechat_conversation';
The state of anonymous session is hold by the client and not the server.
Override the method managing the state of normal conversation.
*/
im_chat.Conversation.include({
im_chat_common.Conversation.include({
init: function(){
this._super.apply(this, arguments);
this.shown = true;
this.loading_history = true; // since the session is kept after a refresh, anonymous can reload their history
this.feedback = false;
},
define_options: function(){
// no options for anonymous user
},
show: function(){
this._super.apply(this, arguments);
this.shown = true;
@@ -37,7 +33,8 @@ im_chat.Conversation.include({
this._super.apply(this, arguments);
this.shown = false;
},
update_fold_state: function(state){
session_update_state: function(state){
// set manually the new state
if(state === 'closed'){
this.destroy();
}else{
@@ -53,10 +50,9 @@ im_chat.Conversation.include({
}
}
}
var session = this.get('session');
session.state = state;
this.set('session', session);
session.set_cookie(COOKIE_NAME, JSON.stringify(session), 60*60);
// session and cookie update
this._super(state);
utils.set_cookie(COOKIE_NAME, JSON.stringify(this.get('session')), 60*60);
},
click_close: function(event) {
if(!this.feedback && (this.get('messages').length > 1)){
@@ -68,21 +64,22 @@ im_chat.Conversation.include({
this.feedback.on("feedback_sent", this, this.click_close);
}else{
this._super.apply(this, arguments);
session.set_cookie(COOKIE_NAME, "", -1);
utils.set_cookie(COOKIE_NAME, "", -1);
}
},
});
// To avoid exeption when the anonymous has close his
// conversation and when operator send him a message.
im_chat.ConversationManager.include({
received_message: function(message) {
im_chat_common.ConversationManager.include({
message_receive: function(message) {
try{
this._super(message);
}catch(e){}
}
});
var LiveSupport = Widget.extend({
init: function(server_url, db, channel, options, rule) {
this._super();
@@ -93,11 +90,11 @@ var LiveSupport = Widget.extend({
defaultMessage: _t("How may I help you?"),
defaultUsername: _t("Visitor"),
});
session.setup(server_url, {use_cors: false});
user_session.setup(server_url, {use_cors: false});
this.load_template(db, channel, options, rule);
},
_get_template_list: function(){
return ['/im_live_chat/static/src/xml/im_live_chat.xml', '/im_chat/static/src/xml/im_chat.xml'];
return ['/im_livechat/static/src/xml/im_livechat.xml', '/im_chat/static/src/xml/im_chat.xml'];
},
load_template: function(db, channel, options, rule){
var self = this;
@@ -105,7 +102,7 @@ var LiveSupport = Widget.extend({
var defs = [];
var templates = this._get_template_list();
_.each(templates, function(tmpl){
defs.push(session.rpc('/web/proxy/load', {path: tmpl}).then(function(xml) {
defs.push(user_session.rpc('/web/proxy/load', {path: tmpl}).then(function(xml) {
QWeb.add_template(xml);
}));
});
@@ -119,7 +116,7 @@ var LiveSupport = Widget.extend({
if(cookie){
self.build_button(channel, options, JSON.parse(cookie), rule);
}else{
session.rpc("/im_livechat/available", {db: db, channel: channel}).then(function(activated) {
user_session.rpc("/im_livechat/available", {db: db, channel: channel}).then(function(activated) {
if(activated){
self.build_button(channel, options, false, rule);
}
@@ -154,11 +151,8 @@ var ChatButton = Widget.extend({
start: function() {
this.$().append(QWeb.render("im_livechat.chatButton", {widget: this}));
// set up the manager
this.manager = new im_chat.ConversationManager(this, this.options);
this.manager = new im_chat_common.ConversationManager(this, this.options);
this.manager.set("bottom_offset", $('.oe_chat_button').outerHeight());
this.manager.notification = function(notif){ // override the notification default function
alert(notif);
};
if(this.session){
this.set_conversation(this.session);
}
@@ -166,17 +160,17 @@ var ChatButton = Widget.extend({
click: function() {
var self = this;
if (!this.conv){
session.rpc("/im_livechat/get_session", {
"channel_id" : self.channel,
user_session.rpc("/im_livechat/get_session", {
"channel_id" : self.channel,
"anonymous_name" : this.options.defaultUsername
}, {shadow: true}).then(function(session) {
if (! session) {
self.manager.notification(self.no_session_message);
alert(self.no_session_message);
return;
}
session.state = 'open';
// save the session in a cookie
session.set_cookie(COOKIE_NAME, JSON.stringify(session), 60*60);
utils.set_cookie(COOKIE_NAME, JSON.stringify(session), 60*60);
// create the conversation with the received session
self.set_conversation(session, true);
});
@@ -188,7 +182,7 @@ var ChatButton = Widget.extend({
if(session.state === 'closed'){
return;
}
this.conv = this.manager.apply_session(session);
this.conv = this.manager.session_apply(session);
this.conv.on("destroyed", this, function() {
bus.bus.stop_polling();
delete self.conv;
@@ -207,7 +201,7 @@ var ChatButton = Widget.extend({
if(this.session.users.length > 0){
if (self.options.defaultMessage) {
setTimeout(function(){
self.conv.received_message({
self.conv.message_receive({
id : 1,
type: "message",
message: self.options.defaultMessage,
@@ -229,7 +223,7 @@ var Feedback = Widget.extend({
this.conversation = parent;
this.reason = false;
this.rating = false;
this.server_origin = session.origin;
this.server_origin = user_session.origin;
},
start: function(){
this._super.apply(this.arguments);
@@ -263,7 +257,7 @@ var Feedback = Widget.extend({
_send_feedback: function(close){
var self = this;
var uuid = this.conversation.get('session').uuid;
return session.rpc("/rating/livechat/feedback", {uuid: uuid, rate: this.rating, reason : this.reason}).then(function(res) {
return user_session.rpc("/rating/livechat/feedback", {uuid: uuid, rate: this.rating, reason : this.reason}).then(function(res) {
if(close){
self.trigger("feedback_sent"); // will close the conversation
}
+47 -18
View File
@@ -96,9 +96,36 @@
<!-- OpenERP minimal lib -->
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<!-- jQuery -->
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery/jquery.browser.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.timeago/jquery.timeago.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
<!-- Qweb2 lib -->
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
<!-- Odoo JS Framework -->
<script type="text/javascript" src="/web/static/src/js/boot.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/class.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/translation.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/ajax.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/time.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/mixins.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/widget.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/registry.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/session.js"></script>
<!--<script type="text/javascript" src="/web/static/src/js/framework/model.js"></script>-->
<script type="text/javascript" src="/web/static/src/js/framework/utils.js"></script>
<script type="text/javascript" src="/web/static/src/js/framework/core.js"></script>
<script type="text/javascript">
odoo.define('web.session', function (require) {
var Session = require('web.Session');
var modules = odoo._modules;
return new Session(undefined, undefined, {modules:modules});
});
</script>
<!-- add the internal lib -->
<t t-call="im_livechat.internal_lib"/>
</template>
@@ -109,7 +136,7 @@
<script type="text/javascript" src="/web/static/lib/moment/moment.js"></script>
<!-- IM module -->
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
<script type="text/javascript" src="/im_chat/static/src/js/im_chat.js"></script>
<script type="text/javascript" src="/im_chat/static/src/js/im_chat_common.js"></script>
<script type="text/javascript" src="/im_livechat/static/src/js/im_livechat.js"></script>
<!-- CSS -->
<link rel="stylesheet" href="/im_chat/static/src/css/im_common.css"></link>
@@ -118,23 +145,25 @@
<!-- the js code to initialize the LiveSupport object -->
<template id="loader" name="Javascript initializing the LiveSupport">
odoo.define('im_livechat.livesupport', function (require) {
var im_livechat = require('im_livechat.im_livechat');
window.livesupport = new im_livechat.LiveSupport(
"<t t-esc="url"/>",
"<t t-esc="dbname"/>",
<t t-esc="channel"/>,
{
buttonText: "<t t-esc="buttonText"/>",
inputPlaceholder: "<t t-esc="inputPlaceholder"/>",
defaultMessage: "<t t-esc="defaultMessage"/>" || '',
auto: window.oe_im_livechat_auto || false,
defaultUsername: "<t t-esc="username"/>" || undefined,
},
<t t-raw="rule"/>
);
$(function() {
odoo.define('im_livechat.livesupport', function (require) {
var im_livechat = require('im_livechat.im_livechat');
window.livesupport = new im_livechat.LiveSupport(
"<t t-esc="url"/>",
"<t t-esc="dbname"/>",
<t t-esc="channel"/>,
{
buttonText: "<t t-esc="buttonText"/>",
inputPlaceholder: "<t t-esc="inputPlaceholder"/>",
defaultMessage: "<t t-esc="defaultMessage"/>" || '',
auto: window.oe_im_livechat_auto || false,
defaultUsername: "<t t-esc="username"/>" || undefined,
},
<t t-raw="rule"/>
);
});
odoo.init();
});
</template>
</data>