diff options
Diffstat (limited to 'addons/im/static/src')
| -rw-r--r-- | addons/im/static/src/css/im.css | 157 | ||||
| -rw-r--r-- | addons/im/static/src/css/im_common.css | 189 | ||||
| -rw-r--r-- | addons/im/static/src/js/im.js | 373 | ||||
| -rw-r--r-- | addons/im/static/src/js/im_common.js | 538 | ||||
| -rw-r--r-- | addons/im/static/src/xml/im.xml | 33 | ||||
| -rw-r--r-- | addons/im/static/src/xml/im_common.xml | 42 |
6 files changed, 809 insertions, 523 deletions
diff --git a/addons/im/static/src/css/im.css b/addons/im/static/src/css/im.css index 269d411d416..03a20bb0510 100644 --- a/addons/im/static/src/css/im.css +++ b/addons/im/static/src/css/im.css @@ -105,160 +105,3 @@ vertical-align: middle; border: 0; } - -/* conversations */ - -.openerp .oe_im_chatview { - position: fixed; - overflow: hidden; - bottom: 6px; - margin-right: 6px; - background: rgba(60, 60, 60, 0.8); - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - -moz-box-shadow: 0 0 3px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.3); - -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3); - box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3); - width: 240px; -} -.openerp .oe_im_chatview .oe_im_chatview_disconnected { - display:none; - z-index: 100; - width: 100%; - background: #E8EBEF; - padding: 5px; - font-size: 11px; - color: #999; - line-height: 14px; - height: 28px; - overflow: hidden; -} -.openerp .oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_disconnected { - display: block; -} -.openerp .oe_im_chatview .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)); - -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; -} -.openerp .oe_im_chatview .oe_im_chatview_close { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; - font-size: 18px; - line-height: 16px; - float: right; - font-weight: bold; - color: black; - text-shadow: 0 1px 0 white; - opacity: 0.2; -} -.openerp .oe_im_chatview .oe_im_chatview_content { - overflow: auto; - height: 287px; -} -.openerp .oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_content { - height: 249px; -} -.openerp .oe_im_chatview .oe_im_chatview_footer { - position: relative; - padding: 3px; - border-top: 1px solid #AEB9BD; - background: #DEDEDE; - background: -moz-linear-gradient(#FCFCFC, #DEDEDE); - background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE)); - -moz-border-radius: 0 0 3px 3px; - -webkit-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; -} -.openerp .oe_im_chatview .oe_im_chatview_input { - width: 222px; - font-family: Lato, Helvetica, sans-serif; - font-size: 13px; - color: #333; - padding: 1px 5px; - border: 1px solid #AEB9BD; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - -moz-box-shadow: inset 0 1px 4px rgba(0,0,0,0.2); - -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2); - box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2); -} -.openerp .oe_im_chatview .oe_im_chatview_bubble { - background: white; - position: relative; - padding: 3px; - margin: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} -.openerp .oe_im_chatview .oe_im_chatview_clip { - position: relative; - float: left; - width: 26px; - height: 26px; - margin-right: 4px; - -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25); - -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); - box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); -} -.openerp .oe_im_chatview .oe_im_chatview_avatar { - float: left; - width: 26px; - height: auto; - clip: rect(0, 26px, 26px, 0); - max-width: 100%; - width: auto 9; - height: auto; - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} -.openerp .oe_im_chatview .oe_im_chatview_time { - position: absolute; - right: 0px; - top: 0px; - margin: 3px; - text-align: right; - line-height: 13px; - font-size: 11px; - color: #999; - width: 60px; - overflow: hidden; -} -.openerp .oe_im_chatview .oe_im_chatview_from { - margin: 0 0 2px 0; - line-height: 14px; - font-weight: bold; - font-size: 12px; - width: 140px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - color: #3A87AD; -} -.openerp .oe_im_chatview .oe_im_chatview_bubble_list { -} -.openerp .oe_im_chatview .oe_im_chatview_bubble_item { - margin: 0 0 2px 30px; - line-height: 14px; - word-wrap: break-word; -} - -.openerp .oe_im_chatview_online { - display: none; - margin-top: -4px; - width: 11px; - height: 11px; -} diff --git a/addons/im/static/src/css/im_common.css b/addons/im/static/src/css/im_common.css new file mode 100644 index 00000000000..30275833b2d --- /dev/null +++ b/addons/im/static/src/css/im_common.css @@ -0,0 +1,189 @@ + + + +.openerp_style { /* base style of openerp */ + font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; + color: #4c4c4c; + font-size: 13px; + background: white; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); +} + +/* button */ + +.oe_chat_button { + position: fixed; + bottom: 0px; + right: 6px; + display: inline-block; + min-width: 100px; + background-color: rgba(60, 60, 60, 0.6); + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Arial, Verdana, sans-serif; + font-size: 14px; + font-weight: bold; + padding: 10px; + color: white; + text-shadow: rgb(59, 76, 88) 1px 1px 0px; + border: 1px solid rgb(80, 80, 80); + border-bottom: 0px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + cursor: pointer; +} + +/* conversations */ + +.oe_im_chatview { + position: fixed; + overflow: hidden; + margin-bottom: 5px; + margin-right: 6px; + background: rgba(60, 60, 60, 0.8); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 0 3px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.3); + -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3); + width: 240px; +} +.oe_im_chatview .oe_im_chatview_disconnected { + display:none; + z-index: 100; + width: 100%; + background: #E8EBEF; + padding: 5px; + font-size: 11px; + color: #999; + line-height: 14px; + height: 28px; + overflow: hidden; +} +.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_disconnected { + display: block; +} +.oe_im_chatview .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)); + -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_close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + font-size: 18px; + line-height: 16px; + float: right; + font-weight: bold; + color: black; + text-shadow: 0 1px 0 white; + opacity: 0.2; +} +.oe_im_chatview .oe_im_chatview_content { + overflow: auto; + height: 287px; + width: 240px; +} +.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_content { + height: 249px; +} +.oe_im_chatview .oe_im_chatview_footer { + position: relative; + padding: 3px; + border-top: 1px solid #AEB9BD; + background: #DEDEDE; + background: -moz-linear-gradient(#FCFCFC, #DEDEDE); + background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE)); + -moz-border-radius: 0 0 3px 3px; + -webkit-border-radius: 0 0 3px 3px; + border-radius: 0 0 3px 3px; +} +.oe_im_chatview .oe_im_chatview_input { + width: 222px; + font-family: Lato, Helvetica, sans-serif; + font-size: 13px; + color: #333; + padding: 1px 5px; + border: 1px solid #AEB9BD; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: inset 0 1px 4px rgba(0,0,0,0.2); + -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2); +} +.oe_im_chatview .oe_im_chatview_bubble { + background: white; + position: relative; + padding: 3px; + margin: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} +.oe_im_chatview .oe_im_chatview_clip { + position: relative; + float: left; + width: 26px; + height: 26px; + margin-right: 4px; + -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25); + -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); + box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25); +} +.oe_im_chatview .oe_im_chatview_avatar { + float: left; + width: 26px; + height: auto; + clip: rect(0, 26px, 26px, 0); + max-width: 100%; + width: auto 9; + height: auto; + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} +.oe_im_chatview .oe_im_chatview_time { + position: absolute; + right: 0px; + top: 0px; + margin: 3px; + text-align: right; + line-height: 13px; + font-size: 11px; + color: #999; + width: 60px; + overflow: hidden; +} +.oe_im_chatview .oe_im_chatview_from { + margin: 0 0 2px 0; + line-height: 14px; + font-weight: bold; + font-size: 12px; + width: 140px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + color: #3A87AD; +} +.oe_im_chatview .oe_im_chatview_bubble_list { +} +.oe_im_chatview .oe_im_chatview_bubble_item { + margin: 0 0 2px 30px; + line-height: 14px; + word-wrap: break-word; +} + +.oe_im_chatview_online { + margin-top: -4px; + width: 11px; + height: 11px; +} diff --git a/addons/im/static/src/js/im.js b/addons/im/static/src/js/im.js index a271c9eccf0..6fbf452f974 100644 --- a/addons/im/static/src/js/im.js +++ b/addons/im/static/src/js/im.js @@ -1,17 +1,26 @@ -openerp.im = function(instance) { +(function() { + "use strict"; + + var instance = openerp; + + openerp.im = {}; var USERS_LIMIT = 20; - var ERROR_DELAY = 5000; - var _t = instance.web._t, - _lt = instance.web._lt; + var _t = instance.web._t; var QWeb = instance.web.qweb; instance.web.UserMenu.include({ do_update: function(){ var self = this; this.update_promise.then(function() { + im_common.notification = function(message) { + instance.client.do_warn(message); + }; + // TODO: allow to use a different host for the chat + im_common.connection = new openerp.Session(self, null, {session_id: openerp.session.session_id}); + var im = new instance.im.InstantMessaging(self); im.appendTo(instance.client.$el); var button = new instance.im.ImTopButton(this); @@ -45,7 +54,8 @@ openerp.im = function(instance) { this.set("right_offset", 0); this.set("current_search", ""); this.users = []; - this.c_manager = new instance.im.ConversationManager(this); + this.c_manager = new im_common.ConversationManager(this); + window.im_conversation_manager = this.c_manager; this.on("change:right_offset", this.c_manager, _.bind(function() { this.c_manager.set("right_offset", this.get("right_offset")); }, this)); @@ -62,7 +72,15 @@ openerp.im = function(instance) { var self = this; - return this.c_manager.start_polling(); + return this.c_manager.start_polling().then(function() { + self.c_manager.on("new_conversation", self, function(conv) { + conv.$el.droppable({ + drop: function(event, ui) { + self.add_user(conv, ui.draggable.data("user")); + } + }); + }); + }); }, calc_box: function() { var $topbar = instance.client.$(".oe_topbar"); @@ -77,17 +95,19 @@ openerp.im = function(instance) { search_changed: function(e) { var users = new instance.web.Model("im.user"); var self = this; - return this.user_search_dm.add(users.call("search_users", - [[["name", "ilike", this.get("current_search")], ["id", "<>", instance.session.uid]], - ["name", "user", "uuid", "im_status"], USERS_LIMIT], {context:new instance.web.CompoundContext()})).then(function(result) { - self.c_manager.add_to_user_cache(result); + return this.user_search_dm.add(users.call("search_users", [this.get("current_search"), ["name", "user_id", "uuid", "im_status"], + USERS_LIMIT], {context:new instance.web.CompoundContext()})).then(function(users) { + var logged_users = _.filter(users, function(u) { return !!u.im_status; }); + var non_logged_users = _.filter(users, function(u) { return !u.im_status; }); + users = logged_users.concat(non_logged_users); + self.c_manager.add_to_user_cache(users); self.$(".oe_im_input").val(""); var old_users = self.users; self.users = []; - _.each(result, function(user) { + _.each(users, function(user) { var widget = new instance.im.UserWidget(self, self.c_manager.get_user(user.id)); widget.appendTo(self.$(".oe_im_users")); - widget.on("activate_user", self, self.activate_user); + widget.on("activate_user", self, function(user) {self.c_manager.chat_with_users([user]);}); self.users.push(widget); }); _.each(old_users, function(user) { @@ -117,8 +137,8 @@ openerp.im = function(instance) { } this.shown = ! this.shown; }, - activate_user: function(user) { - this.c_manager.activate_user(user, true); + add_user: function(conversation, user) { + conversation.add_user(user); }, }); @@ -133,6 +153,8 @@ openerp.im = function(instance) { this.user.add_watcher(); }, start: function() { + this.$el.data("user", this.user); + this.$el.draggable({helper: "clone"}); var change_status = function() { this.$(".oe_im_user_online").toggle(this.user.get("im_status") === true); }; @@ -148,323 +170,8 @@ openerp.im = function(instance) { }, }); - instance.im.ImUser = instance.web.Class.extend(instance.web.PropertiesMixin, { - init: function(parent, user_rec) { - instance.web.PropertiesMixin.init.call(this, parent); - user_rec.image_url = instance.session.url("/im/static/src/img/avatar/avatar.jpeg"); - if (user_rec.user) - user_rec.image_url = instance.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: user_rec.user[0]}); - this.set(user_rec); - this.set("watcher_count", 0); - this.on("change:watcher_count", this, function() { - if (this.get("watcher_count") === 0) - this.destroy(); - }); - }, - destroy: function() { - this.trigger("destroyed"); - instance.web.PropertiesMixin.destroy.call(this); - }, - add_watcher: function() { - this.set("watcher_count", this.get("watcher_count") + 1); - }, - remove_watcher: function() { - this.set("watcher_count", this.get("watcher_count") - 1); - }, - }); - - instance.im.ConversationManager = instance.web.Controller.extend({ - init: function(parent) { - this._super(parent); - this.set("right_offset", 0); - this.conversations = []; - this.users = {}; - this.on("change:right_offset", this, this.calc_positions); - this.set("window_focus", true); - this.set("waiting_messages", 0); - this.focus_hdl = _.bind(function() { - this.set("window_focus", true); - }, this); - $(window).bind("focus", this.focus_hdl); - this.blur_hdl = _.bind(function() { - this.set("window_focus", false); - }, this); - $(window).bind("blur", this.blur_hdl); - this.on("change:window_focus", this, this.window_focus_change); - this.window_focus_change(); - this.on("change:waiting_messages", this, this.messages_change); - this.messages_change(); - this.create_ting(); - this.activated = false; - this.users_cache = {}; - this.last = null; - this.unload_event_handler = _.bind(this.unload, this); - }, - start_polling: function() { - var self = this; - return new instance.web.Model("im.user").call("get_by_user_id", [instance.session.uid]).then(function(my_id) { - self.my_id = my_id["id"]; - return self.ensure_users([self.my_id]).then(function() { - var me = self.users_cache[self.my_id]; - delete self.users_cache[self.my_id]; - self.me = me; - self.rpc("/longpolling/im/activated", {}, {shadow: true}).then(function(activated) { - if (activated) { - self.activated = true; - $(window).on("unload", self.unload_event_handler); - self.poll(); - } - }, function(a, e) { - e.preventDefault(); - }); - }); - }); - }, - unload: function() { - return new instance.web.Model("im.user").call("im_disconnect", [], {context: new instance.web.CompoundContext()}); - }, - ensure_users: function(user_ids) { - var no_cache = {}; - _.each(user_ids, function(el) { - if (! this.users_cache[el]) - no_cache[el] = el; - }, this); - var self = this; - if (_.size(no_cache) === 0) - return $.when(); - else - return new instance.web.Model("im.user").call("read", [_.values(no_cache), ["name", "user", "uuid", "im_status"]], - {context: new instance.web.CompoundContext()}).then(function(users) { - self.add_to_user_cache(users); - }); - }, - add_to_user_cache: function(user_recs) { - _.each(user_recs, function(user_rec) { - if (! this.users_cache[user_rec.id]) { - var user = new instance.im.ImUser(this, user_rec); - this.users_cache[user_rec.id] = user; - user.on("destroyed", this, function() { - delete this.users_cache[user_rec.id]; - }); - } - }, this); - }, - get_user: function(user_id) { - return this.users_cache[user_id]; - }, - poll: function() { - var self = this; - var user_ids = _.map(this.users_cache, function(el) { - return el.get("id"); - }); - this.rpc("/longpolling/im/poll", { - last: this.last, - users_watch: user_ids, - context: instance.web.pyeval.eval('context', {}), - }, {shadow: true}).then(function(result) { - _.each(result.users_status, function(el) { - if (self.get_user(el.id)) - self.get_user(el.id).set(el); - }); - self.last = result.last; - var user_ids = _.pluck(_.pluck(result.res, "from_id"), 0); - self.ensure_users(user_ids).then(function() { - _.each(result.res, function(mes) { - var user = self.get_user(mes.from_id[0]); - self.received_message(mes, user); - }); - self.poll(); - }); - }, function(unused, e) { - e.preventDefault(); - setTimeout(_.bind(self.poll, self), ERROR_DELAY); - }); - }, - get_activated: function() { - return this.activated; - }, - create_ting: function() { - if (typeof(Audio) === "undefined") { - this.ting = {play: function() {}}; - return; - } - var kitten = jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined; - this.ting = new Audio(instance.webclient.session.origin + "/im/static/src/audio/" + (kitten ? "purr" : "Ting") + - (new Audio().canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3")); - }, - window_focus_change: function() { - if (this.get("window_focus")) { - this.set("waiting_messages", 0); - } - }, - messages_change: function() { - if (! instance.webclient.set_title_part) - return; - instance.webclient.set_title_part("aa_im_messages", this.get("waiting_messages") === 0 ? undefined : - _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"))); - }, - activate_user: function(user, focus) { - var conv = this.users[user.get('id')]; - if (! conv) { - conv = new instance.im.Conversation(this, user, this.me); - conv.appendTo(instance.client.$el); - conv.on("destroyed", this, function() { - this.conversations = _.without(this.conversations, conv); - delete this.users[conv.user.get('id')]; - this.calc_positions(); - }); - this.conversations.push(conv); - this.users[user.get('id')] = conv; - this.calc_positions(); - } - if (focus) - conv.focus(); - return conv; - }, - received_message: function(message, user) { - if (! this.get("window_focus")) { - this.set("waiting_messages", this.get("waiting_messages") + 1); - this.ting.play(); - this.create_ting(); - } - var conv = this.activate_user(user); - conv.received_message(message); - }, - calc_positions: function() { - var current = this.get("right_offset"); - _.each(_.range(this.conversations.length), function(i) { - this.conversations[i].set("right_position", current); - current += this.conversations[i].$el.outerWidth(true); - }, this); - }, - destroy: function() { - $(window).off("unload", this.unload_event_handler); - $(window).unbind("blur", this.blur_hdl); - $(window).unbind("focus", this.focus_hdl); - this._super(); - }, - }); - - instance.im.Conversation = instance.web.Widget.extend({ - "template": "Conversation", - events: { - "keydown input": "send_message", - "click .oe_im_chatview_close": "destroy", - "click .oe_im_chatview_header": "show_hide", - }, - init: function(parent, user, me) { - this._super(parent); - this.me = me; - this.user = user; - this.user.add_watcher(); - this.set("right_position", 0); - this.shown = true; - this.set("pending", 0); - }, - start: function() { - var change_status = function() { - this.$el.toggleClass("oe_im_chatview_disconnected_status", this.user.get("im_status") === false); - this.$(".oe_im_chatview_online").toggle(this.user.get("im_status") === true); - this._go_bottom(); - }; - this.user.on("change:im_status", this, change_status); - change_status.call(this); - - this.on("change:right_position", this, this.calc_pos); - this.full_height = this.$el.height(); - this.calc_pos(); - this.on("change:pending", this, _.bind(function() { - if (this.get("pending") === 0) { - this.$(".oe_im_chatview_nbr_messages").text(""); - } else { - this.$(".oe_im_chatview_nbr_messages").text("(" + this.get("pending") + ")"); - } - }, this)); - }, - show_hide: function() { - if (this.shown) { - this.$el.animate({ - height: this.$(".oe_im_chatview_header").outerHeight(), - }); - } else { - this.$el.animate({ - height: this.full_height, - }); - } - this.shown = ! this.shown; - if (this.shown) { - this.set("pending", 0); - } - }, - calc_pos: function() { - this.$el.css("right", this.get("right_position")); - }, - received_message: function(message) { - if (this.shown) { - this.set("pending", 0); - } else { - this.set("pending", this.get("pending") + 1); - } - this._add_bubble(this.user, message.message, message.date); - }, - send_message: function(e) { - if(e && e.which !== 13) { - return; - } - var mes = this.$("input").val(); - if (! mes.trim()) { - return; - } - this.$("input").val(""); - var send_it = _.bind(function() { - var model = new instance.web.Model("im.message"); - return model.call("post", [mes, this.user.get('id')], - {context: new instance.web.CompoundContext()}); - }, this); - var tries = 0; - send_it().then(_.bind(function() { - this._add_bubble(this.me, mes, instance.web.datetime_to_str(new Date())); - }, this), function(error, e) { - e.preventDefault(); - tries += 1; - if (tries < 3) - return send_it(); - }); - }, - _add_bubble: function(user, item, date) { - var items = [item]; - if (user === this.last_user) { - this.last_bubble.remove(); - items = this.last_items.concat(items); - } - this.last_user = user; - this.last_items = items; - date = instance.web.str_to_datetime(date); - var now = new Date(); - var diff = now - date; - if (diff > (1000 * 60 * 60 * 24)) { - date = $.timeago(date); - } else { - date = date.toString(Date.CultureInfo.formatPatterns.shortTime); - } - - this.last_bubble = $(QWeb.render("Conversation.bubble", {"items": items, "user": user, "time": date})); - $(this.$(".oe_im_chatview_content").children()[0]).append(this.last_bubble); - this._go_bottom(); - }, - _go_bottom: function() { - this.$(".oe_im_chatview_content").scrollTop($(this.$(".oe_im_chatview_content").children()[0]).height()); - }, - focus: function() { - this.$(".oe_im_chatview_input").focus(); - if (! this.shown) - this.show_hide(); - }, - destroy: function() { - this.user.remove_watcher(); - this.trigger("destroyed"); - return this._super(); - }, - }); + im_common.technical_messages_handlers.force_kitten = function() { + openerp.webclient.to_kitten(); + }; -}
\ No newline at end of file +})();
\ No newline at end of file diff --git a/addons/im/static/src/js/im_common.js b/addons/im/static/src/js/im_common.js new file mode 100644 index 00000000000..e0692edca2b --- /dev/null +++ b/addons/im/static/src/js/im_common.js @@ -0,0 +1,538 @@ + +/* + This file must compile in EcmaScript 3 and work in IE7. + + Prerequisites to use this module: + - load the im_common.xml qweb template into openerp.qweb + - implement all the stuff defined later +*/ + +(function() { + +function declare($, _, openerp) { + /* jshint es3: true */ + "use strict"; + + var im_common = {}; + + /* + All of this must be defined to use this module + */ + _.extend(im_common, { + notification: function(message) { + throw new Error("Not implemented"); + }, + connection: null + }); + + var _t = openerp._t; + + var ERROR_DELAY = 5000; + + im_common.ImUser = openerp.Class.extend(openerp.PropertiesMixin, { + init: function(parent, user_rec) { + openerp.PropertiesMixin.init.call(this, parent); + + user_rec.image_url = im_common.connection.url('/web/binary/image', {model:'im.user', field: 'image', id: user_rec.id}); + + this.set(user_rec); + this.set("watcher_count", 0); + this.on("change:watcher_count", this, function() { + if (this.get("watcher_count") === 0) + this.destroy(); + }); + }, + destroy: function() { + this.trigger("destroyed"); + openerp.PropertiesMixin.destroy.call(this); + }, + add_watcher: function() { + this.set("watcher_count", this.get("watcher_count") + 1); + }, + remove_watcher: function() { + this.set("watcher_count", this.get("watcher_count") - 1); + } + }); + + im_common.ConversationManager = openerp.Class.extend(openerp.PropertiesMixin, { + init: function(parent, options) { + openerp.PropertiesMixin.init.call(this, parent); + this.options = _.clone(options) || {}; + _.defaults(this.options, { + inputPlaceholder: _t("Say something..."), + defaultMessage: null, + userName: _t("Anonymous"), + anonymous_mode: false + }); + this.set("right_offset", 0); + this.set("bottom_offset", 0); + this.conversations = []; + this.on("change:right_offset", this, this.calc_positions); + this.on("change:bottom_offset", this, this.calc_positions); + this.set("window_focus", true); + this.set("waiting_messages", 0); + this.focus_hdl = _.bind(function() { + this.set("window_focus", true); + }, this); + $(window).bind("focus", this.focus_hdl); + this.blur_hdl = _.bind(function() { + this.set("window_focus", false); + }, this); + $(window).bind("blur", this.blur_hdl); + this.on("change:window_focus", this, this.window_focus_change); + this.window_focus_change(); + this.on("change:waiting_messages", this, this.messages_change); + this.messages_change(); + this.create_ting(); + this.activated = false; + this.users_cache = {}; + this.last = null; + this.unload_event_handler = _.bind(this.unload, this); + }, + start_polling: function() { + var self = this; + var def = $.when(); + var uuid = false; + + if (this.options.anonymous_mode) { + uuid = localStorage["oe_livesupport_uuid"] || false; + + if (! uuid) { + def = im_common.connection.rpc("/longpolling/im/gen_uuid", {}).then(function(my_uuid) { + uuid = my_uuid; + localStorage["oe_livesupport_uuid"] = uuid; + }); + } + def = def.then(function() { + return im_common.connection.model("im.user").call("assign_name", [uuid, self.options.userName]); + }); + } + + return def.then(function() { + return im_common.connection.model("im.user").call("get_my_id", [uuid]); + }).then(function(my_user_id) { + self.my_id = my_user_id; + return self.ensure_users([self.my_id]); + }).then(function() { + var me = self.users_cache[self.my_id]; + delete self.users_cache[self.my_id]; + self.me = me; + me.set("name", _t("You")); + return im_common.connection.rpc("/longpolling/im/activated", {}, {shadow: true}); + }).then(function(activated) { + if (activated) { + self.activated = true; + $(window).on("unload", self.unload_event_handler); + self.poll(); + } else { + return $.Deferred().reject(); + } + }, function(a, e) { + e.preventDefault(); + }); + }, + unload: function() { + return im_common.connection.model("im.user").call("im_disconnect", [], {uuid: this.me.get("uuid"), context: {}}); + }, + ensure_users: function(user_ids) { + var no_cache = {}; + _.each(user_ids, function(el) { + if (! this.users_cache[el]) + no_cache[el] = el; + }, this); + var self = this; + var def; + if (_.size(no_cache) === 0) + def = $.when(); + else + def = im_common.connection.model("im.user").call("read", [_.values(no_cache), []]).then(function(users) { + self.add_to_user_cache(users); + }); + return def.then(function() { + return _.map(user_ids, function(id) { return self.get_user(id); }); + }); + }, + add_to_user_cache: function(user_recs) { + _.each(user_recs, function(user_rec) { + if (! this.users_cache[user_rec.id]) { + var user = new im_common.ImUser(this, user_rec); + this.users_cache[user_rec.id] = user; + user.on("destroyed", this, function() { + delete this.users_cache[user_rec.id]; + }); + } + }, this); + }, + get_user: function(user_id) { + return this.users_cache[user_id]; + }, + poll: function() { + var self = this; + var user_ids = _.map(this.users_cache, function(el) { + return el.get("id"); + }); + im_common.connection.rpc("/longpolling/im/poll", { + last: this.last, + users_watch: user_ids, + uuid: self.me.get("uuid") + }, {shadow: true}).then(function(result) { + _.each(result.users_status, function(el) { + if (self.get_user(el.id)) + self.get_user(el.id).set(el); + }); + self.last = result.last; + self.received_messages(result.res).then(function() { + self.poll(); + }); + }, function(unused, e) { + e.preventDefault(); + setTimeout(_.bind(self.poll, self), ERROR_DELAY); + }); + }, + get_activated: function() { + return this.activated; + }, + create_ting: function() { + if (typeof(Audio) === "undefined") { + this.ting = {play: function() {}}; + return; + } + var kitten = jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined; + this.ting = new Audio(im_common.connection.url( + "/im/static/src/audio/" + + (kitten ? "purr" : "Ting") + + (new Audio().canPlayType("audio/ogg; codecs=vorbis") ? ".ogg": ".mp3") + )); + }, + window_focus_change: function() { + if (this.get("window_focus")) { + this.set("waiting_messages", 0); + } + }, + messages_change: function() { + if (! openerp.webclient || !openerp.webclient.set_title_part) + return; + openerp.webclient.set_title_part("im_messages", this.get("waiting_messages") === 0 ? undefined : + _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"))); + }, + chat_with_users: function(users) { + var self = this; + return im_common.connection.model("im.session").call("session_get", [_.map(users, function(user) {return user.get("id");}), + self.me.get("uuid")]).then(function(session) { + return self.activate_session(session.id, true); + }); + }, + chat_with_all_users: function() { + var self = this; + return im_common.connection.model("im.user").call("search", [[["uuid", "=", false]]]).then(function(user_ids) { + return self.ensure_users(_.without(user_ids, self.me.get("id"))); + }).then(function(users) { + return self.chat_with_users(users); + }); + }, + activate_session: function(session_id, focus) { + var conv = _.find(this.conversations, function(conv) {return conv.session_id == session_id;}); + var def = $.when(); + if (! conv) { + conv = new im_common.Conversation(this, this, session_id, this.options); + def = conv.appendTo($("body")).then(_.bind(function() { + conv.on("destroyed", this, function() { + this.conversations = _.without(this.conversations, conv); + this.calc_positions(); + }); + this.conversations.push(conv); + this.calc_positions(); + this.trigger("new_conversation", conv); + }, this)); + } + if (focus) { + def = def.then(function() { + conv.focus(); + }); + } + return def.then(function() {return conv}); + }, + received_messages: function(messages) { + var self = this; + var defs = []; + var received = false; + _.each(messages, function(message) { + if (! message.technical) { + defs.push(self.activate_session(message.session_id[0]).then(function(conv) { + received = true; + return conv.received_message(message); + })); + } else { + var json = JSON.parse(message.message); + message.json = json; + defs.push($.when(im_common.technical_messages_handlers[json.type](self, message))); + } + }); + if (! this.get("window_focus") && received) { + this.set("waiting_messages", this.get("waiting_messages") + messages.length); + this.ting.play(); + this.create_ting(); + } + return $.when.apply($, defs); + }, + calc_positions: function() { + var current = this.get("right_offset"); + _.each(_.range(this.conversations.length), function(i) { + this.conversations[i].set("bottom_position", this.get("bottom_offset")); + this.conversations[i].set("right_position", current); + current += this.conversations[i].$().outerWidth(true); + }, this); + }, + destroy: function() { + $(window).off("unload", this.unload_event_handler); + $(window).unbind("blur", this.blur_hdl); + $(window).unbind("focus", this.focus_hdl); + openerp.PropertiesMixin.destroy.call(this); + } + }); + + im_common.Conversation = openerp.Widget.extend({ + className: "openerp_style oe_im_chatview", + events: { + "keydown input": "keydown", + "click .oe_im_chatview_close": "close", + "click .oe_im_chatview_header": "show_hide" + }, + init: function(parent, c_manager, session_id, options) { + this._super(parent); + this.c_manager = c_manager; + this.options = options || {}; + this.session_id = session_id; + this.set("right_position", 0); + this.set("bottom_position", 0); + this.shown = true; + this.set("pending", 0); + this.inputPlaceholder = this.options.defaultInputPlaceholder; + this.set("users", []); + this.set("disconnected", false); + this.others = []; + }, + start: function() { + var self = this; + + self.$().append(openerp.qweb.render("im_common.conversation", {widget: self})); + this.$().hide(); + + var change_status = function() { + var disconnected = _.every(this.get("users"), function(u) { return u.get("im_status") === false; }); + self.set("disconnected", disconnected); + this.$(".oe_im_chatview_users").html(openerp.qweb.render("im_common.conversation.header", + {widget: self, to_url: _.bind(im_common.connection.url, im_common.connection)})); + }; + this.on("change:users", this, function(unused, ev) { + _.each(ev.oldValue, function(user) { + user.off("change:im_status", self, change_status); + }); + _.each(ev.newValue, function(user) { + user.on("change:im_status", self, change_status); + }); + change_status.call(self); + _.each(ev.oldValue, function(user) { + if (! _.contains(ev.newValue, user)) { + user.remove_watcher(); + } + }); + _.each(ev.newValue, function(user) { + if (! _.contains(ev.oldValue, user)) { + user.add_watcher(); + } + }); + }); + this.on("change:disconnected", this, function() { + self.$().toggleClass("oe_im_chatview_disconnected_status", this.get("disconnected")); + self._go_bottom(); + }); + + 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)); + + return this.refresh_users().then(function() { + self.$().show(); + }); + }, + refresh_users: function() { + var self = this; + var user_ids; + return im_common.connection.model("im.session").call("read", [self.session_id]).then(function(session) { + user_ids = _.without(session.user_ids, self.c_manager.me.get("id")); + return self.c_manager.ensure_users(user_ids); + }).then(function(users) { + self.set("users", users); + }); + }, + show_hide: function() { + if (this.shown) { + this.$().animate({ + height: this.$(".oe_im_chatview_header").outerHeight() + }); + } else { + this.$().animate({ + height: this.full_height + }); + } + this.shown = ! this.shown; + if (this.shown) { + this.set("pending", 0); + } + }, + calc_pos: function() { + this.$().css("right", this.get("right_position")); + this.$().css("bottom", this.get("bottom_position")); + }, + received_message: function(message) { + if (this.shown) { + this.set("pending", 0); + } else { + this.set("pending", this.get("pending") + 1); + } + this.c_manager.ensure_users([message.from_id[0]]).then(_.bind(function(users) { + var user = users[0]; + if (! _.contains(this.get("users"), user) && ! _.contains(this.others, user)) { + this.others.push(user); + user.add_watcher(); + } + this._add_bubble(user, message.message, openerp.str_to_datetime(message.date)); + }, this)); + }, + 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); + }, + send_message: function(message, technical) { + technical = technical || false; + var send_it = _.bind(function() { + var model = im_common.connection.model("im.message"); + return model.call("post", [message, this.session_id, technical], {uuid: this.c_manager.me.get("uuid"), context: {}}); + }, this); + var tries = 0; + send_it().then(_.bind(function() {}, function(error, e) { + e.preventDefault(); + tries += 1; + if (tries < 3) + return send_it(); + })); + }, + _add_bubble: function(user, item, date) { + var items = [item]; + if (user === this.last_user) { + this.last_bubble.remove(); + items = this.last_items.concat(items); + } + this.last_user = user; + this.last_items = items; + var zpad = function(str, size) { + str = "" + str; + return new Array(size - str.length + 1).join('0') + str; + }; + date = "" + zpad(date.getHours(), 2) + ":" + zpad(date.getMinutes(), 2); + var to_show = _.map(items, im_common.escape_keep_url); + this.last_bubble = $(openerp.qweb.render("im_common.conversation_bubble", {"items": to_show, "user": user, "time": date})); + $(this.$(".oe_im_chatview_content").children()[0]).append(this.last_bubble); + this._go_bottom(); + }, + _go_bottom: function() { + this.$(".oe_im_chatview_content").scrollTop($(this.$(".oe_im_chatview_content").children()[0]).height()); + }, + add_user: function(user) { + if (user === this.me || _.contains(this.get("users"), user)) + return; + im_common.connection.model("im.session").call("add_to_session", + [this.session_id, user.get("id"), this.c_manager.me.get("uuid")]).then(_.bind(function() { + this.send_message(JSON.stringify({"type": "session_modified", "action": "added", "user_id": user.get("id")}), true); + }, this)); + }, + focus: function() { + this.$(".oe_im_chatview_input").focus(); + if (! this.shown) + this.show_hide(); + }, + close: function() { + var def = $.when(); + if (this.get("users").length > 1) { + def = im_common.connection.model("im.session").call("remove_me_from_session", + [this.session_id, this.c_manager.me.get("uuid")]).then(_.bind(function() { + return this.send_message(JSON.stringify({"type": "session_modified", "action": "removed", + "user_id": this.c_manager.me.get("id")}), true) + }, this)) + } + + return def.then(_.bind(function() { + this.destroy(); + }, this)); + }, + destroy: function() { + _.each(this.get("users"), function(user) { + user.remove_watcher(); + }) + _.each(this.others, function(user) { + user.remove_watcher(); + }) + this.trigger("destroyed"); + return this._super(); + } + }); + + im_common.technical_messages_handlers = {}; + + im_common.technical_messages_handlers.session_modified = function(c_manager, message) { + var def = $.when(); + if (message.json.action === "added" && message.json.user_id === c_manager.me.get("id")) { + def = c_manager.activate_session(message.session_id[0], true); + } + return def.then(function() { + var conv = _.find(c_manager.conversations, function(conv) {return conv.session_id == message.session_id[0];}); + if (conv) + return conv.refresh_users(); + return undefined; + }); + }; + + var url_regex = /(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/gi; + + im_common.escape_keep_url = function(str) { + 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 += '<a href="' + url + '">' + url + '</a>'; + } + txt += str.slice(last, str.length); + return txt; + }; + + return im_common; +} + +if (typeof(define) !== "undefined") { + define(["jquery", "underscore", "openerp"], declare); +} else { + window.im_common = declare($, _, openerp); +} + +})(); diff --git a/addons/im/static/src/xml/im.xml b/addons/im/static/src/xml/im.xml index 795d1b3e698..1acb538db4f 100644 --- a/addons/im/static/src/xml/im.xml +++ b/addons/im/static/src/xml/im.xml @@ -27,37 +27,4 @@ <img t-att-src="_s +'/im/static/src/img/green.png'" class="oe_im_user_online"/> </div> </t> - <t t-name="Conversation"> - <div class="oe_im_chatview"> - <div class="oe_im_chatview_header"> - <img t-att-src="_s +'/im/static/src/img/green.png'" class="oe_im_chatview_online"/> - <t t-esc="widget.user.get('name') || 'Anonymous'"/> - <scan class="oe_im_chatview_nbr_messages" /> - <button class="oe_im_chatview_close">×</button> - </div> - <div class="oe_im_chatview_disconnected"> - <t t-esc='_.str.sprintf(_t("%s is offline. He/She will receive your messages on his/her next connection."), widget.user.get("name") || "Anonymous")'/> - </div> - <div class="oe_im_chatview_content"> - <div></div> - </div> - <div class="oe_im_chatview_footer"> - <input class="oe_im_chatview_input" t-att-placeholder="_t('Say something...')" /> - </div> - </div> - </t> - <t t-name="Conversation.bubble"> - <div class="oe_im_chatview_bubble"> - <div class="oe_im_chatview_clip"> - <img class="oe_im_chatview_avatar" t-att-src='user.get("image_url")'/> - </div> - <div class="oe_im_chatview_from"><t t-esc="user.get('name') || 'Anonymous'"/></div> - <div class="oe_im_chatview_bubble_list"> - <t t-foreach="items" t-as="item"> - <div class="oe_im_chatview_bubble_item"><t t-esc="item"/></div> - </t> - </div> - <div class="oe_im_chatview_time"><t t-esc="time"/></div> - </div> - </t> </templates>
\ No newline at end of file diff --git a/addons/im/static/src/xml/im_common.xml b/addons/im/static/src/xml/im_common.xml new file mode 100644 index 00000000000..462e242befd --- /dev/null +++ b/addons/im/static/src/xml/im_common.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<templates> +<t t-name="im_common.conversation"> + <div class="oe_im_chatview_header"> + <span class="oe_im_chatview_users"/> + <scan class="oe_im_chatview_nbr_messages" /> + <button class="oe_im_chatview_close">×</button> + </div> + <div class="oe_im_chatview_disconnected"> + All users are offline. They will receive your messages on their next connection. + </div> + <div class="oe_im_chatview_content"> + <div></div> + </div> + <div class="oe_im_chatview_footer"> + <input class="oe_im_chatview_input" t-att-placeholder="widget.inputPlaceholder" /> + </div> +</t> + +<t t-name="im_common.conversation.header"> + <span t-foreach="widget.get('users')" t-as="user"> + <img t-if="user.get('im_status')" t-att-src="to_url('/im/static/src/img/green.png')" class="oe_im_chatview_online"/> + <t t-esc="user.get('name')"/> + </span> +</t> + +<t t-name="im_common.conversation_bubble"> + <div class="oe_im_chatview_bubble"> + <div class="oe_im_chatview_clip"> + <img class="oe_im_chatview_avatar" t-att-src="user.get('image_url')"/> + </div> + <div class="oe_im_chatview_from"><t t-esc="user.get('name')"/></div> + <div class="oe_im_chatview_bubble_list"> + <t t-foreach="items" t-as="item"> + <div class="oe_im_chatview_bubble_item"><t t-raw="item"/></div> + </t> + </div> + <div class="oe_im_chatview_time"><t t-esc="time"/></div> + </div> +</t> +</templates>
\ No newline at end of file |
