diff --git a/addons/im_chat/static/src/css/im_common.css b/addons/im_chat/static/src/css/im_common.css
index 11cbe5de19f..bb3c4645fd8 100644
--- a/addons/im_chat/static/src/css/im_common.css
+++ b/addons/im_chat/static/src/css/im_common.css
@@ -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;
diff --git a/addons/im_chat/static/src/js/im_chat.js b/addons/im_chat/static/src/js/im_chat.js
index 2017f5c3582..135782a382c 100644
--- a/addons/im_chat/static/src/js/im_chat.js
+++ b/addons/im_chat/static/src/js/im_chat.js
@@ -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 = ' ' + label;
- }
- this.$('.oe_im_chatview_option_list').append('
'+label+'');
- },
- 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,
};
diff --git a/addons/im_chat/static/src/js/im_chat_common.js b/addons/im_chat/static/src/js/im_chat_common.js
new file mode 100644
index 00000000000..0aaa0e68d16
--- /dev/null
+++ b/addons/im_chat/static/src/js/im_chat_common.js
@@ -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,
+};
+
+});
\ No newline at end of file
diff --git a/addons/im_chat/static/src/xml/im_chat.xml b/addons/im_chat/static/src/xml/im_chat.xml
index 290de6b8cc6..9adef41a24f 100644
--- a/addons/im_chat/static/src/xml/im_chat.xml
+++ b/addons/im_chat/static/src/xml/im_chat.xml
@@ -2,16 +2,21 @@