diff options
| author | Géry Debongnie <ged@odoo.com> | 2015-09-30 14:12:19 +0200 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2015-10-01 14:00:18 +0200 |
| commit | 62be162877f30a6ca0f61b38f0b070d56f7ab419 (patch) | |
| tree | 1d2f750c3ce7f29bd0547acdc98afa160a827f04 /addons/mail/static/src | |
| parent | 512287cac1fb7d4da632a4be0922679b203aff66 (diff) | |
[REF] mail: second part of the chat refactoring (client side)
Joint work with aab
Diffstat (limited to 'addons/mail/static/src')
| -rw-r--r-- | addons/mail/static/src/js/chat_window.js | 77 | ||||
| -rw-r--r-- | addons/mail/static/src/js/client_action.js | 461 | ||||
| -rw-r--r-- | addons/mail/static/src/js/systray.js | 97 | ||||
| -rw-r--r-- | addons/mail/static/src/js/window_manager.js | 115 | ||||
| -rw-r--r-- | addons/mail/static/src/less/chat_window.less | 50 | ||||
| -rw-r--r-- | addons/mail/static/src/less/client_action.less | 188 | ||||
| -rw-r--r-- | addons/mail/static/src/xml/chat_window.xml | 24 | ||||
| -rw-r--r-- | addons/mail/static/src/xml/client_action.xml | 97 | ||||
| -rw-r--r-- | addons/mail/static/src/xml/systray.xml | 20 |
9 files changed, 1129 insertions, 0 deletions
diff --git a/addons/mail/static/src/js/chat_window.js b/addons/mail/static/src/js/chat_window.js new file mode 100644 index 00000000000..c9ca3675054 --- /dev/null +++ b/addons/mail/static/src/js/chat_window.js @@ -0,0 +1,77 @@ +odoo.define('mail.ChatWindow', function (require) { +"use strict"; + +var ChatThread = require('mail.ChatThread'); + +var core = require('web.core'); +var Widget = require('web.Widget'); + +var _t = core._t; + +return Widget.extend({ + template: "mail.ChatWindow", + events: { + "keydown .o_chat_input": "on_keydown", + "click .o_chat_window_close": "on_click_close", + "click .o_chat_title": "on_click_fold", + }, + + init: function (parent, channel_id, title, is_folded) { + this._super(parent); + this.title = title; + this.channel_id = channel_id; + this.placeholder = _t("Say something"); + this.folded = is_folded; + }, + start: function () { + this.$content = this.$('.o_chat_content'); + this.$input = this.$('.o_chat_input input'); + + this.thread = new ChatThread(this, {display_avatar: false}); + this.thread.on('toggle_star_status', this, function (message_id) { + this.trigger('toggle_star_status', message_id); + }); + + this.fold(); + var def = this.thread.appendTo(this.$content); + return $.when(this._super(), def); + }, + render: function (messages) { + this.thread.render(messages, {display_load_more: false}); + }, + scrollBottom: function () { + this.$content.scrollTo('max'); + }, + fold: function () { + this.$el.animate({ + height: this.folded ? "28px" : "333px" + }); + }, + toggle_fold: function () { + this.folded = !this.folded; + this.fold(); + }, + on_keydown: function (event) { + if (event.which === $.ui.keyCode.ENTER) { + var message = { + content: this.$input.val(), + attachment_ids: [], + partner_ids: [], + channel_id: this.channel_id, + }; + this.$input.val(''); + this.trigger('post_message', message); + } + }, + on_click_close: function (event) { + event.stopPropagation(); + event.preventDefault(); + this.trigger("close_chat_session"); + }, + on_click_fold: function () { + this.trigger("fold_channel", this.channel_id); + this.toggle_fold(); + }, +}); + +}); diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js new file mode 100644 index 00000000000..a5797b8f58d --- /dev/null +++ b/addons/mail/static/src/js/client_action.js @@ -0,0 +1,461 @@ +odoo.define('mail.chat_client_action', function (require) { +"use strict"; + +var chat_manager = require('mail.chat_manager'); +var ChatComposer = require('mail.ChatComposer'); +var ChatThread = require('mail.ChatThread'); + +var ControlPanelMixin = require('web.ControlPanelMixin'); +var core = require('web.core'); +var data = require('web.data'); +var Dialog = require('web.Dialog'); +var framework = require('web.framework'); +var Model = require('web.Model'); + +var pyeval = require('web.pyeval'); +var SearchView = require('web.SearchView'); +var Sidebar = require('web.Sidebar'); +var Widget = require('web.Widget'); + +var QWeb = core.qweb; +var _t = core._t; + +/** + * Widget : Invite People to Channel Dialog + * + * Popup containing a 'many2many_tags' custom input to select multiple partners. + * Search user according to the input, and trigger event when selection is validated. + **/ +var PartnerInviteDialog = Dialog.extend({ + dialog_title: _t('Invite people'), + template: "mail.PartnerInviteDialog", + init: function(parent, title, channel_id){ + this.channel_id = channel_id; + + this._super(parent, { + title: title, + size: "medium", + buttons: [{ + text: _t("Invite"), + close: true, + classes: "btn-primary", + click: _.bind(this.on_click_add, this), + }], + }); + this.PartnersModel = new Model('res.partner'); + }, + start: function(){ + var self = this; + this.$input = this.$('.o_mail_chat_partner_invite_input'); + this.$input.select2({ + width: '100%', + allowClear: true, + multiple: true, + formatResult: function(item){ + var css_class = "fa-circle" + (item.im_status === 'online' ? "" : "-o"); + return $('<span class="fa">').addClass(css_class).text(item.text); + }, + query: function (query) { + self.PartnersModel.call('im_search', [query.term, 20]).then(function(result){ + var data = []; + _.each(result, function(partner){ + partner.text = partner.name; + data.push(partner); + }); + query.callback({results: data}); + }); + } + }); + return this._super.apply(this, arguments); + }, + on_click_add: function(){ + var self = this; + var data = this.$input.select2('data'); + if(data.length >= 1){ + var ChannelModel = new Model('mail.channel'); + return ChannelModel.call('channel_invite', [], {ids : [this.channel_id], partner_ids: _.pluck(data, 'id')}) + .then(function(){ + var names = _.pluck(data, 'text').join(', '); + var notification = _.str.sprintf(_t('You added <b>%s</b> to the conversation.'), names); + self.do_notify(_t('New people'), notification); + }); + } + }, +}); + +var ChatAction = Widget.extend(ControlPanelMixin, { + template: 'mail.client_action', + + events: { + "click .o_mail_chat_channel_item": function (event) { + event.preventDefault(); + var channel_id = this.$(event.currentTarget).data('channel-id'); + this.set_channel(channel_id); + }, + "click .o_mail_sidebar_title .o_add": function (event) { + event.preventDefault(); + var type = $(event.target).data("type"); + this.$('.o_mail_add_channel[data-type=' + type + ']') + .show() + .find("input").focus(); + }, + "blur .o_mail_add_channel input": function () { + this.$('.o_mail_add_channel') + .hide(); + }, + "click .o_mail_partner_unpin": function (event) { + event.stopPropagation(); + var self = this; + var channel_id = $(event.target).data("channel-id"); + chat_manager.unsubscribe(channel_id).then(function () { + self.render_sidebar(); + self.set_channel("channel_inbox"); + }); + }, + }, + + on_attach_callback: function () { + this.thread.scroll_to({offset: this.channels_scrolltop[this.channel_id]}); + }, + on_detach_callback: function () { + this.channels_scrolltop[this.channel_id] = this.thread.get_scrolltop(); + }, + + init: function(parent, action) { + this._super.apply(this, arguments); + this.action_manager = parent; + this.domain = []; + this.action = action; + this.channels_scrolltop = {}; + }, + + start: function() { + var self = this; + + // create searchview + var options = { + $buttons: $("<div>"), + action: this.action, + disable_groupby: true, + }; + var dataset = new data.DataSetSearch(this, 'mail.message'); + var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; + var default_channel = this.action.context.active_id || this.action.params.default_active_id || 'channel_inbox'; + this.searchview = new SearchView(this, dataset, view_id, {}, options); + this.searchview.on('search_data', this, this.on_search); + + this.sidebar = new Sidebar(this, { + sections: [ + {name: 'action', label: _t('Action')}, + ], + items: { + action: [ + {label: _t('Unsubscribe'), classname: 'o_mail_chat_button_unsubscribe', callback: this.on_click_button_unsubscribe}, + {label: _t('Settings'), classname: 'o_mail_chat_button_settings', callback: this.on_click_button_settings}, + ], + }, + }); + + this.composer = new ChatComposer(this); + this.thread = new ChatThread(this, { + no_content_helper: this.action.help, + }); + + this.$buttons = $(QWeb.render("mail.chat.ControlButtons", {})); + this.$buttons.find('button').css({display:"inline-block"}); + this.$buttons.on('click', '.o_mail_chat_button_invite', this.on_click_button_invite); + this.$buttons.on('click', '.o_mail_chat_button_detach', this.on_click_button_detach); + + this.thread.on('redirect', this, this.on_redirect); + this.thread.on('load_more_messages', this, this.load_more_messages); + this.thread.on('mark_as_read', this, function (message_id) { + chat_manager.mark_as_read(message_id); + }); + this.thread.on('toggle_star_status', this, function (message_id) { + chat_manager.toggle_star_status(message_id); + }); + this.composer.on('post_message', this, this.on_post_message); + + var def1 = this.thread.prependTo(this.$('.o_mail_chat_content')); + var def2 = this.composer.appendTo(this.$('.o_mail_chat_content')); + var def3 = this.searchview.appendTo($("<div>")); + var def4 = this.sidebar.appendTo($("<div>")); + + this.render_sidebar(); + + return $.when(def1, def2, def3, def4) + .then(this.set_channel.bind(this, default_channel)) + .then(function () { + chat_manager.bus.on('new_message', self, self.on_new_message); + chat_manager.bus.on('update_message', self, self.on_update_message); + chat_manager.bus.on('new_channel', self, self.on_new_channel); + chat_manager.bus.on('anyone_listening', self, function (channel, query) { + query.is_displayed = query.is_displayed || channel.id === self.channel_id; + }); + }); + }, + + render_sidebar: function () { + var self = this; + var $sidebar = QWeb.render("mail.chat.Sidebar", { + channels: chat_manager.get_channels(), + needaction_counter: chat_manager.get_needaction_counter() + }); + this.$(".o_mail_chat_sidebar").replaceWith($sidebar); + + this.$('.o_mail_add_channel[data-type=public]').find("input").autocomplete({ + source: function(request, response) { + self.last_search_val = request.term; + self.do_search_channel(request.term).done(function(result){ + result.push({ + 'label': _.str.sprintf('<strong>'+_t("Create %s")+'</strong>', '<em>"'+self.last_search_val+'"</em>'), + 'value': '_create', + }); + response(result); + }); + }, + select: function(event, ui) { + if (self.last_search_val) { + if (ui.item.value === '_create') { + chat_manager.create_channel(self.last_search_val, "public"); + } else { + chat_manager.join_channel(ui.item.id); + } + } + }, + focus: function(event) { + event.preventDefault(); + }, + html: true, + }); + + this.$('.o_mail_add_channel[data-type=dm]').find("input").autocomplete({ + source: function(request, response) { + self.last_search_val = request.term; + self.do_search_partner(request.term).done(function(result){ + response(result); + }); + }, + select: function(event, ui) { + var partner_id = ui.item.id; + chat_manager.create_channel(partner_id, "dm"); + }, + focus: function(event) { + event.preventDefault(); + }, + html: true, + }); + + this.$('.o_mail_add_channel[data-type=private]').find("input").on('keyup', this, function (event) { + var name = $(event.target).val(); + if(event.which === $.ui.keyCode.ENTER && name) { + chat_manager.create_channel(name, "private"); + } + }); + }, + + do_search_channel: function(search_val){ + var Channel = new Model("mail.channel"); + return Channel.call('channel_search_to_join', [search_val]).then(function(result){ + var values = []; + _.each(result, function(channel){ + values.push(_.extend(channel, { + 'value': channel.name, + 'label': channel.name, + })); + }); + return values; + }); + }, + + do_search_partner: function (search_val) { + var Partner = new Model("res.partner"); + return Partner.call('im_search', [search_val, 20]).then(function(result){ + var values = []; + _.each(result, function(user){ + values.push(_.extend(user, { + 'value': user.name, + 'label': user.name, + })); + }); + return values; + }); + }, + + set_channel: function (channel_id) { + var self = this; + // Store scroll position of previous channel + if (this.channel_id) { + this.channels_scrolltop[this.channel_id] = this.thread.get_scrolltop(); + } + var new_channel_scrolltop = this.channels_scrolltop[channel_id]; + var channel = chat_manager.get_channel(channel_id); + channel_id = channel.id; + this.channel_id = channel_id; + this.set("title", channel.name); + this.$buttons.toggle(channel.type !== "static"); + this.$buttons + .find('.o_mail_chat_button_invite') + .toggle(channel.type !== "dm"); + + this.update_cp(); + this.action.context.active_id = channel_id; + this.action.context.active_ids = [channel_id]; + + this.$('.o_mail_chat_channel_item') + .removeClass('o_active') + .filter('[data-channel-id=' + channel_id + ']') + .addClass('o_active'); + + this.$('.o_chat_composer').toggle(channel.type !== 'static'); + this.sidebar.$el.toggle(channel.type !== 'static'); + + return this.fetch_and_render_thread().then(function () { + self.thread.scroll_to({offset: new_channel_scrolltop}); + self.action_manager.do_push_state({ + action: self.action.id, + active_id: self.channel_id, + }); + }); + }, + + fetch_and_render_thread: function () { + var self = this; + return chat_manager.fetch(this.channel_id, this.domain).then(function(result) { + self.thread.render(result, {display_load_more: !chat_manager.all_history_loaded(self.channel_id)}); + }); + }, + + load_more_messages: function () { + var self = this; + var oldest_msg_id = this.$('.o_thread_message').first().data('messageId'); + var oldest_msg_selector = '.o_thread_message[data-message-id="' + oldest_msg_id + '"]'; + var offset = -framework.getPosition(document.querySelector(oldest_msg_selector)).top; + return chat_manager + .fetch_more(this.channel_id, this.domain) + .then(function(result) { + self.thread.render(result, {display_load_more: !chat_manager.all_history_loaded(self.channel_id)}); + offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; + self.thread.scroll_to({offset: offset}); + }); + }, + + update_cp: function () { + this.update_control_panel({ + breadcrumbs: this.action_manager.get_breadcrumbs(), + cp_content: { + $buttons: this.$buttons, + $searchview: this.searchview.$el, + $searchview_buttons: this.searchview.$buttons.contents(), + $sidebar: this.sidebar.$el, + }, + searchview: this.searchview, + }); + }, + + on_search: function (domains) { + var result = pyeval.sync_eval_domains_and_contexts({ + domains: domains + }); + + this.domain = result.domain; + this.fetch_and_render_thread(); + }, + + on_redirect: function (res_model, res_id) { + this.action_manager.do_push_state({ + model: res_model, + id: res_id, + }); + this.do_action({ + type:'ir.actions.act_window', + view_type: 'form', + view_mode: 'form', + res_model: res_model, + views: [[false, 'form']], + res_id: res_id, + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb, + }); + }, + on_reverse_breadcrumb: function () { + this.update_cp(); // do not reload the client action, just display it, but a refresh of the control panel is needed. + this.action_manager.do_push_state({ + action: this.action.id, + active_id: this.channel_id, + }); + }, + on_post_message: function (message) { + message.channel_id = this.channel_id; + chat_manager + .post_message(message) + .fail(function () { + // todo: display notification + }); + }, + on_new_message: function (message) { + var self = this; + if (_.contains(message.channel_ids, this.channel_id)) { + this.fetch_and_render_thread().then(function () { + self.thread.scroll_to({id: message.id}); + }); + } + // Dump scroll position of channels in which the new message arrived + this.channels_scrolltop = _.omit(this.channels_scrolltop, message.channel_ids); + }, + on_update_message: function (message) { + var self = this; + if ((this.channel_id === "channel_starred" && !message.is_starred) || + (this.channel_id === "channel_inbox" && !message.is_needaction)) { + chat_manager.fetch(this.channel_id, this.domain).then(function (messages) { + self.thread.remove_message_and_render(message.id, messages); + }); + } else if (_.contains(message.channel_ids, this.channel_id)) { + this.fetch_and_render_thread(); + } + }, + on_new_channel: function (channel) { + this.render_sidebar(); + if (channel.autoswitch) { + this.set_channel(channel.id); + } + }, + + on_click_button_invite: function () { + var channel = chat_manager.get_channel(this.channel_id); + var title = _.str.sprintf(_t('Invite people to %s'), channel.name); + new PartnerInviteDialog(this, title, this.channel_id).open(); + }, + on_click_button_detach: function () { + chat_manager.detach_channel(this.channel_id); + }, + + on_click_button_unsubscribe: function () { + var self = this; + var channel = chat_manager.get_channel(this.channel_id); + chat_manager + .unsubscribe(channel.id) + .then(this.set_channel.bind(this, "channel_inbox")) + .then(this.render_sidebar.bind(this)) + .then(function () { + var msg = _.str.sprintf(_t('You unsubscribed from <b>%s</b>.'), channel.name); + self.do_notify(_t("Unsubscribed"), msg); + }); + }, + on_click_button_settings: function() { + this.do_action({ + type: 'ir.actions.act_window', + res_model: "mail.channel", + res_id: this.channel_id, + views: [[false, 'form']], + target: 'current' + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb, + }); + }, +}); + + +core.action_registry.add('mail.chat.instant_messaging', ChatAction); + +}); diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js new file mode 100644 index 00000000000..f60ab76e04c --- /dev/null +++ b/addons/mail/static/src/js/systray.js @@ -0,0 +1,97 @@ +odoo.define('mail.systray', function (require) { +"use strict"; + +var core = require('web.core'); +var Model = require('web.Model'); +var session = require('web.session'); +var SystrayMenu = require('web.SystrayMenu'); +var web_client = require('web.web_client'); +var Widget = require('web.Widget'); + +var internal_bus = core.bus; + +/** + * Widget Top Menu Notification Counter + * + * Counter of notification in the Systray Menu. Need to know if InstantMessagingView is displayed to + * increment (or not) the counter. On click, should redirect to the client action. + **/ +var NotificationTopButton = Widget.extend({ + template:'mail.chat.NotificationTopButton', + events: { + "click": "on_click", + }, + init: function () { + this._super.apply(this, arguments); + this.set('counter', 0); + }, + willStart: function () { + var self = this; + var ir_model = new Model("ir.model.data"); + var def1 = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"]); + var def2 = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"]); + var def3 = session.rpc('/mail/needaction'); + var def4 = this._super(); + return $.when(def1, def2, def3, def4).then(function (menu_id, action_id, needaction_count) { + self.discuss_menu_id = menu_id; + self.client_action_id = action_id; + self.set('counter', needaction_count); + }); + }, + start: function () { + this.on("change:counter", this, this.on_change_counter); + // events + internal_bus.on('mail_needaction_new', this, this.counter_increment); + internal_bus.on('mail_needaction_done', this, this.counter_decrement); + return this._super(); + }, + counter_increment: function (inc) { + this.set('counter', this.get('counter')+inc); + }, + counter_decrement: function (dec) { + this.set('counter', this.get('counter')-dec); + }, + on_change_counter: function () { + this.$('.fa-comment').html(this.get('counter') || ''); + }, + on_click: function (event) { + var self = this; + event.preventDefault(); + this.do_action(this.client_action_id, {clear_breadcrumbs: true}).then(function () { + core.bus.trigger('change_menu_section', self.discuss_menu_id); + }); + }, +}); + +SystrayMenu.Items.push(NotificationTopButton); + + + +/** + * * Global ComposeMessage Top Button + * * + * * Add a link on the top user bar to write a full mail. It opens the form view + * * of the mail.compose.message (in a modal). + * */ +var ComposeMessageTopButton = Widget.extend({ + template:'mail.ComposeMessageTopButton', + events: { + "click": "on_compose_message", + }, + on_compose_message: function (ev) { + ev.preventDefault(); + web_client.action_manager.do_action({ + type: 'ir.actions.act_window', + res_model: 'mail.compose.message', + view_mode: 'form', + view_type: 'form', + views: [[false, 'form']], + target: 'new', + }); + }, +}); + +// Put the ComposeMessageTopButton widget in the systray menu +SystrayMenu.Items.push(ComposeMessageTopButton); + +}); diff --git a/addons/mail/static/src/js/window_manager.js b/addons/mail/static/src/js/window_manager.js new file mode 100644 index 00000000000..5f0f559ba19 --- /dev/null +++ b/addons/mail/static/src/js/window_manager.js @@ -0,0 +1,115 @@ +odoo.define('mail.window_manager', function (require) { +"use strict"; + +var chat_manager = require('mail.chat_manager'); +var ChatWindow = require('mail.ChatWindow'); + +var core = require('web.core'); +var web_client = require('web.web_client'); + + +// chat window management +//---------------------------------------------------------------- +var chat_sessions = []; + +function open_chat (session) { + if (!_.findWhere(chat_sessions, {id: session.id})) { + var chat_session = { + id: session.id, + uuid: session.uuid, + name: session.name, + window: new ChatWindow(web_client, session.id, session.name, session.is_folded), + }; + chat_session.window.on("close_chat_session", null, function () { + close_chat(chat_session); + chat_manager.close_chat_session(chat_session.id); + }); + chat_session.window.on("toggle_star_status", null, function (message_id) { + chat_manager.toggle_star_status(message_id); + }); + + chat_session.window.on("fold_channel", null, function (channel_id) { + chat_manager.fold_channel(channel_id); + }); + + chat_session.window.on("post_message", null, function (message) { + chat_manager.post_message(message); + }); + + chat_sessions.push(chat_session); + chat_session.window.appendTo($('body')) + .then(reposition_windows) + .then(function () { + return chat_manager.get_messages({channel_id: chat_session.id}); + }).then(function (messages) { + chat_session.window.render(messages); + chat_session.window.scrollBottom(); + }); + } +} + +function close_chat (chat_session) { + var session = _.find(chat_sessions, {id: chat_session.id}); + if (session) { + chat_sessions = _.without(chat_sessions, session); + session.window.destroy(); + reposition_windows(); + } +} + +function toggle_fold_chat (channel) { + var session = _.find(chat_sessions, {id: channel.id}); + if (session) { + session.window.toggle_fold(); + } +} + +function reposition_windows () { + _.each(chat_sessions, function (session, index) { + session.window.$el.css({right: 245 * index, bottom: 0}); + }); +} + +function update_sessions (message, scrollBottom) { + _.each(chat_sessions, function (session) { + if (_.contains(message.channel_ids, session.id)) { + chat_manager.get_messages({channel_id: session.id}).then(function (messages) { + session.window.render(messages); + if (scrollBottom) { + session.window.scrollBottom(); + } + }); + } + }); +} + + +core.bus.on('web_client_ready', null, function () { + chat_manager.bus.on('open_chat', null, open_chat); + chat_manager.bus.on('close_chat', null, close_chat); + chat_manager.bus.on('channel_toggle_fold', null, toggle_fold_chat); + + chat_manager.bus.on('new_message', null, function (message) { + update_sessions(message, true); + }); + + chat_manager.bus.on('update_message', null, function (message) { + update_sessions(message, false); + }); + + chat_manager.bus.on('anyone_listening', null, function (channel, query) { + _.each(chat_sessions, function (session) { + if (_.contains(channel.id, session.id)) { + query.is_displayed = true; + } + }); + }); + + _.each(chat_manager.get_channels(), function (channel) { + if (channel.is_detached) { + open_chat(channel); + } + }); +}); + +}); diff --git a/addons/mail/static/src/less/chat_window.less b/addons/mail/static/src/less/chat_window.less new file mode 100644 index 00000000000..43fb5c7f6f1 --- /dev/null +++ b/addons/mail/static/src/less/chat_window.less @@ -0,0 +1,50 @@ +@o-chat-window-bg: lighten(lightgray, 13%); + +.o_chat_window { + .o-flex-display(); + .o-flex-flow(column, nowrap); + position: fixed; + width: 240px; + height: 333px; + background-color: @o-chat-window-bg; + border: 1px solid gray; + + .o_chat_header { + .o-flex-display(); + .o-flex(0, 0, auto); + color: white; + background-color: @odoo-brand-primary; + padding: 5px; + + .o_chat_title { + cursor: pointer; + .o-flex(1, 1, auto); + } + + .o_chat_window_close { + color: white; + padding: 3px; + &:hover { + background-color: darken(@odoo-brand-primary, 10%); + } + } + } + + .o_chat_content { + .o-flex(1, 1, auto); + overflow: auto; + padding: 5px; + + .o_mail_thread .o_thread_date_separator { + border-color: lightgray; + .o_thread_date { + background-color: @o-chat-window-bg; + } + } + } + + .o_chat_input > input { + padding: 5px; + width: 100%; + } +} diff --git a/addons/mail/static/src/less/client_action.less b/addons/mail/static/src/less/client_action.less new file mode 100644 index 00000000000..ddb074f6923 --- /dev/null +++ b/addons/mail/static/src/less/client_action.less @@ -0,0 +1,188 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ +.o_mail_chat { + .o-position-absolute(0, 0, 0, 0); + height: 100%; + + @media (max-width: @screen-xs-max) { + position: static; + height: auto; + } + + .o-flex-display(); + .o-flex-flow(row, nowrap); + + .o_mail_chat_sidebar { + .o-flex(0, 0, 200px); + overflow: auto; + padding: 8px 0; + + .o_mail_sidebar_title { + .o-webclient-padding(@top: 5px, @bottom: 5px); + .o-flex-display(); + align-items: center; + justify-content: space-between; + h4 { + text-transform: uppercase; + } + .o_add { + padding: 8px; + } + } + .o_mail_chat_channel_item { + width: 100%; + .o-webclient-padding(@top: 5px, @bottom: 5px); + padding-left: 2*@odoo-horizontal-padding; // larger padding than titles + > .badge { + margin: 0; + } + .o_mail_partner_unpin { + padding: 8px; + padding-top: 2px; + } + } + .o_mail_add_channel { + width: 100%; + .o-webclient-padding(@top: 5px, @bottom: 5px); + padding-left: 2*@odoo-horizontal-padding; // larger padding than titles + span { + margin-right: 5px; + } + } + } + + .o_mail_chat_content { + .o-flex(1, 1, 100%); + height: 100%; + .o-flex-display(); + .o-flex-flow(column, nowrap); + @media (max-width: @screen-xs-max) { + height: auto; + } + + .o_mail_thread { + .o-flex(1, 0, 0); + overflow: auto; + .o-webclient-padding(); + + .o_chat_help_message { + padding: 25px 0; + text-align: justify; + } + } + .o_chat_composer { + .o-flex(0, 0, auto); + .o-webclient-padding(@top: 10px, @bottom: 5px); + + .o_composer { + .o-flex-display(); + + .o_composer_input { + .o-flex(1, 1, auto); + padding: 5px; + margin-right: 5px; + border: none; + } + + .o_composer_buttons { + .o-flex(0, 0, auto); + } + } + } + } +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ +.o_mail_chat { + .o_mail_chat_sidebar { + background-color: @gray; + color: @gray-lighter; + + .o_mail_chat_channel_item { + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + &:hover { + background-color: @gray-dark; + } + + > .badge { + color: white; + background-color: @odoo-brand-optional; + } + + &.o_active { + background-color: @gray-dark; + } + } + + .o_mail_add_channel { + display: none; + input { + color: black; + } + } + + .o_mail_chat_channel_unread { + font-weight: bold; + } + + .o_mail_sidebar_title { + h4 { + font-size: 13px; + text-transform: uppercase; + } + .o_add { + cursor: pointer; + &:hover { + background-color: @gray-dark; + } + } + } + + // .o_mail_chat_add_more_search { + // padding: @padding-base-vertical @padding-base-horizontal; + // .o_mail_chat_add_more_text { + // color: @gray-lighter; + // display: inline-block; + // font-style: italic; + // cursor: pointer; + // font-size: @font-size-base; + // } + // .o_mail_chat_add_more_text:hover { + // text-decoration: underline; + // color: white; + // cursor: pointer; + // } + // } + + } + + .o_mail_chat_content { + background-color: white; + + .o_chat_composer { + border-top: 1px solid @odoo-brand-optional; + background-color: @odoo-brand-optional; + + .o_attachment { + .o_attachment_name, .o_attachment_delete { + color: white; + } + } + + .o_composer_mentioned_partners { + color: white; + + > .badge { + color: white; + border: 1px solid white; + background-color: transparent; + } + } + } + } +} diff --git a/addons/mail/static/src/xml/chat_window.xml b/addons/mail/static/src/xml/chat_window.xml new file mode 100644 index 00000000000..e94c51767c2 --- /dev/null +++ b/addons/mail/static/src/xml/chat_window.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + + <t t-name="mail.ChatWindow"> + <div class="o_chat_window"> + <div class="o_chat_header"> + <span class="o_chat_title"> + <t t-raw="widget.title"/> + </span> + <span> + <a href="#" class="o_chat_window_close fa fa-close"></a> + </span> + </div> + <div class="o_chat_content"> + <!-- <t t-call="mail.chat.im.Conversation.messages"/> --> + </div> + <div class="o_chat_input"> + <input t-att-placeholder="widget.placeholder"/> + </div> + </div> + </t> + +</templates> diff --git a/addons/mail/static/src/xml/client_action.xml b/addons/mail/static/src/xml/client_action.xml new file mode 100644 index 00000000000..8a23b33333e --- /dev/null +++ b/addons/mail/static/src/xml/client_action.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <!-- + ChatMailThread + Layout of the client action + param: widget: the widget (ChatMailThread) + --> + <t t-name="mail.client_action"> + <div class="o_mail_chat"> + <div class="o_mail_chat_sidebar"/> + <div class="o_mail_chat_content"/> + </div> + </t> + + <div t-name="mail.chat.Sidebar" class="o_mail_chat_sidebar"> + <div class="o_mail_chat_channel_item" data-channel-id="channel_inbox"> + @ Inbox + <span t-attf-class="badge pull-right #{(!needaction_counter ? 'hide' : '')}"> + <t t-esc="needaction_counter"/> + </span> + </div> + <div class="o_mail_chat_channel_item" data-channel-id="channel_starred"> + <span class="fa fa-star-o"/> Starred + </div> + + <div class="o_mail_sidebar_title"> + <h4>Channels</h4> + <span class="fa fa-plus o_add" data-type="public"/> + </div> + <t t-foreach="channels" t-as="channel"> + <t t-if="channel.type === 'public'"> + <div class="o_mail_chat_channel_item" t-att-data-channel-id="channel.id"> + <t t-esc="channel.name"/> + </div> + </t> + </t> + <div class="o_mail_add_channel" data-type="public"> + <input type="text" placeholder="Add a channel"/> + </div> + + <div class="o_mail_sidebar_title"> + <h4>Direct Messages</h4> + <span class="fa fa-plus o_add" data-type="dm"/> + </div> + <t t-foreach="channels" t-as="channel"> + <t t-if="channel.type === 'dm'"> + <div class="o_mail_chat_channel_item" t-att-data-channel-id="channel.id"> + <i t-att-class="'fa ' + (channel.status == 'online' ? 'fa-circle' : 'fa-circle-o')"/> + <t t-esc="channel.name"/> + <span class="fa fa-times pull-right o_mail_partner_unpin" t-att-title="_t('Close')" t-att-data-channel-id="channel.id"/> + </div> + </t> + </t> + <div class="o_mail_add_channel" data-type="dm"> + <input type="text" placeholder="Open chat"/> + </div> + + <div class="o_mail_sidebar_title"> + <h4>Private Channels</h4> + <span class="fa fa-plus o_add" data-type="private"/> + </div> + <t t-foreach="channels" t-as="channel"> + <t t-if="channel.type === 'private'"> + <div class="o_mail_chat_channel_item" t-att-data-channel-id="channel.id"> + <t t-esc="channel.name"/> + </div> + </t> + </t> + <div class="o_mail_add_channel" data-type="private"> + <input type="text" placeholder="Add a channel"/> + </div> + + + <!-- <a href="#" class="o_mail_chat_add_more_text"><span class="fa fa-plus"/>Add</a> + <div class="o_mail_chat_add_more_search_bar" style="display: none;"> + <input type="text"/> + </div> --> + <!-- <t t-call="mail.chat.ChatMailThread.channels"> + <t t-set="channel_slot" t-value="'channel_private_group'"/> + </t> --> + </div> + + <!-- + Buttons of the Control Panel + --> + <t t-name="mail.chat.ControlButtons"> + <div> + <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_invite" t-att-title='_t("Invite people")'>Invite</button> + <button type="button" class="btn btn-default btn-sm o_mail_chat_button_detach" t-att-title='_t("Detach the conversation")'>Detach</button> + </div> + </t> + + <div t-name="mail.PartnerInviteDialog"> + <input type="text" class="o_mail_chat_partner_invite_input" id="mail_search_partners"/> + </div> +</templates> diff --git a/addons/mail/static/src/xml/systray.xml b/addons/mail/static/src/xml/systray.xml new file mode 100644 index 00000000000..5f355d88969 --- /dev/null +++ b/addons/mail/static/src/xml/systray.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + <t t-name="mail.ComposeMessageTopButton"> + <li title='Compose new Message' class="oe_topbar_item oe_topbar_compose_full_email"> + <a href="#"> + <i class="fa fa-envelope-o fa-fw"></i> + </a> + </li> + </t> + + <t t-name="mail.chat.NotificationTopButton"> + <li t-att-title='_t("Notifications")'> + <a href="#"> + <i class="fa fa-comment"/> <t t-esc="widget.get('counter') || ''"/> + </a> + </li> + </t> + +</templates> + |
