[MERGE] im_livechat, mail: new Discuss mobile UI

- Brand new UI for Discuss client action (with tabs for for
  Discuss client action (with tabs for Inbox, chats,
  channels...)
- In mobile, conversations always open in Chat Windows
- Chat window UI improved (both mobile and desktop)
- Channels preview (used in the systray menu, and now in the mobile
  UI of Discuss) improved
- Other minor less tweaks

Task #31446
PR #16995
This commit is contained in:
Aaron Bohy
2017-09-15 15:25:16 +02:00
23 changed files with 1673 additions and 690 deletions
@@ -7,9 +7,20 @@
<t t-set="disable_add_channel" t-value="true"/>
<t t-call="mail.chat.SidebarTitle">
<t t-set="channel_title">Livechat</t>
<t t-set="channel_icon">fa-comments</t>
</t>
<t t-call="mail.chat.SidebarItems"/>
</t>
</t>
<!-- Mobile templates -->
<t t-extend="mail.client_action_mobile">
<t t-jquery=".o_mail_mobile_tabs" t-operation="append">
<div class="o_mail_mobile_tab" data-type="livechat">
<span class="fa fa-comments"/>
<span class="o_tab_title">Livechat</span>
</div>
</t>
</t>
</template>
+8 -1
View File
@@ -354,6 +354,13 @@ class Channel(models.Model):
.channel_partner_ids
.filtered(lambda p: p.id != self.env.user.partner_id.id)
.read(['id', 'name', 'im_status']))
# add last message preview (only used in mobile)
if self._context.get('isMobile', False):
last_message = channel.channel_fetch_preview()
if last_message:
info['last_message'] = last_message[0].get('last_message')
# add user session state, if available and if user is logged
if partner_channels.ids:
partner_channel = partner_channels.filtered(lambda c: channel.id == c.channel_id.id)
@@ -572,9 +579,9 @@ class Channel(models.Model):
'email_send': False,
'channel_partner_ids': [(4, self.env.user.partner_id.id)]
})
channel_info = new_channel.channel_info('creation')[0]
notification = _('<div class="o_mail_notification">created <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (new_channel.id, new_channel.name,)
new_channel.message_post(body=notification, message_type="notification", subtype="mail.mt_comment")
channel_info = new_channel.channel_info('creation')[0]
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), channel_info)
return channel_info
+32 -8
View File
@@ -82,6 +82,11 @@ function add_message (data, options) {
_.each(msg.channel_ids, function (channel_id) {
var channel = chat_manager.get_channel(channel_id);
if (channel) {
// update the channel's last message (displayed in the channel
// preview, in mobile)
if (!channel.last_message || msg.id > channel.last_message.id) {
channel.last_message = msg;
}
add_to_cache(msg, []);
if (options.domain && options.domain !== []) {
add_to_cache(msg, options.domain);
@@ -95,7 +100,7 @@ function add_message (data, options) {
update_channel_unread_counter(channel, channel.unread_counter+1);
}
if (channel.is_chat && options.show_notification) {
if (!client_action_open && config.device.size_class !== config.device.SIZES.XS) {
if (!client_action_open && !config.isMobile) {
// automatically open chat window
chat_manager.bus.trigger('open_chat', channel, { passively: true });
}
@@ -258,6 +263,9 @@ function add_channel (data, options) {
} else {
channel = chat_manager.make_channel(data, options);
channels.push(channel);
if (data.last_message) {
channel.last_message = add_message(data.last_message);
}
// In case of a static channel (Inbox, Starred), the name is translated thanks to _lt
// (lazy translate). In this case, channel.name is an object, not a string.
channels = _.sortBy(channels, function (channel) { return _.isString(channel.name) ? channel.name.toLowerCase() : '' });
@@ -638,7 +646,8 @@ var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
start: function () {
this.is_ready = session.is_bound.then(function(){
return session.rpc('/mail/client_action');
var context = _.extend({isMobile: config.isMobile}, session.user_context);
return session.rpc('/mail/client_action', {context: context});
}).then(this._onMailClientAction.bind(this));
add_channel({
@@ -1023,11 +1032,12 @@ var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
create_channel: function (name, type) {
var method = type === "dm" ? "channel_get" : "channel_create";
var args = type === "dm" ? [[name]] : [name, type];
var context = _.extend({isMobile: config.isMobile}, session.user_context);
return this._rpc({
model: 'mail.channel',
method: method,
args: args,
kwargs: {context: context},
})
.then(add_channel);
},
@@ -1147,9 +1157,10 @@ var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
get_channels_preview: function (channels) {
var channels_preview = _.map(channels, function (channel) {
var info;
if (channel.channel_ids && _.contains(channel.channel_ids,"channel_inbox")) {
// map inbox(mail_message) data with existing channel/chat template
var info = _.pick(channel, 'id', 'body', 'avatar_src', 'res_id', 'model', 'module_icon', 'subject','date', 'record_name', 'status', 'displayed_author', 'email_from', 'unread_counter');
info = _.pick(channel, 'id', 'body', 'avatar_src', 'res_id', 'model', 'module_icon', 'subject','date', 'record_name', 'status', 'displayed_author', 'email_from', 'unread_counter');
info.last_message = {
body: info.body,
date: info.date,
@@ -1161,8 +1172,8 @@ var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
info.id = 'channel_inbox';
return info;
}
var info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
info.last_message = _.last(channel.cache['[]'].messages);
info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
info.last_message = channel.last_message || _.last(channel.cache['[]'].messages);
if (!info.is_chat) {
info.image_src = '/web/image/mail.channel/'+channel.id+'/image_small';
} else if (channel.direct_partner_id) {
@@ -1194,9 +1205,22 @@ var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
channel_preview.last_message = add_message(channel.last_message);
}
});
return _.filter(channels_preview, function (channel) {
return channel.last_message; // remove empty channels
// sort channels: 1. unread, 2. chat, 3. date of last msg
channels_preview.sort(function (c1, c2) {
return Math.min(1, c2.unread_counter) - Math.min(1, c1.unread_counter) ||
c2.is_chat - c1.is_chat ||
!!c2.last_message - !!c1.last_message ||
(c2.last_message && c2.last_message.date.diff(c1.last_message.date));
});
// generate last message preview (inline message body and compute date to display)
_.each(channels_preview, function (channel) {
if (channel.last_message) {
channel.last_message_preview = chat_manager.get_message_body_preview(channel.last_message.body);
channel.last_message_date = channel.last_message.date.fromNow();
}
});
return channels_preview;
});
},
get_message_body_preview: function (message_body) {
+4 -2
View File
@@ -11,7 +11,7 @@ var QWeb = core.qweb;
var _t = core._t;
var HEIGHT_OPEN = '400px';
var HEIGHT_FOLDED = '28px';
var HEIGHT_FOLDED = '34px';
return Widget.extend({
template: "mail.ChatWindow",
@@ -43,6 +43,7 @@ return Widget.extend({
this.status = this.options.status;
this.unread_msgs = unread_msgs || 0;
this.is_hidden = false;
this.isMobile = config.isMobile;
},
start: function () {
this.$input = this.$('.o_composer_text_field');
@@ -85,12 +86,13 @@ return Widget.extend({
status: this.status,
title: this.title,
unread_counter: this.unread_msgs,
widget: this,
}));
},
fold: function () {
this.$el.animate({
height: this.folded ? HEIGHT_FOLDED : HEIGHT_OPEN
});
}, 200);
},
toggle_fold: function (fold) {
this.folded = _.isBoolean(fold) ? fold : !this.folded;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,283 @@
odoo.define('mail.chat_client_action_mobile', function (require) {
"use strict";
var ChatAction = require('mail.chat_client_action');
var chat_manager = require('mail.chat_manager');
var config = require('web.config');
var core = require('web.core');
var session = require('web.session');
var QWeb = core.qweb;
if (!config.isMobile) {
return;
}
ChatAction.include({
template: 'mail.client_action_mobile',
need_control_panel: false, // in mobile, we use a custom control panel
events: _.extend(ChatAction.prototype.events, {
'click .o_mail_mobile_tab': '_onMobileTabClicked',
'click .o_channel_inbox_item': '_onMobileInboxButtonClicked',
'click .o_mail_channel_preview': '_onMobileChannelClicked',
}),
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.currentState = this.defaultChannelID;
},
/**
* @override
*/
start: function () {
this.$mainContent = this.$('.o_mail_chat_content');
return this._super.apply(this, arguments)
.then(this._updateControlPanel.bind(this));
},
/**
* @override
*/
on_attach_callback: function () {
if (this.channel && this._isInInboxTab()) {
this.thread.scroll_to({offset: this.channels_scrolltop[this.channel.id]});
}
},
/**
* @override
*/
on_detach_callback: function () {
if (this._isInInboxTab()) {
this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop();
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
* @returns {Boolean} true iff we currently are in the Inbox tab
*/
_isInInboxTab: function () {
return _.contains(['channel_inbox', 'channel_starred'], this.currentState);
},
/**
* @override
* @private
*/
_renderButtons: function () {
var self = this;
this._super.apply(this, arguments);
_.each(['dm', 'public', 'private'], function (type) {
var selector = '.o_mail_chat_button_' + type;
self.$buttons.on('click', selector, self._onAddChannel.bind(self));
});
},
/**
* Overrides to only store the channel state if we are in the Inbox tab, as
* this is the only tab in which we actually have a displayed channel
*
* @override
* @private
*/
_restoreChannelState: function () {
if (this._isInInboxTab()) {
this._super.apply(this, arguments);
}
},
/**
* Overrides to toggle the visibility of the tabs when a message is selected
*
* @override
* @private
*/
_selectMessage: function () {
this._super.apply(this, arguments);
this.$('.o_mail_mobile_tabs').addClass('o_hidden');
},
/**
* @override
* @private
*/
_setChannel: function (channel) {
if (channel.type !== 'static') {
chat_manager.detach_channel(channel);
} else {
this._super.apply(this, arguments);
}
},
/**
* Overrides to only store the channel state if we are in the Inbox tab, as
* this is the only tab in which we actually have a displayed channel
*
* @override
* @private
*/
_storeChannelState: function () {
if (this.channel && this._isInInboxTab()) {
this._super.apply(this, arguments);
}
},
/**
* @private
*/
_toggleSearchView: function () {
this.searchviewDisplayed = !this.searchviewDisplayed;
this.searchview.$el.toggleClass('o_hidden', !this.searchviewDisplayed);
this.$buttons.toggleClass('o_hidden', this.searchviewDisplayed);
},
/**
* Overrides to toggle the visibility of the tabs when a message is unselected
*
* @override
* @private
*/
_unselectMessage: function () {
this._super.apply(this, arguments);
this.$('.o_mail_mobile_tabs').removeClass('o_hidden');
},
/**
* @override
* @private
*/
_updateChannels: function () {
return this._updateContent(this.currentState);
},
/**
* Redraws the content of the client action according to its current state.
*
* @private
* @param {string} type the channel's type to display (e.g. 'channel_inbox',
* 'channel_starred', 'dm'...).
*/
_updateContent: function (type) {
var self = this;
var inInbox = type === 'channel_inbox' || type === 'channel_starred';
if (!inInbox && this._isInInboxTab()) {
// we're leaving the inbox, so store the thread scrolltop
this._storeChannelState();
}
var previouslyInInbox = this._isInInboxTab();
this.currentState = type;
// fetch content to display
var def;
if (inInbox) {
def = this._fetchAndRenderThread();
} else {
var channels = _.where(chat_manager.get_channels(), {type: type});
def = chat_manager.get_channels_preview(channels);
}
return $.when(def).then(function (channelsPreview) {
// update content
if (inInbox) {
if (!previouslyInInbox) {
self.$('.o_mail_chat_tab_pane').remove();
self.$mainContent.append(self.thread.$el);
self.$mainContent.append(self.extended_composer.$el);
}
self._restoreChannelState();
} else {
self.thread.$el.detach();
self.extended_composer.$el.detach();
var $content = $(QWeb.render("mail.chat.MobileTabPane", {
channels: channelsPreview,
get_message_body_preview: chat_manager.get_message_body_preview,
moment: moment,
partner_id: session.partner_id,
type: type,
widget: self,
}));
self._prepareAddChannelInput($content.find('.o_mail_add_channel input'), type);
self.$mainContent.html($content);
}
// update control panel
self.$buttons.find('button').addClass('o_hidden');
self.$buttons.find('.o_mail_chat_button_' + type).removeClass('o_hidden');
self.$buttons.find('.o_mail_chat_button_mark_read').toggleClass('o_hidden', type !== 'channel_inbox');
self.$buttons.find('.o_mail_chat_button_unstar_all').toggleClass('o_hidden', type !== 'channel_starred');
self.$('.o_enable_searchview').toggleClass('o_hidden', !inInbox);
if (!inInbox && self.searchviewDisplayed) {
self._toggleSearchView(); // close the searchview when leaving Inbox
}
// update Inbox page buttons
if (inInbox) {
self.$('.o_mail_chat_mobile_inbox_buttons').removeClass('o_hidden');
self.$('.o_channel_inbox_item').removeClass('btn-primary').addClass('btn-default');
self.$('.o_channel_inbox_item[data-type=' + type + ']').removeClass('btn-default').addClass('btn-primary');
} else {
self.$('.o_mail_chat_mobile_inbox_buttons').addClass('o_hidden');
}
// update bottom buttons
self.$('.o_mail_mobile_tab').removeClass('active');
// channel_inbox and channel_starred share the same tab
type = type === 'channel_starred' ? 'channel_inbox' : type;
self.$('.o_mail_mobile_tab[data-type=' + type + ']').addClass('active');
});
},
/**
* @override
*/
_updateControlPanel: function () {
this.$buttons.appendTo(this.$('.o_mail_chat_mobile_control_panel'));
this.searchview.$el.appendTo(this.$('.o_mail_chat_mobile_control_panel'));
var $enable_searchview = $('<button/>', {type: 'button'})
.addClass('o_enable_searchview btn fa fa-search')
.on('click', this._toggleSearchView.bind(this));
$enable_searchview.insertAfter(this.searchview.$el);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onAddChannel: function () {
this.$('.o_mail_add_channel').show().find('input').focus();
},
/**
* Switches to the clicked channel in the Inbox page (Inbox or Starred).
*
* @private
* @param {MouseEvent}
*/
_onMobileInboxButtonClicked: function (event) {
this._setChannel(chat_manager.get_channel($(event.currentTarget).data('type')));
this._updateContent(this.channel.id);
},
/**
* Switches to another tab.
*
* @private
* @param {MouseEvent}
*/
_onMobileTabClicked: function (event) {
var type = $(event.currentTarget).data('type');
if (type === 'channel_inbox') {
this._setChannel(chat_manager.get_channel('channel_inbox'));
}
this._updateContent(type);
},
/**
* Opens a channel in a chat windown (full screen in mobile).
*
* @private
* @param {MouseEvent}
*/
_onMobileChannelClicked: function (event) {
var channelId = $(event.currentTarget).data("channel_id");
chat_manager.detach_channel(chat_manager.get_channel(channelId));
},
});
});
+2
View File
@@ -5,6 +5,7 @@ var chat_mixin = require('mail.chat_mixin');
var DocumentViewer = require('mail.DocumentViewer');
var utils = require('mail.utils');
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
var dom = require('web.dom');
@@ -374,6 +375,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
send_text: _t('Send'),
default_body: '',
default_mention_selections: {},
isMobile: config.isMobile
});
this.context = this.options.context;
+15 -24
View File
@@ -1,6 +1,7 @@
odoo.define('mail.systray', function (require) {
"use strict";
var config = require('web.config');
var core = require('web.core');
var framework = require('web.framework');
var session = require('web.session');
@@ -27,6 +28,10 @@ var MessagingMenu = Widget.extend({
"click .o_new_message": "on_click_new_message",
"click .o_mail_channel_preview": "_onClickChannel",
},
init: function () {
this._super.apply(this, arguments);
this.isMobile = config.isMobile; // used by the template
},
start: function () {
this.$filter_buttons = this.$('.o_filter_button');
this.$channels_preview = this.$('.o_mail_navbar_dropdown_channels');
@@ -83,23 +88,6 @@ var MessagingMenu = Widget.extend({
});
},
_render_channels_preview: function (channels_preview) {
// Sort channels: 1. channels with unread messages, 2. chat, 3. by date of last msg
channels_preview.sort(function (c1, c2) {
return Math.min(1, c2.unread_counter) - Math.min(1, c1.unread_counter) ||
c2.is_chat - c1.is_chat ||
c2.last_message.date.diff(c1.last_message.date);
});
// Generate last message preview (inline message body and compute date to display)
_.each(channels_preview, function (channel) {
channel.last_message_preview = chat_manager.get_message_body_preview(channel.last_message.body);
if (channel.last_message.date.isSame(new Date(), 'd')) { // today
channel.last_message_date = channel.last_message.date.format('LT');
} else {
channel.last_message_date = channel.last_message.date.format('lll');
}
});
this.$channels_preview.html(QWeb.render('mail.chat.ChannelsPreview', {
channels: channels_preview,
}));
@@ -111,9 +99,9 @@ var MessagingMenu = Widget.extend({
},
on_click_filter_button: function (event) {
event.stopPropagation();
this.$filter_buttons.removeClass('o_selected');
this.$filter_buttons.removeClass('active');
var $target = $(event.currentTarget);
$target.addClass('o_selected');
$target.addClass('active');
this.filter = $target.data('filter');
this.update_channels_preview();
},
@@ -125,14 +113,14 @@ var MessagingMenu = Widget.extend({
/**
* When a channel is clicked on, we want to open chat/channel window
* If channel is inbox then redirect to that record view
* If record not linked redirect to Inbox
*
* @private
* @param {MouseEvent} event
*/
_onClickChannel: function (event) {
var self = this;
var channelID = $(event.currentTarget).data('channel_id');
if (channelID == 'channel_inbox') {
if (channelID === 'channel_inbox') {
var resID = $(event.currentTarget).data('res_id');
var resModel = $(event.currentTarget).data('res_model');
if (resModel && resID) {
@@ -143,8 +131,11 @@ var MessagingMenu = Widget.extend({
res_id: resID
});
} else {
// if no model linked redirect to inbox
framework.redirect('mail/view?message_id=channel_inbox');
this.do_action('mail.mail_channel_action_client_chat', {clear_breadcrumbs: true})
.then(function () {
self.trigger_up('hide_app_switcher');
core.bus.trigger('change_menu_section', chat_manager.get_discuss_menu_id());
});
}
} else {
var channel = chat_manager.get_channel(channelID);
@@ -272,6 +272,7 @@ function render_hidden_sessions_dropdown () {
sessions: display_state.hidden_sessions,
open: display_state.windows_dropdown_is_open,
unread_counter: display_state.hidden_unread_counter,
widget: {isMobile: config.isMobile},
}));
return $dropdown;
}
+23 -13
View File
@@ -1,7 +1,8 @@
@o-chat-window-bg: white;
@o-chat-window-bg: #FAFAFA;
@o-chat-window-width: 325px;
@o-mail-chatter-gap: 10px; // has to be defined here as this file is both in
// backend and other assets (livechat)
@o-chat-header-height: 46px;
.o_chat_window {
.o-flex-display();
@@ -13,13 +14,14 @@
max-height: 100%;
font-size: 12px;
background-color: @o-chat-window-bg;
border-radius: 3px 3px 0 0;
border-radius: 6px 6px 0 0;
z-index: 3;
box-shadow: -5px -5px 20px fade(black, 28%);
box-shadow: -5px -5px 10px fade(black, 18%);
@media (max-width: @screen-xs-max) {
width: 100%;
height: 100%!important;
box-shadow: none;
&.o_folded {
display: none;
}
@@ -37,11 +39,20 @@
border-radius: 3px 3px 0 0;
border-bottom: 1px solid @odoo-color-silver-dark; // cannot use gray-lighter-dark as this file is also frontend
background-color: @odoo-brand-primary;
padding: 8px;
@media (max-width: @screen-xs-max) {
height: @navbar-height;
.o_chat_title, .o_chat_window_close {
font-size: 22px;
height: @o-chat-header-height;
padding-top: 12px;
border-radius: 0px;
.o_chat_title {
font-size: 16px;
margin-left: 10px;
}
.o_chat_window_close {
font-size: 17px;
color: white;
}
}
@@ -66,24 +77,23 @@
.o_mail_thread {
.o-flex(1, 1, auto);
overflow: auto;
border: 1px solid @odoo-color-silver-dark; // cannot use gray-lighter-dark as this file is also frontend
border-width: 0 1px;
.o_thread_date_separator {
margin-top: 0px;
margin-bottom: 15px;
border-color: lightgray;
margin: 0px 0px 15px 0px;
.o_thread_date {
background-color: @o-chat-window-bg;
}
}
.o_thread_message {
padding: 4px 5px;
.o_thread_message_sidebar {
margin-right: 5px;
}
padding: 4px 5px;
}
}
.o_chat_composer input {
width: 100%;
}
}
.o_chat_window_dropdown {
+214 -16
View File
@@ -6,10 +6,7 @@
.o_mail_chat {
.o-position-absolute(0, 0, 0, 0);
height: 100%;
@media (max-width: @screen-xs-max) {
height: auto;
}
overflow: hidden;
.o_mail_annoying_notification_bar {
height: 40px;
@@ -47,12 +44,6 @@
overflow: auto;
padding: @odoo-horizontal-padding 0;
@media (max-width: @screen-xs-max) {
width: 100%;
float: none;
display: none;
}
@media (min-width: @screen-lg-min) {
width: @mail-chat-sidebar-width + 50px;
}
@@ -130,10 +121,6 @@
overflow: auto;
}
@media (max-width: @screen-xs-max) {
margin-left: 0;
}
@media (min-width: @screen-lg-min) {
margin-left: @mail-chat-sidebar-width + 50px;
}
@@ -267,11 +254,222 @@
.o_mail_user_status {
font-size: 1em;
position: relative;
&.o_user_online {
color: @brand-success;
color: @odoo-brand-optional;
}
&.o_user_idle {
color: @brand-warning;
}
}
// ------------------------------------------------------------------
// Mobile
// ------------------------------------------------------------------
@media (max-width: @screen-xs-max) {
.o_mail_chat {
.o-flex-display();
.o-flex-flow(column, nowrap);
background: white;
.o_mail_chat_mobile_control_panel {
.o-flex(0, 0, auto);
padding: 5px;
min-height: 44px;
border-bottom: 1px solid #ebebeb;
box-shadow: 0px 0px 10px #afafaf;
z-index: 1; // so that box shaddow is above thread's images
.o_searchview {
border: none;
padding: 5px 5px 0px 5px;
.o_searchview_input {
font-size: 15px;
}
.o_searchview_more {
display: none;
}
}
.o_enable_searchview, .o_enable_searchview:focus,
.o_enable_searchview:hover, .o_enable_searchview:active {
.o-position-absolute(@right: 5px, @top: 5px);
color: gray;
background: none;
font-size: 15px;
&:active {
box-shadow: none;
}
}
}
.o_mail_chat_mobile_inbox_buttons {
.o-flex(0, 0, auto);
padding: 15px 15px 10px 15px;
.o_channel_inbox_item {
width: 50%;
border: 1px solid @odoo-brand-optional;
&:first-child {
border-radius: 4px 0px 0px 4px;
}
&:last-child {
border-radius: 0px 4px 4px 0px;
}
}
}
.o_mail_chat_content {
.o-flex(1, 0, 0);
margin-left: 0;
background-color: transparent;
.o_mail_no_content {
.o-position-absolute(@top: 30%);
text-align: center;
}
}
.o_mail_chat_tab_pane {
overflow-x: scroll;
.o_mail_add_channel {
display: none;
position: relative;
padding: 5px 0px;
margin: 10px;
border: 1px solid #afafaf;
border-radius: 4px;
> span {
.o-position-absolute(@top: 10px, @left: 10px);
}
> input {
border: none;
padding: 5px 8px;
padding-left: 20px;
}
}
}
.o_mail_mobile_tabs {
.o-flex(0, 0, auto);
}
}
}
// ------------------------------------------------------------------
// Channel preview: shared between client action (mobile) and systray
// ------------------------------------------------------------------
.o_mail_channel_preview {
.o-flex-display();
border-bottom: 1px solid #ebebeb;
overflow: hidden;
position: relative;
.o_mail_channel_image {
.o-flex(0, 0, auto);
position: relative;
text-align: center;
> img {
border-radius: 50%;
}
.o_mail_user_status {
.o-position-absolute(@bottom: 0px, @right: 0px);
}
}
.o_channel_info {
.o-flex(1, 1, 100%);
overflow: hidden;
.o_channel_title {
.o-flex-display();
.o_channel_name {
.o-flex(0, 1, auto);
.o-text-overflow();
}
.o_channel_counter {
.o-flex(1, 1, auto);
}
.o_channel_name, .o_channel_counter {
color: @odoo-main-text-color;
}
.o_last_message_date {
.o-flex(0, 0, auto);
color: @odoo-main-color-muted;
}
}
.o_last_message_preview {
width: 100%;
max-height: 20px;
color: @odoo-main-color-muted;
.o-text-overflow();
}
}
&.o_channel_unread {
background-color: #f5f5f5;
.o_channel_info {
.o_channel_title {
.o_channel_name, .o_channel_counter {
font-weight: 700;
}
.o_last_message_date {
color: @odoo-brand-optional;
}
}
}
}
}
@media (max-width: @screen-xs-max) {
.o_mail_channel_preview {
height: 85px;
padding: 15px;
.o_channel_info {
margin-left: 15px;
padding-top: 5px;
.o_channel_title {
.o_channel_name, .o_channel_counter {
font-size: 15px;
font-weight: 500;
}
.o_last_message_date {
padding-top: 2px;
margin-left: 10px;
}
}
}
&.o_channel_unread {
.o_channel_info .o_last_message_preview {
font-weight: 500;
}
}
}
.o_mail_channel_image {
width: 55px;
> img {
max-height: 55px;
}
}
.o_mail_mobile_tabs {
.o-flex-display();
.o-flex-flow(row, nowrap);
border-top: 1px solid #ebebeb;
box-shadow: 0px 0px 10px #afafaf;
z-index: 1; // so that box shaddow is above thread's images
.o_mail_mobile_tab {
.o-flex(1, 1, 0);
padding: 8px 4px;
text-align: center;
&:not(:last-child) {
border-right: 1px solid #d7d7d7;
}
> span {
display: block;
&.fa {
font-size: 16px;
}
&.o_tab_title{
font-size: 8px;
padding-top: 8px;
}
}
&.active > span {
color: @odoo-brand-optional;
}
}
}
}
+13 -6
View File
@@ -54,6 +54,11 @@
.o_composer_button_full_composer {
.o-position-absolute(0, 0);
}
@media (max-width: @screen-xs-max) {
.o_composer_button_send {
color: @odoo-brand-optional;
}
}
}
// Both inline and not-inline design. Mini-composer disabled.
@@ -171,19 +176,21 @@
.o_composer_input {
.o-flex-display();
.o-flex-flow(row, nowrap);
padding: @o-mail-chatter-gap;
textarea.o_composer_text_field {
.o-flex(1, 1, auto);
padding-top: @o-mail-chatter-gap*0.5;
}
.o_chatter_composer_tools {
.o-flex(0, 0, auto);
}
@media (max-width: @screen-xs-max) {
.o_composer_button_send {
display: inline-block;
}
}
}
.o_composer_button_send, .o_chatter_avatar, .o_composer_button_add_attachment, .o_composer_attachments_list {
.o_composer_button_send, .o_chatter_avatar {
display: none;
}
}
@@ -197,8 +204,8 @@
// Emoji
.o_mail_emoji {
display: inline-block;
width: 24px;
height: 24px;
height: 20px;
width: 20px;
> img {
width: 100%;
@@ -24,4 +24,37 @@
display: none;
}
}
.o_chat_composer {
.o_composer {
.o_composer_input {
width: 80%;
> textarea {
padding: 10px;
font-size: 13px;
}
@media (max-width: @screen-xs-max) {
width: 70%;
> textarea {
padding: 15px 10px;
}
}
}
.o_chatter_composer_tools {
.o-position-absolute(@right: 0px, @top: 0px);
button {
padding: 10px;
@media (max-width: @screen-xs-max) {
padding: 13px;
}
&.btn-icon {
font-size: 1.3em;
}
}
}
}
.o_composer_attachments_list {
margin: 0px;
}
}
}
+56 -54
View File
@@ -20,13 +20,11 @@
.o-flex-flow(column, nowrap);
}
.o_notification_counter {
.o-position-absolute(@top: 20%, @right: 1px);
background: @odoo-brand-optional;
color: white;
padding: 0em 0.3em;
font-size: 0.7em;
top: 19%;
position: absolute;
margin: 0px 0px 0px 6px;
}
.o_mail_navbar_dropdown {
width: 350px;
@@ -45,17 +43,16 @@
.o-flex(0, 0, auto);
justify-content: space-between;
border-bottom: 1px solid lightgray;
.o_filter_button, .o_new_message {
.btn-link;
padding: 5px;
}
.o_filter_button {
color: @odoo-main-color-muted;
&:hover, &.o_selected {
&:hover, &.active {
color: @odoo-brand-optional;
}
&.o_selected {
&.active {
cursor: default;
font-weight: bold;
}
@@ -68,56 +65,28 @@
min-height: 50px;
overflow-y: auto;
@media (max-width: @screen-xs-max) {
max-height: none;
}
.o_mail_channel_preview {
.o-flex-display();
height: 50px;
align-items: center;
padding: 5px;
cursor: pointer;
font-size: 12px;
overflow: hidden;
&~.o_mail_channel_preview {
border-top: 1px solid lightgray;
}
&.o_channel_unread {
background-color: lighten(lightgray, 10%);
}
&:hover {
background-color: lighten(lightgray, 5%);
}
.o_channel_image {
.o-flex(0, 0, 36px);
max-height: 36px;
}
.o_channel_info {
.o-flex(1, 1, 100%);
margin-left: 5px;
overflow: hidden;
.o_channel_title {
.o-flex-display();
.o_channel_name {
.o-flex(1, 1, auto);
.o-text-overflow();
color: @odoo-main-text-color;
}
.o_last_message_date {
.o-flex(0, 0, auto);
padding-top: 3px;
font-size: xx-small;
color: @odoo-main-color-muted;
@media (min-width: @screen-sm-min) {
.o_mail_channel_preview {
height: 50px;
padding: 5px;
.o_mail_channel_image {
width: 40px;
> img {
max-height: 40px;
}
}
.o_last_message_preview {
width: 100%;
max-height: 20px;
color: @odoo-main-color-muted;
.o-text-overflow();
.o_channel_info {
margin-left: 10px;
.o_channel_title {
.o_channel_name, .o_channel_counter {
color: #666666;
}
.o_last_message_date {
padding-top: 2px;
font-size: x-small;
margin-left: 10px;
}
}
}
}
}
@@ -139,3 +108,36 @@
.o_no_chat_window .o_mail_navbar_dropdown .o_new_message {
display: none; // hide 'new message' button if chat windows are disabled
}
// Mobile rules
// Goal: mock the design of Discuss in mobile
@media (max-width: @screen-xs-max) {
.o_mail_navbar_item {
.o_notification_counter {
top: 10%;
}
.o_mail_navbar_dropdown {
position: relative;
.o_mail_navbar_dropdown_top {
padding: 5px;
}
.o_mail_navbar_mobile_header {
padding: 5px;
height: 44px;
border-bottom: 1px solid #ebebeb;
}
.o_mail_navbar_dropdown_channels {
max-height: none;
padding-bottom: 52px; // leave space for tabs
}
.o_mail_mobile_tabs {
position: fixed;
bottom: 0px;
left: 0px;
right: 0px;
background-color: white;
color: @odoo-main-text-color;
}
}
}
}
+12 -12
View File
@@ -11,6 +11,10 @@
.o_thread_date_separator {
margin-top: 15px;
margin-bottom: 30px;
@media (max-width: @screen-xs-max) {
margin-top: 0px;
margin-bottom: 15px;
}
border-bottom: 1px solid @odoo-color-silver-darker; // cannot use gray-lighter-darker as this file is also frontend
text-align: center;
@@ -69,14 +73,12 @@
opacity: 0;
}
}
.o_thread_icons {
.o_thread_icon {
cursor: pointer;
> .o_thread_icon {
opacity: 0;
&.fa-star {
opacity: @mail-thread-icon-opacity;
color: gold;
}
opacity: 0;
&.fa-star {
opacity: @mail-thread-icon-opacity;
color: gold;
}
}
@@ -84,7 +86,7 @@
.o_thread_message_side_date {
opacity: @mail-thread-side-date-opacity;
}
.o_thread_icons > * {
.o_thread_icon {
opacity: @mail-thread-icon-opacity;
&:hover {
opacity: 1;
@@ -207,9 +209,7 @@
}
.o_web_client.o_touch_device {
.o_thread_icons {
> .o_thread_icon {
opacity: @mail-thread-icon-opacity;
}
.o_mail_thread .o_thread_icon {
opacity: @mail-thread-icon-opacity;
}
}
+4 -1
View File
@@ -41,12 +41,15 @@
</t>
<t t-name="mail.ChatWindowHeaderContent">
<span t-if="widget.isMobile">
<a href="#" class="o_chat_window_close fa fa-1x fa-arrow-left mr4"/>
</span>
<span class="o_chat_title">
<t t-if="status" t-call="mail.chat.UserStatus"/>
<t t-esc="title"/>
<span t-if="unread_counter"> (<t t-esc="unread_counter"/>)</span>
</span>
<span class="o_chat_window_buttons">
<span t-if="!widget.isMobile" class="o_chat_window_buttons">
<a href="#" class="o_chat_window_close fa fa-close"/>
</span>
</t>
+90 -8
View File
@@ -17,13 +17,13 @@
</t>
<t t-name="mail.chat.Sidebar">
<div class="o_mail_chat_sidebar">
<div t-attf-class="o_mail_chat_title_main o_mail_chat_channel_item #{(active_channel_id == 'channel_inbox') ? 'o_active': ''}"
<div t-attf-class="o_mail_chat_title_main o_mail_chat_channel_item #{(active_channel_id === 'channel_inbox') ? 'o_active': ''}"
data-channel-id="channel_inbox">
<span class="o_channel_name"><i class="fa fa-inbox mr8"/>Inbox</span>
<t t-set="counter" t-value="needaction_counter"/>
<t t-call="mail.chat.SidebarNeedaction"/>
</div>
<div t-attf-class="o_mail_chat_title_main o_mail_chat_title_starred o_mail_chat_channel_item #{(active_channel_id == 'channel_starred') ? 'o_active': ''}"
<div t-attf-class="o_mail_chat_title_main o_mail_chat_title_starred o_mail_chat_channel_item #{(active_channel_id === 'channel_starred') ? 'o_active': ''}"
data-channel-id="channel_starred">
<span class="o_channel_name"><i class="fa fa-star-o mr8"/>Starred</span>
<t t-set="counter" t-value="starred_counter"/>
@@ -130,12 +130,13 @@
<!-- Buttons of the Control Panel -->
<t t-name="mail.chat.ControlButtons">
<div>
<button type="button" class="btn btn-primary btn-sm o_mail_toggle_channels visible-xs-inline" title="Toggle Channels">Channels</button>
<button type="button" class="btn btn-primary btn-sm o_mail_chat_button_invite" title="Invite people">Invite</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_mark_read hidden-xs" title="Mark all as read">Mark all read</button>
<button type="button" class="btn btn-primary btn-sm o_mail_chat_button_unstar_all" title="Unstar all messages">Unstar all</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_unsubscribe" title="Unsubscribe from channel">Unsubscribe</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_settings" title="Open channel settings">Settings</button>
<button type="button" class="btn btn-primary btn-sm o_mail_chat_button_invite hidden-xs" title="Invite people">Invite</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_mark_read" title="Mark all as read">Mark all read</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_unstar_all" title="Unstar all messages">Unstar all</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_unsubscribe hidden-xs" title="Unsubscribe from channel">Unsubscribe</button>
<button type="button" class="btn btn-default btn-sm o_mail_chat_button_dm visible-xs" title="New Message">New Message</button>
<button t-if="!disable_add_channel" type="button" class="btn btn-default btn-sm o_mail_chat_button_public o_mail_chat_button_private visible-xs" title="New Channel">New Channel</button>
<button t-if="debug" type="button" class="btn btn-default btn-sm o_mail_chat_button_settings" title="Open channel settings">Settings</button>
</div>
</t>
@@ -143,4 +144,85 @@
<div t-name="mail.PartnerInviteDialog">
<input type="text" class="o_input o_mail_chat_partner_invite_input" id="mail_search_partners"/>
</div>
<!-- Mobile templates -->
<t t-name="mail.client_action_mobile">
<div class="o_mail_chat">
<div class="o_mail_chat_mobile_control_panel"/>
<div class="o_mail_chat_mobile_inbox_buttons">
<button type="button" class="btn btn-primary btn-sm visible-xs-inline o_channel_inbox_item" title="Inbox" data-type="channel_inbox">
Inbox
</button><button type="button" class="btn btn-default btn-sm visible-xs-inline o_channel_inbox_item" title="Starred" data-type="channel_starred">
Starred
</button>
</div>
<div class="o_mail_chat_content"/>
<div class="o_mail_mobile_tabs">
<div class="o_mail_mobile_tab" data-type="channel_inbox">
<span class="fa fa-inbox"/>
<span class="o_tab_title">Inbox</span>
</div>
<div class="o_mail_mobile_tab" data-type="dm">
<span class="fa fa-user"/>
<span class="o_tab_title">Chat</span>
</div>
<div class="o_mail_mobile_tab" data-type="public">
<span class="fa fa-users"/>
<span class="o_tab_title">Channels</span>
</div>
<div class="o_mail_mobile_tab" data-type="private">
<span class="fa fa-eye-slash"/>
<span class="o_tab_title">Private Channels</span>
</div>
</div>
</div>
</t>
<t t-name="mail.chat.MobileTabPane">
<div class="o_mail_chat_tab_pane" t-att-data-type="type">
<div t-if="!disable_add_channel" class="o_mail_add_channel" t-att-data-type="type">
<span t-if="type == 'private' || type == 'public'">#</span>
<t t-set="input_placeholder" t-if="type == 'private' || type == 'public'">Add a channel</t>
<t t-set="input_placeholder" t-if="type == 'dm'">Open chat</t>
<input type="text" t-attf-placeholder="#{input_placeholder}"/>
</div>
<t t-foreach="channels" t-as="channel">
<t t-call="mail.chat.ChannelPreview"/>
</t>
</div>
</t>
<!-- The ChannelPreview template is used by the client action in mobile, and by the systray menu -->
<t t-name="mail.chat.ChannelPreview">
<div t-attf-class="o_mail_channel_preview #{channel.unread_counter ? 'o_channel_unread' : ''}"
t-att-data-channel_id="channel.id" t-att-data-res_id="channel.res_id" t-att-data-res_model="channel.model">
<div class="o_mail_channel_image">
<img class="o_mail_channel_image" t-att-src="channel.image_src"/>
<i t-if="channel.status === 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online"/>
<i t-if="channel.status === 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle"/>
</div>
<div class="o_channel_info">
<div class="o_channel_title">
<span class="o_channel_name">
<t t-esc="channel.name"/>
</span>
<span class="o_channel_counter">
<t t-if="channel.unread_counter">&amp;nbsp;(<t t-esc="channel.unread_counter"/>)</t>
</span>
<span class="o_last_message_date"> <t t-esc="channel.last_message_date"/> </span>
</div>
<div t-if="channel.last_message" class="o_last_message_preview">
<t t-if="channel.last_message.is_author">
<span class="fa fa-mail-reply"/> You:
</t>
<t t-else="">
<t t-esc="channel.last_message.displayed_author"/>:
</t>
<t t-raw="channel.last_message_preview"/>
</div>
</div>
</div>
</t>
</templates>
+2 -1
View File
@@ -13,13 +13,14 @@
<div class="o_chatter_composer_tools">
<button tabindex="4" class="btn btn-sm btn-icon fa fa-smile-o o_composer_button_emoji" type="button" data-toggle="popover"/>
<button tabindex="5" class="btn btn-sm btn-icon fa fa-paperclip o_composer_button_add_attachment" type="button"/>
<button t-if="widget.options.isMobile" tabindex="3" class="btn btn-sm btn-icon fa fa-paper-plane-o o_composer_button_send" type="button"/>
</div>
</div>
</div>
<div class="o_composer_attachments_list"/>
</div>
<div class="o_composer_send">
<button tabindex="3" class="btn btn-sm btn-primary o_composer_button_send" type="button"><t t-esc="widget.options.send_text"/></button>
<button tabindex="3" class="btn btn-sm btn-primary o_composer_button_send hidden-xs" type="button"><t t-esc="widget.options.send_text"/></button>
</div>
<span class="hide">
<t t-call="HiddenInputFile">
@@ -11,9 +11,8 @@
</t>
<t t-extend="mail.ChatWindowHeaderContent">
<t t-jquery=".o_chat_window_close" t-operation="before">
<a href="#" class="o_chat_window_expand fa fa-expand" title="
Open in Discuss"/>
<t t-jquery=".o_chat_window_buttons" t-operation="prepend">
<a href="#" class="o_chat_window_expand fa fa-expand" title="Open in Discuss"/>
</t>
</t>
+29 -25
View File
@@ -7,15 +7,36 @@
<i class="fa fa-comments"/> <span class="o_notification_counter badge"/>
</a>
<ul class="o_mail_navbar_dropdown dropdown-menu" role="menu">
<li class="o_mail_navbar_dropdown_top">
<li t-if="widget.isMobile">
<div class="o_mail_navbar_mobile_header">
<button type="button" class="btn btn-default btn-sm o_new_message"> New message </button>
</div>
</li>
<li t-if="!widget.isMobile" class="o_mail_navbar_dropdown_top">
<div>
<button type="button" class="btn btn-sm o_filter_button o_selected"> All </button>
<button type="button" class="btn btn-sm o_filter_button active"> All </button>
<button type="button" class="btn btn-sm o_filter_button" data-filter='chat'> Chat </button>
<button type="button" class="btn btn-sm o_filter_button" data-filter='channels'> Channels </button>
</div>
<button type="button" class="btn btn-sm o_new_message"> New message </button>
</li>
<li class="o_mail_navbar_dropdown_channels"/>
<li t-if="widget.isMobile">
<div class="o_mail_mobile_tabs">
<div class="o_mail_mobile_tab o_filter_button active">
<span class="fa fa-envelope"/>
<span class="o_tab_title">All</span>
</div>
<div class="o_mail_mobile_tab o_filter_button" data-filter='chat'>
<span class="fa fa-user"/>
<span class="o_tab_title">Chat</span>
</div>
<div class="o_mail_mobile_tab o_filter_button" data-filter='channels'>
<span class="fa fa-users"/>
<span class="o_tab_title">Channels</span>
</div>
</div>
</li>
</ul>
</li>
</t>
@@ -27,26 +48,7 @@
</li>
</t>
<t t-foreach="channels" t-as="channel">
<div t-attf-class="o_mail_channel_preview #{channel.unread_counter ? 'o_channel_unread' : ''}"
t-att-data-channel_id="channel.id" t-att-data-res_id="channel.res_id" t-att-data-res_model="channel.model">
<img class="o_channel_image" t-att-src="channel.image_src"/>
<div class="o_channel_info">
<div class="o_channel_title">
<span class="o_channel_name">
<t t-if="channel.status" t-call="mail.chat.UserStatus">
<t t-set="status" t-value="channel.status"/>
</t>
<t t-esc="channel.name"/>
<t t-if="channel.unread_counter">(<t t-esc="channel.unread_counter"/>)</t>
</span>
<span class="o_last_message_date"> <t t-esc="channel.last_message_date"/> </span>
</div>
<div class="o_last_message_preview">
<t t-esc="channel.last_message.displayed_author"/>:
<t t-raw="channel.last_message_preview"/>
</div>
</div>
</div>
<t t-call="mail.chat.ChannelPreview"/>
</t>
</t>
@@ -58,7 +60,9 @@
</t>
<t t-foreach="activities" t-as="activity">
<div class="o_mail_channel_preview" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='my'>
<img class="o_channel_image" t-att-src="activity.icon"/>
<div class="o_mail_channel_image">
<img t-att-src="activity.icon"/>
</div>
<div class="o_channel_info">
<div class="o_channel_title">
<span class="o_channel_name">
@@ -70,8 +74,8 @@
<span t-if="!activity.overdue_count" class="o_no_activity mr16">0 Late </span>
<button t-if="activity.today_count" type="button" class="btn btn-link o_activity_filter_button mr16" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='today'> <t t-esc="activity.today_count"/> Today </button>
<span t-if="!activity.today_count" class="o_no_activity mr16">0 Today </span>
<button t-if="activity.planned_count" type="button" class="btn btn-link o_activity_filter_button pull-right mr16" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='upcoming_all'> <t t-esc="activity.planned_count"/> Future </button>
<span t-if="!activity.planned_count" class="o_no_activity pull-right mr16">0 Future</span>
<button t-if="activity.planned_count" type="button" class="btn btn-link o_activity_filter_button pull-right" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='upcoming_all'> <t t-esc="activity.planned_count"/> Future </button>
<span t-if="!activity.planned_count" class="o_no_activity pull-right">0 Future</span>
</div>
</div>
</div>
+28 -27
View File
@@ -3,10 +3,10 @@
<t t-name="mail.ChatThread">
<t t-if="messages.length">
<t t-if="options.display_load_more &amp;&amp; options.display_order == ORDER.ASC"
<t t-if="options.display_load_more and options.display_order == ORDER.ASC"
t-call="mail.ChatThread.LoadMore"/>
<t t-call="mail.ChatThread.Content"/>
<t t-if="options.display_load_more &amp;&amp; options.display_order == ORDER.DESC"
<t t-if="options.display_load_more and options.display_order == ORDER.DESC"
t-call="mail.ChatThread.LoadMore"/>
</t>
<t t-if="options.display_empty_channel">
@@ -133,23 +133,23 @@
<t t-name="mail.ChatThread.Message">
<div t-att-class="'o_thread_message' + (message.expanded ? ' o_message_expanded ' : ' ') + (message.is_selected ? 'o_thread_selected_message' : '')" t-att-data-message-id="message.id">
<div t-if="options.display_avatar" class="o_thread_message_sidebar">
<t t-if="!message.mailto &amp;&amp; message.author_id[0]">
<img t-if="message.avatar_src &amp;&amp; message.display_author"
<t t-if="!message.mailto and message.author_id[0]">
<img t-if="message.avatar_src and message.display_author"
t-att-src="message.avatar_src"
data-oe-model="res.partner"
t-att-data-oe-id="message.author_redirect ? message.author_id[0] : ''"
t-attf-class="o_thread_message_avatar img-circle #{message.author_redirect ? 'o_mail_redirect' : ''}"/>
</t>
<t t-if="message.mailto || !message.author_id[0]">
<img t-if="message.avatar_src &amp;&amp; message.display_author"
<img t-if="message.avatar_src and message.display_author"
t-att-src="message.avatar_src"
class="o_thread_message_avatar img-circle"/>
</t>
<span t-if="!message.display_author" t-att-title="message.date" class="o_thread_message_side_date">
<t t-esc="message.date.format('hh:mm')"/>
</span>
<i t-if="!message.display_author &amp;&amp; options.display_stars &amp;&amp; message.message_type != 'notification'"
t-att-class="'fa o_thread_message_star ' + (message.is_starred ? 'fa-star' : 'fa-star-o')"
<i t-if="!message.display_author and options.display_stars and message.message_type != 'notification'"
t-att-class="'fa o_thread_message_star o_thread_icon ' + (message.is_starred ? 'fa-star' : 'fa-star-o')"
t-att-data-message-id="message.id" title="Mark as Todo"/>
</div>
<div t-att-class="'o_thread_message_core' + (message.is_note ? ' o_mail_note' : '')">
@@ -163,23 +163,23 @@
<t t-esc="message.mailto"/>
</a>
</strong>
<strong t-if="!message.mailto &amp;&amp; message.author_id[0]"
<strong t-if="!message.mailto and message.author_id[0]"
data-oe-model="res.partner" t-att-data-oe-id="message.author_redirect ? message.author_id[0] : ''"
t-attf-class="#{message.author_redirect ? 'o_mail_redirect' : ''}">
t-attf-class="o_thread_author #{message.author_redirect ? 'o_mail_redirect' : ''}">
<t t-esc="message.displayed_author"/>
</strong>
<strong t-if="!message.mailto &amp;&amp; !message.author_id[0]">
<strong t-if="!message.mailto and !message.author_id[0]" class="o_thread_author">
<t t-esc="message.displayed_author"/>
</strong>
- <small class="o_mail_timestamp" t-att-title="message.date"><t t-esc="message.hour"/></small>
<t t-if="message.model &amp;&amp; (message.model != 'mail.channel') &amp;&amp; options.display_document_link">
on <a t-att-href="message.url" t-att-data-oe-model="message.model" t-att-data-oe-id="message.res_id"><t t-esc="message.record_name"/></a>
<t t-if="message.model and (message.model != 'mail.channel') and options.display_document_link">
<a t-att-href="message.url" t-att-data-oe-model="message.model" t-att-data-oe-id="message.res_id" class="o_document_link">on <t t-esc="message.record_name"/></a>
</t>
<t t-if="message.origin_id &amp;&amp; (message.origin_id !== options.channel_id)">
<t t-if="message.origin_id and (message.origin_id !== options.channel_id)">
(from <a t-att-data-oe-id="message.origin_id" href="#">#<t t-esc="message.origin_name"/></a>)
</t>
<span t-if="options.display_email_icon &amp;&amp; message.customer_email_data &amp;&amp; message.customer_email_data.length" class="o_thread_tooltip_container">
<span t-if="options.display_email_icon and message.customer_email_data and message.customer_email_data.length" class="o_thread_tooltip_container">
<i t-att-class="'o_thread_tooltip o_thread_message_email o_thread_message_email_' + message.customer_email_status + ' fa fa-envelope-o'"/>
<span class="o_thread_tooltip_content">
<t t-foreach="message.customer_email_data" t-as="customer">
@@ -206,19 +206,21 @@
t-att-data-message-id="message.id" title="Mark as Read"/>
</span>
</p>
<t t-if="message.tracking_value_ids &amp;&amp; message.tracking_value_ids.length &gt; 0">
<t t-if="message.subtype_description">
<p t-esc="message.subtype_description"/>
<div class="o_thread_message_content">
<t t-if="message.tracking_value_ids and message.tracking_value_ids.length &gt; 0">
<t t-if="message.subtype_description">
<p><t t-esc="message.subtype_description"/></p>
</t>
<t t-call="mail.ChatThread.MessageTracking"/>
</t>
<t t-call="mail.ChatThread.MessageTracking"/>
</t>
<p t-if="options.display_subject &amp;&amp; message.display_subject" class="o_mail_subject">Subject: <t t-esc="message.subject"/></p>
<t t-if="!(message.tracking_value_ids &amp;&amp; message.tracking_value_ids.length &gt; 0)">
<t t-raw="message.body"/>
</t>
<t t-foreach="message.attachment_ids" t-as="attachment">
<t t-call="mail.Attachment"/>
</t>
<p t-if="options.display_subject and message.display_subject" class="o_mail_subject">Subject: <t t-esc="message.subject"/></p>
<t t-if="!(message.tracking_value_ids and message.tracking_value_ids.length &gt; 0)">
<t t-raw="message.body"/>
</t>
<t t-foreach="message.attachment_ids" t-as="attachment">
<t t-call="mail.Attachment"/>
</t>
</div>
</div>
</div>
<t t-if="options.messages_separator_position == message.id">
@@ -329,5 +331,4 @@
<button class="btn btn-sm btn-link">-------- Show older messages --------</button>
</div>
</t>
</templates>
@@ -0,0 +1,92 @@
odoo.define('mail.client_action_test', function (require) {
"use strict";
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
var ChatAction = require("mail.chat_client_action");
QUnit.module('mail', {}, function () {
QUnit.module('Discuss client action', {
beforeEach: function () {
this.data = {
'mail.message': {
fields: {},
},
};
this.createChatAction = function (params) {
var parent = new Widget();
testUtils.addMockEnvironment(parent, {
data: this.data,
archs: {
'mail.message,false,search': '<search/>',
},
config: {
isMobile: true
},
});
var chatAction = new ChatAction(parent, params);
chatAction.set_cp_bus(new Widget());
chatAction.appendTo($('#qunit-fixture'));
return chatAction;
};
},
});
QUnit.skip('mobile basic rendering', function (assert) {
// Unfortunately, this test is skipped for now because there is no way to
// execute the whole test suite in mobile (it only works test by test), so
// as the client action include for mobile is rejected when we are not in
// mobile, it isn't possible to test it
// Moreover, RPCs done by the chat_manager (e.g. message_fetch) should be
// properly mocked.
assert.expect(11);
var parent = new Widget();
testUtils.addMockEnvironment(parent, {
data: this.data,
archs: {
'mail.message,false,search': '<search/>',
},
config: {
isMobile: true,
},
});
var params = {
id: 1,
context: {},
params: {},
};
var chatAction = this.createChatAction(params);
// test basic rendering in mobile
assert.equal(chatAction.$(".o_mail_chat_mobile_control_panel").length, 1, "Mobile control panel created");
assert.equal(chatAction.$(".o_mail_mobile_tab").length, 4, "Four mobile tabs created");
assert.equal(chatAction.$('.o_mail_chat_content').length, 1, "One default chat content pane created");
assert.equal(chatAction.$(".o_mail_chat_tab_pane").length, 3, "Three mobile tab panes created");
// Inbox
assert.equal(chatAction.activeMobileTab, "channel_inbox", "'channel_inbox' is default active tab");
assert.ok(chatAction.$(".o_channel_inbox_item:nth(0)").hasClass("btn-primary"), "Showing 'Inbox'");
// Starred
chatAction.$(".o_channel_inbox_item[data-type='channel_starred']").click();
assert.ok(chatAction.$(".o_channel_inbox_item:nth(1)").hasClass("btn-primary"), "Clicked on 'Starred'");
assert.ok(chatAction.$(".o_mail_chat_content").is(":visible"), "Default main content pane visible");
chatAction.$(".o_mail_mobile_tab[data-type='dm']").click();
assert.equal(chatAction.activeMobileTab, "dm", "After click on 'Conversation', is now active tab");
assert.ok(!chatAction.$(".o_mail_chat_content").is(":visible"), "none", "'Main' content pane is invisible");
assert.ok(chatAction.$(".o_mail_chat_tab_pane:nth(0)").is(":visible"), "'Conversation' pane is visible");
chatAction.destroy();
});
});
});
+2
View File
@@ -6,6 +6,7 @@
<script type="text/javascript" src="/mail/static/src/js/many2many_tags_email.js"></script>
<script type="text/javascript" src="/mail/static/src/js/client_action.js"></script>
<script type="text/javascript" src="/mail/static/src/js/client_action_mobile.js"></script>
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
<script type="text/javascript" src="/mail/static/src/js/extended_chat_window.js"></script>
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
@@ -40,6 +41,7 @@
<template id="qunit_suite" name="mail_tests" inherit_id="web.qunit_suite">
<xpath expr="//t[@t-set='head']" position="inside">
<script type="text/javascript" src="/mail/static/tests/client_action_tests.js"></script>
<script type="text/javascript" src="/mail/static/tests/chatter_tests.js"></script>
<script type="text/javascript" src="/mail/static/tests/systray_tests.js"></script>
<script type="text/javascript" src="/mail/static/tests/chat_window_test.js"></script>