diff options
| author | Jérome Maes <jem@openerp.com> | 2015-06-01 16:37:07 +0200 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2015-09-01 20:16:08 +0200 |
| commit | 4af96310721237d3640bbd162ff9ea84e627028d (patch) | |
| tree | 2deb74fa477c9ea33b1a0a65199b96e2d6e5f02b /addons/mail/static | |
| parent | 5907be1f335dc21934619a34fc928a19e7885211 (diff) | |
[MOV] mail, im_chat : move files and code from im_chat to mail and bus
- im_chat.session will be replaced by mail.channel
- im_chat.shortode is renamed into mail.shortcode
- im_chat.presence is moved to bus module
- js and controller code is moved from im_chat to mail module
This commit only move files, and modify manifests, bundles, ... The code will be adapt in the next commits.
Diffstat (limited to 'addons/mail/static')
37 files changed, 1210 insertions, 0 deletions
diff --git a/addons/mail/static/src/audio/purr.mp3 b/addons/mail/static/src/audio/purr.mp3 Binary files differnew file mode 100644 index 00000000000..849d303c3b1 --- /dev/null +++ b/addons/mail/static/src/audio/purr.mp3 diff --git a/addons/mail/static/src/audio/purr.ogg b/addons/mail/static/src/audio/purr.ogg Binary files differnew file mode 100644 index 00000000000..2ddc77537f1 --- /dev/null +++ b/addons/mail/static/src/audio/purr.ogg diff --git a/addons/mail/static/src/audio/ting.mp3 b/addons/mail/static/src/audio/ting.mp3 Binary files differnew file mode 100644 index 00000000000..fd29bf599d6 --- /dev/null +++ b/addons/mail/static/src/audio/ting.mp3 diff --git a/addons/mail/static/src/audio/ting.ogg b/addons/mail/static/src/audio/ting.ogg Binary files differnew file mode 100644 index 00000000000..cc8c0f01eff --- /dev/null +++ b/addons/mail/static/src/audio/ting.ogg diff --git a/addons/mail/static/src/css/mail_chat_backend.css b/addons/mail/static/src/css/mail_chat_backend.css new file mode 100644 index 00000000000..d01577eb56a --- /dev/null +++ b/addons/mail/static/src/css/mail_chat_backend.css @@ -0,0 +1,114 @@ +.oe_im { + position: fixed; + background-color: #eaeaea; + width: 250px; + border-left: 1px solid #AEB9BD; + z-index: 500; + -webkit-box-shadow: -2px 0px 5px 0px rgba(50, 50, 50, 0.25); + -moz-box-shadow: -2px 0px 5px 0px rgba(50, 50, 50, 0.25); + box-shadow: -2px 0px 5px 0px rgba(50, 50, 50, 0.25); +} + +@media print { + .oe_im, .oe_im_chatview { + visibility: hidden; + } +} +/* button */ +.oe_systray #oe_topbar_imbutton_icon { + color: white; +} +.oe_topbar_item.oe_topbar_imbutton .fa { + position: relative; + top: -8px; +} + +/* search stuff */ +.oe_im_frame_header { + position: relative; + padding: 16px; +} +.oe_im_frame_header .oe_im_searchbox { + width: 200px; + padding: 1px 21px 1px 19px; + font-size: 13px; + border: none; + border-bottom: 1px solid #c6c6c6; + background: none; + margin-left: 20px; + height: 38px; +} +.oe_im_frame_header .oe_im_searchbox:focus { + outline: none; +} +.oe_im_frame_header .oe_im_search_icon { + position: absolute; + color: #888; + top: 28px; + left: 8px; + font-size: 16px; + font-weight: 300 !important; +} +.oe_im_frame_header .oe_im_search_clear { + display: none; + position: absolute; + right: 11px; + top: 4px; + font-size: 26px; + color: #b6b6b6; + cursor: pointer; +} +.oe_im_frame_header .oe_im_search_clear:hover { + color: #888; +} + +/* users */ + +.oe_im_users { + padding-bottom: 38px; + height: 100%; + overflow-y: scroll; +} +.oe_im_user { + position: relative; + padding: 2px 8px; + cursor: pointer; + font-size: 13px; + margin-bottom: 10px; +} +.oe_im_user:hover { + background: lightGrey; +} +.oe_im_user_clip { + display: inline-block; + 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_user_avatar { + width: 26px; + height: auto; +} +.oe_im_user_name { + width: 162px; + line-height: 26px; + padding-right: 15px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + position: relative; +} + +.oe_im_user_online { + display: none; + position: absolute; + top: 9.5px; + right: 11px; + width: 11px; + height: 11px; + vertical-align: middle; + border: 0; +} diff --git a/addons/mail/static/src/css/mail_chat_common.css b/addons/mail/static/src/css/mail_chat_common.css new file mode 100644 index 00000000000..1ad2981eb41 --- /dev/null +++ b/addons/mail/static/src/css/mail_chat_common.css @@ -0,0 +1,259 @@ + + + +.openerp_style { /* base style of openerp */ + font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; + color: #4c4c4c; + font-size: 13px; + background: white; +} + +/* 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; + z-index:500; +} + +/* conversations */ + +.oe_im_chatview { + position: fixed; + overflow: hidden; + margin-bottom: 5px; + margin-right: 6px; + background-color: #fff; + border: 1px solid #DEDEDE; + width: 240px; + z-index:500; +} +@media screen and (max-width:360px) { + .oe_im_chatview { + width: 100%; + margin-right: 0; + } + .oe_im_chatview .oe_im_chatview_content { + width: 100% !important; + } + .oe_im_chatview .oe_im_chatview_input { + width: 100% !important; + } +} +.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; +} + + + +/* ####### 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-bottom: 1px solid #AEB9BD; + cursor: pointer; +} + +.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_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; +} + +.oe_im_chatview_header .right_buttons .dropdown-menu{ + right: 0; + left: auto; +} +.oe_im_chatview_header .right_buttons .dropdown-menu > li{ + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #4c4c4c; + white-space: nowrap; +} + + + +.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_message_bubble { + background: white; + position: relative; + min-height: 32px; + padding: 3px; + margin: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +/* Smiley size */ +.oe_im_chatview .oe_im_chatview_bubble_item img{ + width: 18px; +} + +.oe_im_chatview .oe_im_chatview_technical_bubble { + background: #D8D8D8; + position: relative; + min-height: 32px; + padding: 3px; + margin: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + + font-style:italic; +} + +.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; +} + +.oe_im_chatview .oe_im_chatview_date_separator { + background: transparent; + position: relative; + padding: 3px; + color: #aaa; + margin: 3px; + text-align: right; + font-size: 12px; +}
\ No newline at end of file diff --git a/addons/mail/static/src/img/smiley/avatar.jpg b/addons/mail/static/src/img/smiley/avatar.jpg Binary files differnew file mode 100644 index 00000000000..7168794022e --- /dev/null +++ b/addons/mail/static/src/img/smiley/avatar.jpg diff --git a/addons/mail/static/src/img/smiley/beer.png b/addons/mail/static/src/img/smiley/beer.png Binary files differnew file mode 100644 index 00000000000..cd78bed7440 --- /dev/null +++ b/addons/mail/static/src/img/smiley/beer.png diff --git a/addons/mail/static/src/img/smiley/boar.png b/addons/mail/static/src/img/smiley/boar.png Binary files differnew file mode 100644 index 00000000000..3ef50325016 --- /dev/null +++ b/addons/mail/static/src/img/smiley/boar.png diff --git a/addons/mail/static/src/img/smiley/cat.png b/addons/mail/static/src/img/smiley/cat.png Binary files differnew file mode 100644 index 00000000000..dbf1b0276ab --- /dev/null +++ b/addons/mail/static/src/img/smiley/cat.png diff --git a/addons/mail/static/src/img/smiley/confused.png b/addons/mail/static/src/img/smiley/confused.png Binary files differnew file mode 100644 index 00000000000..8dc494db083 --- /dev/null +++ b/addons/mail/static/src/img/smiley/confused.png diff --git a/addons/mail/static/src/img/smiley/cookie.png b/addons/mail/static/src/img/smiley/cookie.png Binary files differnew file mode 100644 index 00000000000..177d0f9ad86 --- /dev/null +++ b/addons/mail/static/src/img/smiley/cookie.png diff --git a/addons/mail/static/src/img/smiley/ghost.png b/addons/mail/static/src/img/smiley/ghost.png Binary files differnew file mode 100644 index 00000000000..671dd0c9e2e --- /dev/null +++ b/addons/mail/static/src/img/smiley/ghost.png diff --git a/addons/mail/static/src/img/smiley/green.png b/addons/mail/static/src/img/smiley/green.png Binary files differnew file mode 100644 index 00000000000..0700a5ef71b --- /dev/null +++ b/addons/mail/static/src/img/smiley/green.png diff --git a/addons/mail/static/src/img/smiley/heart.png b/addons/mail/static/src/img/smiley/heart.png Binary files differnew file mode 100644 index 00000000000..7d7790ce4df --- /dev/null +++ b/addons/mail/static/src/img/smiley/heart.png diff --git a/addons/mail/static/src/img/smiley/laugh.png b/addons/mail/static/src/img/smiley/laugh.png Binary files differnew file mode 100644 index 00000000000..11c91eb22e6 --- /dev/null +++ b/addons/mail/static/src/img/smiley/laugh.png diff --git a/addons/mail/static/src/img/smiley/man.png b/addons/mail/static/src/img/smiley/man.png Binary files differnew file mode 100644 index 00000000000..c4a4217f4d4 --- /dev/null +++ b/addons/mail/static/src/img/smiley/man.png diff --git a/addons/mail/static/src/img/smiley/man_with_turban.png b/addons/mail/static/src/img/smiley/man_with_turban.png Binary files differnew file mode 100644 index 00000000000..8a0bda1ca77 --- /dev/null +++ b/addons/mail/static/src/img/smiley/man_with_turban.png diff --git a/addons/mail/static/src/img/smiley/musti.png b/addons/mail/static/src/img/smiley/musti.png Binary files differnew file mode 100644 index 00000000000..f6843ddf95c --- /dev/null +++ b/addons/mail/static/src/img/smiley/musti.png diff --git a/addons/mail/static/src/img/smiley/neutral.png b/addons/mail/static/src/img/smiley/neutral.png Binary files differnew file mode 100644 index 00000000000..682a1ba066d --- /dev/null +++ b/addons/mail/static/src/img/smiley/neutral.png diff --git a/addons/mail/static/src/img/smiley/open_mouth.png b/addons/mail/static/src/img/smiley/open_mouth.png Binary files differnew file mode 100644 index 00000000000..daf914274a6 --- /dev/null +++ b/addons/mail/static/src/img/smiley/open_mouth.png diff --git a/addons/mail/static/src/img/smiley/pinky.png b/addons/mail/static/src/img/smiley/pinky.png Binary files differnew file mode 100644 index 00000000000..4466946601c --- /dev/null +++ b/addons/mail/static/src/img/smiley/pinky.png diff --git a/addons/mail/static/src/img/smiley/poop.png b/addons/mail/static/src/img/smiley/poop.png Binary files differnew file mode 100644 index 00000000000..73a4dc84008 --- /dev/null +++ b/addons/mail/static/src/img/smiley/poop.png diff --git a/addons/mail/static/src/img/smiley/postal_horn.png b/addons/mail/static/src/img/smiley/postal_horn.png Binary files differnew file mode 100644 index 00000000000..5b7ed85d4fc --- /dev/null +++ b/addons/mail/static/src/img/smiley/postal_horn.png diff --git a/addons/mail/static/src/img/smiley/sad.png b/addons/mail/static/src/img/smiley/sad.png Binary files differnew file mode 100644 index 00000000000..82552008719 --- /dev/null +++ b/addons/mail/static/src/img/smiley/sad.png diff --git a/addons/mail/static/src/img/smiley/scream.png b/addons/mail/static/src/img/smiley/scream.png Binary files differnew file mode 100644 index 00000000000..76bfc6b8a65 --- /dev/null +++ b/addons/mail/static/src/img/smiley/scream.png diff --git a/addons/mail/static/src/img/smiley/smile.png b/addons/mail/static/src/img/smiley/smile.png Binary files differnew file mode 100644 index 00000000000..81a83968996 --- /dev/null +++ b/addons/mail/static/src/img/smiley/smile.png diff --git a/addons/mail/static/src/img/smiley/stuck_out_tongue.png b/addons/mail/static/src/img/smiley/stuck_out_tongue.png Binary files differnew file mode 100644 index 00000000000..ea9aeb99e29 --- /dev/null +++ b/addons/mail/static/src/img/smiley/stuck_out_tongue.png diff --git a/addons/mail/static/src/img/smiley/thumbsdown.png b/addons/mail/static/src/img/smiley/thumbsdown.png Binary files differnew file mode 100644 index 00000000000..74a5005c893 --- /dev/null +++ b/addons/mail/static/src/img/smiley/thumbsdown.png diff --git a/addons/mail/static/src/img/smiley/thumbsup.png b/addons/mail/static/src/img/smiley/thumbsup.png Binary files differnew file mode 100644 index 00000000000..116388fc3e2 --- /dev/null +++ b/addons/mail/static/src/img/smiley/thumbsup.png diff --git a/addons/mail/static/src/img/smiley/trollface.png b/addons/mail/static/src/img/smiley/trollface.png Binary files differnew file mode 100644 index 00000000000..eb222e2ad69 --- /dev/null +++ b/addons/mail/static/src/img/smiley/trollface.png diff --git a/addons/mail/static/src/img/smiley/wink.png b/addons/mail/static/src/img/smiley/wink.png Binary files differnew file mode 100644 index 00000000000..756766dd3e9 --- /dev/null +++ b/addons/mail/static/src/img/smiley/wink.png diff --git a/addons/mail/static/src/img/smiley/yellow.png b/addons/mail/static/src/img/smiley/yellow.png Binary files differnew file mode 100644 index 00000000000..726570e518f --- /dev/null +++ b/addons/mail/static/src/img/smiley/yellow.png diff --git a/addons/mail/static/src/img/smiley/yum.png b/addons/mail/static/src/img/smiley/yum.png Binary files differnew file mode 100644 index 00000000000..fc39637ecd8 --- /dev/null +++ b/addons/mail/static/src/img/smiley/yum.png diff --git a/addons/mail/static/src/js/mail_chat_backend.js b/addons/mail/static/src/js/mail_chat_backend.js new file mode 100644 index 00000000000..cd1cc00fdf2 --- /dev/null +++ b/addons/mail/static/src/js/mail_chat_backend.js @@ -0,0 +1,301 @@ +odoo.define('im_chat.im_chat', function (require) { +"use strict"; + +// to do: make this work in website + +var bus = require('bus.bus'); +var core = require('web.core'); +var data = require('web.data'); +var Model = require('web.DataModel'); +var session = require('web.session'); +var SystrayMenu = require('web.SystrayMenu'); +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 USERS_LIMIT = 20; + + +// ########## CONVERSATION extentions ############### + +im_chat_common.ConversationManager.include({ + _message_receive: function(message){ + var self = this; + var session_id = message.to_id[0]; + 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.session_apply(session, {'force_open': true}); + conv.message_receive(message); + }); + }else{ + this._super(message); + } + }, +}); + +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); + }, + action_shorcode: function(e){ + return web_client.action_manager.do_action({ + type: 'ir.actions.act_window', + name : _t('Shortcode'), + res_model: 'im_chat.shortcode', + view_mode: 'tree,form', + view_type: 'tree', + views: [[false, 'list'], [false, 'form']], + target: "new", + limit: 80, + flags: { + action_buttons: true, + pager: true, + } + }); + }, + 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); + }, + // TODO : change this way + add_user: function(user){ + return new Model("im_chat.session").call("add_user", [this.get("session").uuid , user.id]); + }, +}); + + + +// ###### BACKEND : contact panel, top menu button ######### + +var UserWidget = 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', img_src); + }, + activate_user: function() { + this.trigger("user_clicked", this.get("id")); + }, +}); + +var InstantMessaging = 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 = {}; + + // 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()); + this.position_compute(); + + // business + this.on("change:current_search", this, this.user_search); + + // add a drag & drop listener + self.conv_manager.on("im_session_activated", self, function(conv) { + conv.$el.droppable({ + drop: function(event, ui) { + conv.add_user(ui.draggable.data("user")); + } + }); + }); + + // fetch the unread message and the recent activity (e.i. to re-init in case of refreshing page) + return session.rpc("/im_chat/init", {}).then(function(notifications) { + _.each(notifications, function(notif){ + self.conv_manager.on_notification(notif, {'load_history': true}); + }); + // start polling + self.bus.start_polling(); + }); + }, + 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 top = 48; + this.$el.css("top", top); + this.$el.css("bottom", 0); + }, + input_change: function() { + this.set("current_search", this.$(".oe_im_searchbox").val()); + }, + switch_display: function() { + this.position_compute(); + var fct = _.bind(function(place) { + this.set("right_offset", place + this.$el.outerWidth()); + this.$(".oe_im_searchbox").focus(); + }, this); + var opt = { + step: fct, + }; + if (this.shown) { + this.$el.animate({ + right: -this.$el.outerWidth(), + }, opt); + } else { + if (! bus.bus.activated) { + this.do_warn("Instant Messaging is not activated on this server. Try later.", ""); + return; + } + // update the list of user status when show the IM + this.user_search(); + this.$el.animate({ + right: 0, + }, opt); + } + this.shown = ! this.shown; + }, + // 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/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.conv_manager.session_apply(session, {'focus': true, 'force_open': true}); + }); + }, + user_update_status: function(users_list){ + var self = this; + _.each(users_list, function(el) { + if (self.widgets[el.id]) { + self.widgets[el.id].set("im_status", el.im_status); + } + }); + } +}); + + +var ImTopButton = Widget.extend({ + template:'im_chat.ImTopButton', + events: { + "click": "clicked", + }, + start: function() { + // Create the InstantMessaging widget and put it in the DOM + var im = new InstantMessaging(this); + web_client.im_messaging = im; + im.appendTo(web_client.$el); + // Bind the click action to the ImTopButton + this.on("clicked", im, im.switch_display); + return this._super(); + }, + clicked: function(ev) { + ev.preventDefault(); + this.trigger("clicked"); + }, +}); + +// Put the ImTopButton widget in the systray menu if the user is an employee +var Users = new Model('res.users'); +Users.call('has_group', ['base.group_user']).done(function(is_employee) { + if (is_employee) { + SystrayMenu.Items.push(ImTopButton); + } +}); + +return { + InstantMessaging: InstantMessaging, + UserWidget: UserWidget, +}; + +}); diff --git a/addons/mail/static/src/js/mail_chat_common.js b/addons/mail/static/src/js/mail_chat_common.js new file mode 100644 index 00000000000..0aaa0e68d16 --- /dev/null +++ b/addons/mail/static/src/js/mail_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/mail/static/src/xml/mail_chat_common.xml b/addons/mail/static/src/xml/mail_chat_common.xml new file mode 100644 index 00000000000..eb479863b3e --- /dev/null +++ b/addons/mail/static/src/xml/mail_chat_common.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> +<t t-name="im_chat.Conversation"> + <div class="oe_im_chatview_header"> + <span class="header_name"></span> + <span class="nbr_messages"/> + <span class="right_buttons"> + <!-- options --> + <div t-if="widget.actions.length !== 0" class="btn-group button_option_group"> + <span class="dropdown-toggle" data-toggle="dropdown"> + <i class="fa fa-cogs"></i> + </span> + <ul class="dropdown-menu" role="menu"> + <t t-foreach="widget.actions" t-as="action"> + <li t-att-class="action.class"> + <i t-att-class="action.icon_class"></i> <t t-esc="action.label"/> + </li> + </t> + </ul> + </div> + <div class="oe_im_chatview_close">×</div> + </span> + </div> + <div class="oe_im_chatview_content"> + <div class="oe_im_chatview_status"/> + <div class="oe_im_chatview_content_bubbles"></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_chat.Conversation_content"> + <t t-foreach="_.keys(list)" t-as="date"> + <div class="oe_im_chatview_date_separator"> + <t t-esc="window.moment(date).format('LL')"/> + </div> + <t t-foreach="list[date]" t-as="bubble"> + <t t-if="bubble[0].type === 'message'"> + <t t-call="im_chat.Conversation_message_bubble"> + <t t-set="messages" t-value="bubble"/> + </t> + </t> + <t t-if="bubble[0].type === 'meta'"> + <t t-call="im_chat.Conversation_technical_bubble"> + <t t-set="messages" t-value="bubble"/> + </t> + </t> + </t> + </t> +</t> +<t t-name="im_chat.Conversation_message_bubble"> + <div class="oe_im_chatview_message_bubble"> + <div class="oe_im_chatview_clip"> + <img class="oe_im_chatview_avatar" t-att-src="_.last(messages).from_id[2]"/> + </div> + <div class="oe_im_chatview_from"><t t-esc="_.last(messages).from_id[1]"/></div> + <div class="oe_im_chatview_bubble_list"> + <t t-foreach="messages" t-as="m"> + <div class="oe_im_chatview_bubble_item"><t t-raw="m.message"/></div> + </t> + </div> + <div class="oe_im_chatview_time"><t t-esc="window.moment((_.last(messages).create_date)).format('LT')"/></div> + </div> +</t> +<t t-name="im_chat.Conversation_technical_bubble"> + <div class="oe_im_chatview_technical_bubble"> + <div class="oe_im_chatview_from"><t t-esc="_.last(messages).from_id[1]"/></div> + <div> + <t t-foreach="messages" t-as="m"> + <div><t t-raw="m.message"/></div> + </t> + </div> + <div class="oe_im_chatview_time"><t t-esc="window.moment((_.last(messages).create_date)).format('LT')"/></div> + </div> +</t> + +<t t-name="im_chat.UserWidget"> + <div class="oe_im_user ui-draggable"> + <span class="oe_im_user_clip"> + <img t-att-src="widget.get('image_url')" class="oe_im_user_avatar"/> + </span> + <span class="oe_im_user_name"><t t-esc="widget.get('name')"/></span> + <img t-att-src="'/im_chat/static/src/img/green.png'" t-att-data-im-user-id="widget.get('id')" class="oe_im_user_online"/> + </div> +</t> +<t t-name="im_chat.InstantMessaging"> + <div class="oe_im"> + <div class="oe_im_frame_header"> + <i class="fa fa-search oe_im_search_icon"/> + <input class="oe_im_searchbox" placeholder="Search users..."/> + </div> + <div class="oe_im_users"></div> + <div class="oe_im_content"></div> + </div> +</t> +<t t-name="im_chat.ImTopButton"> + <li t-att-title='_t("Display Instant Messaging")'> + <a href="#"> + <i id="oe_topbar_imbutton_icon" class="fa fa-comments"/> + </a> + </li> +</t> +</templates> |
