(function(){ "use strict"; var _t = openerp._t; var _lt = openerp._lt; var QWeb = openerp.qweb; var NBR_LIMIT_HISTORY = 20; var USERS_LIMIT = 20; var im_chat = openerp.im_chat = {}; im_chat.ConversationManager = openerp.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 = openerp.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) { 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"; var kitten = jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined; audio.src = openerp.session.url("/im_chat/static/src/audio/" + (kitten ? "purr" : "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 (! openerp.webclient || !openerp.webclient.set_title_part) return; openerp.webclient.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 im_chat.Conversation(this, this, session, this.options); conv.appendTo($("body")); conv.on("destroyed", this, function() { delete this.sessions[session.uuid]; this.calc_positions(); }); 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; }, received_message: function(message) { var self = this; var session_id = message.to_id[0]; var uuid = message.to_id[1]; if (! this.get("window_focus")) { this.set("waiting_messages", this.get("waiting_messages") + 1); } var conv = this.sessions[uuid]; if(!conv){ // fetch the session, and init it with the message var def_session = new openerp.Model("im_chat.session").call("session_info", [], {"ids" : [session_id]}).then(function(session){ conv = self.activate_session(session, false); conv.received_message(message); }); }else{ conv.received_message(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(); } }); im_chat.Conversation = openerp.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.$().append(openerp.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(); }, 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 openerp.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( (openerp.session.uid !== user.id) && !(_.isUndefined(openerp.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 domain = [["to_id.uuid", "=", this.get("session").uuid]]; _.first(this.get("messages")) && domain.push(['id','<', _.first(this.get("messages")).id]); new openerp.Model("im_chat.message").call("search_read", [domain, ['id', 'create_date','to_id','from_id', 'type', 'message'], 0, NBR_LIMIT_HISTORY]).then(function(messages){ self.insert_messages(messages); if(messages.length != NBR_LIMIT_HISTORY){ 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 openerp.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.options["defaultUsername"]]; } m.message = self.escape_keep_url(m.message); m.message = self.smiley(m.message); m.create_date = Date.parse(m.create_date).setTimezone("UTC").toString("yyyy-dd-MM 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] = openerp.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 this.$('.oe_im_chatview_content_bubbles').html($(openerp.qweb.render("im_chat.Conversation_content", {"list": res}))); }, keydown: function(e) { if(e && e.which !== 13) { return; } var mes = this.$("input").val(); if (! mes.trim()) { return; } this.$("input").val(""); this.send_message(mes, "message"); }, get_smiley_list: function(){ var kitten = jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined; var smileys = { ":'(": "😢", ":O" : "😱", "3:)": "😈", ":)" : "😊", ":D" : "😅", ";)" : "😉", ":p" : "😋", ":(" : "☹", ":|" : "😐", ":/" : "😏", "8)" : "😳", ":s" : "😖", ":pinky" : "", ":musti" : "", }; if(kitten){ _.extend(smileys, { ":)" : "😺", ":D" : "😹", ";)" : "😼", ":p" : "😽", ":(" : "🙀", ":|" : "😿", }); } return smileys; }, smiley: function(str){ var re_escape = function(str){ return String(str).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); }; var smileys = this.get_smiley_list(); _.each(_.keys(smileys), function(key){ str = str.replace( new RegExp("(?:^|\\s)(" + re_escape(key) + ")(?:\\s|$)"), ' '+smileys[key]+' '); }); return str; }, escape_keep_url: function(str){ var url_regex = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi; var last = 0; var txt = ""; while (true) { var result = url_regex.exec(str); if (! result) break; txt += _.escape(str.slice(last, result.index)); last = url_regex.lastIndex; var url = _.escape(result[0]); txt += '' + url + ''; } txt += _.escape(str.slice(last, str.length)); return txt; }, _go_bottom: function() { this.$(".oe_im_chatview_content").scrollTop(this.$(".oe_im_chatview_content").get(0).scrollHeight); }, add_user: function(user){ return new openerp.Model("im_chat.session").call("add_user", [this.get("session").uuid , user.id]); }, focus: function() { this.$(".oe_im_chatview_input").focus(); }, click_header: function(){ this.update_fold_state(); }, click_close: function(event) { event.stopPropagation(); this.update_fold_state('closed'); }, destroy: function() { this.trigger("destroyed"); return this._super(); } }); im_chat.UserWidget = openerp.Widget.extend({ "template": "im_chat.UserWidget", events: { "click": "activate_user", }, init: function(parent, user) { this._super(parent); this.set("id", user.id); this.set("name", user.name); this.set("im_status", user.im_status); this.set("image_url", user.image_url); }, start: function() { this.$el.data("user", {id:this.get("id"), name:this.get("name")}); this.$el.draggable({helper: "clone"}); this.on("change:im_status", this, this.update_status); this.update_status(); }, update_status: function(){ this.$(".oe_im_user_online").toggle(this.get('im_status') !== 'offline'); var img_src = (this.get('im_status') == 'away' ? '/im_chat/static/src/img/yellow.png' : '/im_chat/static/src/img/green.png'); this.$(".oe_im_user_online").attr('src', openerp.session.server + img_src); }, activate_user: function() { this.trigger("activate_user", this.get("id")); }, }); im_chat.InstantMessaging = openerp.Widget.extend({ template: "im_chat.InstantMessaging", events: { "keydown .oe_im_searchbox": "input_change", "keyup .oe_im_searchbox": "input_change", "change .oe_im_searchbox": "input_change", }, init: function(parent) { this._super(parent); this.shown = false; this.set("right_offset", 0); this.set("current_search", ""); this.users = []; this.widgets = {}; this.c_manager = new openerp.im_chat.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 openerp.web.DropMisordered(); }, start: function() { var self = 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.on("change:current_search", this, this.search_changed); this.search_changed(); // add a drag & drop listener self.c_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) openerp.session.rpc("/im_chat/init",{}).then(function(notifications) { _.each(notifications, function(notif){ self.c_manager.on_notification(notif); }); // start polling openerp.bus.bus.start_polling(); }); return; }, calc_box: 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); this.$el.css("top", top); this.$el.css("bottom", 0); }, input_change: function() { this.set("current_search", this.$(".oe_im_searchbox").val()); }, search_changed: function(e) { var user_model = new openerp.web.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 openerp.web.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 = openerp.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: user.id}); var widget = new openerp.im_chat.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() { var fct = _.bind(function(place) { this.set("right_offset", place + this.$el.outerWidth()); }, this); var opt = { step: fct, }; if (this.shown) { this.$el.animate({ right: -this.$el.outerWidth(), }, opt); } else { if (! openerp.bus.bus.activated) { this.do_warn("Instant Messaging is not activated on this server. Try later.", ""); return; } this.$el.animate({ right: 0, }, opt); } this.shown = ! this.shown; }, activate_user: function(user_id) { var self = this; var sessions = new openerp.web.Model("im_chat.session"); return sessions.call("session_get", [user_id]).then(function(session) { self.c_manager.activate_session(session, true); }); }, update_users_status: function(users_list){ var self = this; _.each(users_list, function(el) { self.widgets[el.id] && self.widgets[el.id].set("im_status", el.im_status); }); } }); im_chat.ImTopButton = openerp.Widget.extend({ template:'im_chat.ImTopButton', events: { "click": "clicked", }, clicked: function(ev) { ev.preventDefault(); this.trigger("clicked"); }, }); if(openerp.web && openerp.web.UserMenu) { openerp.web.UserMenu.include({ do_update: function(){ var self = this; this.update_promise.then(function() { var im = new openerp.im_chat.InstantMessaging(self); openerp.im_chat.single = im; im.appendTo(openerp.client.$el); var button = new openerp.im_chat.ImTopButton(this); button.on("clicked", im, im.switch_display); button.appendTo(window.$('.oe_systray')); }); return this._super.apply(this, arguments); }, }); } return im_chat; })();