diff options
| author | Robot Odoo <xmo+roboto@odoo.com> | 2019-02-11 12:57:48 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-11 12:57:48 +0100 |
| commit | 47bb7ef7968cb3c5c11f767df06fcf09333a2b13 (patch) | |
| tree | 1e86b27e45fe2b5803294e886227495fdd68a96b /addons/mail/static/src | |
| parent | 768844040f887868759309ad101fe50191fc949a (diff) | |
| parent | 975164473ccd51c00eee5917dddbd8be71b5aefc (diff) | |
[MERGE] mail, hr_holidays, *: add out of office status
*: base, bus, mail_bot, test_mail
Before this task, it was not easy to tell when a user is not available (or is on holiday)
from conversations, such as from chat windows.
With this task, a user can now tell that he is out-of-office from the user menu preferences,
by defining an an out-of-office message. This information will be displayed to other users from
chat conversations and mention suggestions (i.e. with `@`).
Also, the computation of the user `im_status` has been changed when he has an active leave:
an out-of-office message can be set on the leave, and this will be displayed on conversations
instead of the user's out-of-office message from the user menu preferences.
Task: 1856205
closes odoo/odoo#29974
Diffstat (limited to 'addons/mail/static/src')
24 files changed, 479 insertions, 62 deletions
diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js index 8a045ac2d82..58e334808bf 100644 --- a/addons/mail/static/src/js/composers/basic_composer.js +++ b/addons/mail/static/src/js/composers/basic_composer.js @@ -371,7 +371,15 @@ var BasicComposer = Widget.extend({ { limit: limit, search: search } ); } - return suggestions; + return $.when(suggestions).then(function (suggestions) { + //add im_status on suggestions + _.each(suggestions, function (suggestionsSet) { + _.each(suggestionsSet, function (suggestion) { + suggestion.im_status = self.call('mail_service', 'getImStatus', { partnerID: suggestion.id }); + }); + }); + return suggestions; + }); }); }, /** diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 808cd8338ad..073befc5fcd 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -56,7 +56,10 @@ var PartnerInviteDialog = Dialog.extend({ allowClear: true, multiple: true, formatResult: function (item) { - var status = QWeb.render('mail.UserStatus', { status: item.im_status }); + var status = QWeb.render('mail.UserStatus', { + status: self.call('mail_service', 'getImStatus', { partnerID: item.id }), + partnerID: item.id, + }); return $('<span>').text(item.text).prepend(status); }, query: function (query) { @@ -992,10 +995,10 @@ var Discuss = AbstractAction.extend({ .on('new_channel', this, this._onNewChannel) .on('is_thread_bottom_visible', this, this._onIsThreadBottomVisible) .on('unsubscribe_from_channel', this, this._onChannelLeft) + .on('updated_im_status', this, this._onUpdatedImStatus) .on('update_needaction', this, this._onUpdateNeedaction) .on('update_starred', this, this._onUpdateStarred) .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter) - .on('update_dm_presence', this, this._onUpdateDmPresence) .on('activity_updated', this, this._onActivityUpdated) .on('update_moderation_counter', this, this._onUpdateModerationCounter) .on('update_typing_partners', this, this._onTypingPartnersUpdated) @@ -1572,7 +1575,7 @@ var Discuss = AbstractAction.extend({ /** * @private */ - _onUpdateDmPresence: function () { + _onUpdatedImStatus: function () { this._throttledUpdateThreads(); }, /** diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js index c1ae22b9708..bb2a19cc6ac 100644 --- a/addons/mail/static/src/js/models/messages/abstract_message.js +++ b/addons/mail/static/src/js/models/messages/abstract_message.js @@ -76,6 +76,14 @@ var AbstractMessage = Class.extend({ return this._serverAuthorID[0]; }, /** + * Threads do not have an im status by default + * + * @return {undefined} + */ + getAuthorImStatus: function () { + return undefined; + }, + /** * Get the relative url of the avatar to display next to the message * * @abstract diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js index dc98bb7195b..a414a11874b 100644 --- a/addons/mail/static/src/js/models/messages/message.js +++ b/addons/mail/static/src/js/models/messages/message.js @@ -66,6 +66,16 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin this._customerEmailData.push(data); }, /** + * @override + * @return {string|undefined} + */ + getAuthorImStatus: function () { + if (!this.hasAuthor()) { + return undefined; + } + return this.call('mail_service', 'getImStatus', { partnerID: this.getAuthorID() }); + }, + /** * Get the name of the author of this message * If there are no author, return "". * diff --git a/addons/mail/static/src/js/models/threads/abstract_thread.js b/addons/mail/static/src/js/models/threads/abstract_thread.js index d6a37967c68..a515c4d2f8a 100644 --- a/addons/mail/static/src/js/models/threads/abstract_thread.js +++ b/addons/mail/static/src/js/models/threads/abstract_thread.js @@ -75,6 +75,22 @@ var AbstractThread = Class.extend(Mixins.EventDispatcherMixin, { return this._name; }, /** + * By default, threads do not have out-of-office feature + * + * @return {string|undefined} + */ + getOutOfOfficeInfo: function () { + return undefined; + }, + /** + * By default, threads do not have out-of-office feature + * + * @return {string|undefined} + */ + getOutOfOfficeMessage: function () { + return undefined; + }, + /** * Get the status of the thread (e.g. 'online', 'offline', etc.) * * @returns {string} diff --git a/addons/mail/static/src/js/models/threads/dm_chat.js b/addons/mail/static/src/js/models/threads/dm_chat.js index 5e69dbeff8c..fb524515193 100644 --- a/addons/mail/static/src/js/models/threads/dm_chat.js +++ b/addons/mail/static/src/js/models/threads/dm_chat.js @@ -3,6 +3,10 @@ odoo.define('mail.model.DMChat', function (require) { var TwoUserChannel = require('mail.model.TwoUserChannel'); +var core = require('web.core'); + +var _t = core._t; + /** * Any piece of code in JS that make use of DMs must ideally interact with * such objects, instead of direct data from the server. @@ -18,6 +22,8 @@ var DMChat = TwoUserChannel.extend({ * @param {integer} params.data.direct_partner[0].id * @param {string} params.data.direct_partner[0].im_status * @param {string} params.data.direct_partner[0].name + * @param {string} [params.data.direct_partner[0].out_of_office_message] + * @param {string} [params.data.direct_partner[0].out_of_office_date_end] */ init: function (params) { this._super.apply(this, arguments); @@ -26,8 +32,14 @@ var DMChat = TwoUserChannel.extend({ this._directPartnerID = data.direct_partner[0].id; this._name = data.custom_channel_name || data.direct_partner[0].name; - this._status = data.direct_partner[0].im_status; + this._outOfOfficeMessage = data.direct_partner[0].out_of_office_message; + this._outOfOfficeDateEnd = data.direct_partner[0].out_of_office_date_end; this._type = 'dm_chat'; + + this.call('mail_service', 'updateImStatus', [{ + id: this._directPartnerID, + im_status: data.direct_partner[0].im_status + }]); }, //-------------------------------------------------------------------------- @@ -45,6 +57,34 @@ var DMChat = TwoUserChannel.extend({ return this._directPartnerID; }, /** + * Get the out of office info + * + * @override {mail.model.AbstractThread} + * @returns {string|undefined} + */ + getOutOfOfficeInfo: function () { + if (!this._outOfOfficeDateEnd) { + return undefined; + } + var currentDate = new Date(); + var date = new Date(this._outOfOfficeDateEnd); + var options = { day: 'numeric', month: 'short' }; + if (currentDate.getFullYear() !== date.getFullYear()) { + options.year = 'numeric'; + } + var formattedDate = date.toLocaleDateString(window.navigator.language, options); + return _.str.sprintf(_t("Out of office until %s"), formattedDate); + }, + /** + * Get the out of office message of the thread + * + * @override {mail.model.AbstractThread} + * @returns {string} + */ + getOutOfOfficeMessage: function () { + return this._outOfOfficeMessage; + }, + /** * @override */ getPreview: function () { @@ -53,10 +93,11 @@ var DMChat = TwoUserChannel.extend({ return result; }, /** - * @param {string} newStatus + * @override + * @return {string} */ - setStatus: function (newStatus) { - this._status = newStatus; + getStatus: function () { + return this.call('mail_service', 'getImStatus', { partnerID: this._directPartnerID }); }, }); diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index 777df1df4a6..204a88bb8e9 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -443,10 +443,6 @@ var MailManager = AbstractService.extend({ channel = this._makeChannel(data, options); if (channel.getType() === 'dm_chat') { this._pinnedDmPartners.push(channel.getDirectPartnerID()); - this.call('bus_service', 'updateOption', - 'bus_presence_partner_ids', - this._pinnedDmPartners - ); } this._threads.push(channel); if (data.last_message) { @@ -1041,10 +1037,6 @@ var MailManager = AbstractService.extend({ var index = this._pinnedDmPartners.indexOf(channel.getDirectPartnerID()); if (index > -1) { this._pinnedDmPartners.splice(index, 1); - this.call('bus_service', 'updateOption', - 'bus_presence_partner_ids', - this._pinnedDmPartners - ); } } this._threads = _.without(this._threads, channel); diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js index b59d7b91434..b77fa3e65fd 100644 --- a/addons/mail/static/src/js/services/mail_notification_manager.js +++ b/addons/mail/static/src/js/services/mail_notification_manager.js @@ -494,22 +494,6 @@ MailManager.include({ }); }, /** - * On receiving an update on user status (e.g. becoming 'online', 'offline', - * 'idle', etc.). - * - * @private - * @param {Object} data partner infos - * @param {integer} data.id partner ID - * @param {string} data.im_status partner new 'im status' (e.g. 'online') - */ - _handlePresenceNotification: function (data) { - var dmChat = this.getDMChatFromPartnerID(data.id); - if (dmChat) { - dmChat.setStatus(data.im_status); - this._mailBus.trigger('update_dm_presence', dmChat); - } - }, - /** * @override * @private */ @@ -569,8 +553,6 @@ MailManager.include({ }); } else if (model === 'res.partner') { self._handlePartnerNotification(notif[1]); - } else if (model === 'bus.presence') { - self._handlePresenceNotification(notif[1]); } }); }, diff --git a/addons/mail/static/src/js/services/mail_service.js b/addons/mail/static/src/js/services/mail_service.js index 4f6b0c29dfc..2256e696c2f 100644 --- a/addons/mail/static/src/js/services/mail_service.js +++ b/addons/mail/static/src/js/services/mail_service.js @@ -5,6 +5,7 @@ var core = require('web.core'); var MailManager = require('mail.Manager'); +require('mail.Manager.Status'); require('mail.Manager.Notification'); require('mail.Manager.Window'); require('mail.Manager.DocumentThread'); diff --git a/addons/mail/static/src/js/services/mail_status_manager.js b/addons/mail/static/src/js/services/mail_status_manager.js new file mode 100644 index 00000000000..5ca151d47bf --- /dev/null +++ b/addons/mail/static/src/js/services/mail_status_manager.js @@ -0,0 +1,197 @@ +odoo.define('mail.Manager.Status', function (require) { +"use strict"; + +var core = require('web.core'); +var MailManager = require('mail.Manager'); +var mailUtils = require('mail.utils'); +var QWeb = core.qweb; + +/** + * Mail Manager: IM Status + * + * This component handles im status of partners, which is useful for DM Chats, + * partner mention suggestions, and chatter messages that display the user icon. + */ +MailManager.include({ + _UPDATE_INTERVAL: 50, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Returns the uin cache im status, and triggers an asynchronous throttled + * update of im_status cache for missings + * + * @param {Object} data + * @param {integer} data.partnerID + * @return {String} + */ + getImStatus: function (data) { + var self = this; + var partnerID = data.partnerID; + if (!this._imStatus[partnerID]) { + // Add to list to call it in next bus update or _fetchMissingImStatus + this._imStatus[partnerID] = undefined; + // fetch after some time if no other getImStatus occurs + mailUtils.clearTimeout(this._fetchStatusTimeout); + this._fetchStatusTimeout = mailUtils.setTimeout(function () { + self._fetchMissingImStatus(); + }, 500); + } + return this._imStatus[partnerID]; + }, + /** + * Update status manually, to avoid to do a rpc and an asynchronous update + * after getImStatus. Can be done by any caller knowing the last im_status + * state. + * + * @param {Object[]} statusList, A list of {id, im_status} + */ + updateImStatus: function (statusList) { + var updatedIDs = []; + var self = this; + _.each(statusList, function (status) { + if (self._imStatus[status.id] === status.im_status) { + return; + } + updatedIDs.push(status.id); + self._imStatus[status.id] = status.im_status; + }); + if (! _.isEmpty(updatedIDs)) { + this._mailBus.trigger('updated_im_status', updatedIDs); // useful for thread window header + this._renderImStatus(updatedIDs); + } + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Fetch the list of im_status for partner with id in ids list and triggers + * an update. + * + * @private + * @param {Object} data + * @param {integer[]} data.partnerIDs + * @return {Deferred} + */ + _fetchImStatus: function (data) { + var self = this; + var partnerIDs = data.partnerIDs; + if (_.isEmpty(partnerIDs)) { + return $.when(); + } + return this._rpc({ + model: 'res.partner', + method: 'read', + args: [partnerIDs, ['id', 'im_status']], + }).then( function (results) { + self.updateImStatus(results); + }); + }, + /** + * Fetch the list of im_status for partner with an unknown im_status and + * triggers an update. + * + * @private + * @return {Deferred} + */ + _fetchMissingImStatus: function () { + var missing = []; + _.each(this._imStatus, function (value, key) { + if (value === undefined) { + missing.push(Number(key)); + } + }); + return this._fetchImStatus({ partnerIDs: missing }); + }, + /** + * @private + * @return {integer[]} a list of partner ids that needs update + */ + _getImStatusToUpdate: function () { + var toUpdate = []; + _.each(this._imStatus, function (status, key) { + //filter on im_partner and bot: useless to update them, status won't change + if (['im_partner', 'bot'].indexOf(status) === -1) { + toUpdate.push(Number(key)); + } + }); + return toUpdate; + }, + /** + * @override + * @private + */ + _initializeInternalState: function () { + this._super.apply(this, arguments); + this._fetchStatusTimeout = undefined; + this._imStatus = {}; + this._isTabFocused = true; + this._updateImStatusLoop(); + }, + /** + * @override + * @private + */ + _listenOnBuses: function () { + this._super.apply(this, arguments); + $(window).on("focus", this._onWindowFocusChange.bind(this, true)); + $(window).on("blur", this._onWindowFocusChange.bind(this, false)); + $(window).on("unload", this._onWindowFocusChange.bind(this, false)); + }, + /** + * @private + * @param {integer[]} updatedIds + */ + _renderImStatus: function (updatedIds) { + var self = this; + $('.o_updatable_im_status').each(function () { + var $this = $(this); + var partnerID = $this.data('partner-id'); + if (partnerID !== undefined && updatedIds.indexOf(partnerID) !== -1) { // todo instead add id on o_updatable_im_status and select only concerned ones + var status = QWeb.render('mail.UserStatus', { + status: self.getImStatus({ partnerID: partnerID }), + partnerID: partnerID, + }); + $this.replaceWith(status); + } + }); + }, + /** + * Once initialised, this loop will update the im_status of registered + * users. + * + * @private + * @param {integer} [counter=0] The recursion loop counter + */ + _updateImStatusLoop: function (counter) { + var self = this; + if (!_.isNumber(counter)) { + counter = 0; + } + mailUtils.setTimeout(function () { + if (counter >= self._UPDATE_INTERVAL && self._isTabFocused) { + self._fetchImStatus({ partnerIDs: self._getImStatusToUpdate() }); + counter = 0; + } + self._updateImStatusLoop(counter+1); + }, 1000); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {boolean} focused + */ + _onWindowFocusChange: function (focused) { + this._isTabFocused = focused; + }, +}); + +}); diff --git a/addons/mail/static/src/js/services/mail_window_manager.js b/addons/mail/static/src/js/services/mail_window_manager.js index 4971e7f3f4b..e047c71ab89 100644 --- a/addons/mail/static/src/js/services/mail_window_manager.js +++ b/addons/mail/static/src/js/services/mail_window_manager.js @@ -334,8 +334,8 @@ MailManager.include({ .on('new_channel', this, this._onNewChannel) .on('is_thread_bottom_visible', this, this._onIsThreadBottomVisible) .on('unsubscribe_from_channel', this, this._onUnsubscribeFromChannel) - .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter) - .on('update_dm_presence', this, this._onUpdateDmPresence); + .on('updated_im_status', this, this._onUpdatedImStatus) + .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter); core.bus.on('resize', this, _.debounce(this._repositionThreadWindows.bind(this), 100)); }, @@ -664,6 +664,28 @@ MailManager.include({ this._closeThreadWindow(channelID); }, /** + * Called when there is a change of the im status of the partner. + * The header of the thread window should be updated accordingly, + * in order to display the correct new im status of this users. + * + * @private + * @param {integer} partnerID + */ + _onUpdatedImStatus: function (partnerIDs) { + var self = this; + _.each(partnerIDs, function (partnerID) { + var thread = self.getDMChatFromPartnerID(partnerID); + if (! thread) { + return; + } + var threadWindow = self._getThreadWindow(thread.getID()); + if (!threadWindow) { + return; + } + threadWindow.renderHeader(); + }); + }, + /** * Called when a thread has its unread counter that has changed. * The unread counter on the thread windows should be updated. * @@ -691,21 +713,6 @@ MailManager.include({ } }, /** - * Called when there is a change of the im status of the user linked to - * DMs. The header of the thread window should be updated accordingly, - * in order to display the correct new im status of this users. - * - * @private - * @param {mail.model.Thread} thread - */ - _onUpdateDmPresence: function (thread) { - _.each(this._threadWindows, function (threadWindow) { - if (thread.getID() === threadWindow.getID()) { - threadWindow.renderHeader(); - } - }); - }, - /** * Called when a message has been updated. * * @private diff --git a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js index 3e4e2e16325..300bfb11a3e 100644 --- a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js +++ b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js @@ -148,6 +148,28 @@ var AbstractThreadWindow = Widget.extend({ return this._thread; }, /** + *Get out of office info + * + * @returns {string|undefined} + */ + getOutOfOfficeInfo: function () { + if (!this.hasThread()) { + return undefined; + } + return this._thread.getOutOfOfficeInfo(); + }, + /** + * Get out of office user text + * + * @returns {string|undefined} + */ + getOutOfOfficeMessage: function () { + if (!this.hasThread()) { + return undefined; + } + return this._thread.getOutOfOfficeMessage(); + }, + /** * Get the status of the thread, such as the im status of a DM chat * ('online', 'offline', etc.). If this window has no thread, returns * `undefined`. diff --git a/addons/mail/static/src/js/thread_windows/thread_window.js b/addons/mail/static/src/js/thread_windows/thread_window.js index 331228c91c8..41b0ef6d36c 100644 --- a/addons/mail/static/src/js/thread_windows/thread_window.js +++ b/addons/mail/static/src/js/thread_windows/thread_window.js @@ -21,6 +21,7 @@ var ThreadWindow = AbstractThreadWindow.extend({ 'click .o_mail_thread': '_onThreadWindowFocus', 'click .o_thread_composer': '_onThreadWindowFocus', 'click .o_thread_window_expand': '_onClickExpand', + 'click .o_out_of_office_read_more_less_button': '_onClickOutOfOfficeReadMoreLess', }), /** * Version of thread window that supports {mail.model.Thread} @@ -80,6 +81,7 @@ var ThreadWindow = AbstractThreadWindow.extend({ self.$input = self.$('.o_composer_text_field'); }); } + this._updateOutOfOfficeReadMoreLessButton(); return $.when(superDef, composerDef); }, @@ -250,6 +252,19 @@ var ThreadWindow = AbstractThreadWindow.extend({ }) .focus(); }, + /** + * @private + */ + _updateOutOfOfficeReadMoreLessButton: function () { + var $readMore = this.$('.o_out_of_office_text'); + var isOverflowing = $readMore.prop('scrollWidth') > $readMore.width(); + var isOverflowShown = !$readMore.hasClass('o_text_wrap'); + if (isOverflowing || isOverflowShown) { + var $button = this.$('.o_out_of_office_read_more_less_button'); + $button.show(); + $button.text(isOverflowing ? _t('Read more') : _t('Read less')); + } + }, //-------------------------------------------------------------------------- // Handlers @@ -289,6 +304,15 @@ var ThreadWindow = AbstractThreadWindow.extend({ } }, 1000, true), /** + * @private + * @param {MouseEvent} ev + */ + _onClickOutOfOfficeReadMoreLess: function (ev) { + ev.preventDefault(); + this.$('.o_out_of_office_text').toggleClass('o_text_wrap'); + this._updateOutOfOfficeReadMoreLessButton(); + }, + /** * @override * @private * @param {KeyboardEvent} ev diff --git a/addons/mail/static/src/js/user_menu.js b/addons/mail/static/src/js/user_menu.js new file mode 100644 index 00000000000..77235c1f166 --- /dev/null +++ b/addons/mail/static/src/js/user_menu.js @@ -0,0 +1,22 @@ +odoo.define('mail.UserMenu', function (require) { +"use strict"; + +/** + * This file includes the UserMenu widget defined in Community to add or + * override actions only available in Enterprise. + */ + +var UserMenu = require('web.UserMenu'); + +UserMenu.include({ + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + var session = this.getSession(); + this.outOfOfficeMessage = session.out_of_office_message; + }, +}); + +}); diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js index 1f31a6ead2f..14ac97def76 100644 --- a/addons/mail/static/src/js/utils.js +++ b/addons/mail/static/src/js/utils.js @@ -91,6 +91,14 @@ function timeFromNow(date) { return date.fromNow(); } +function o_clearTimeout(id) { + return clearTimeout(id); +} + +function o_setTimeout(func, delay) { + return setTimeout(func, delay); +} + return { addLink: addLink, getTextToHTML: getTextToHTML, @@ -100,6 +108,8 @@ return { parseEmail: parseEmail, stripHTML: stripHTML, timeFromNow: timeFromNow, + clearTimeout: o_clearTimeout, + setTimeout: o_setTimeout, }; }); diff --git a/addons/mail/static/src/scss/abstract_thread_window.scss b/addons/mail/static/src/scss/abstract_thread_window.scss index b148afa111f..ab6b46d4b9a 100644 --- a/addons/mail/static/src/scss/abstract_thread_window.scss +++ b/addons/mail/static/src/scss/abstract_thread_window.scss @@ -79,6 +79,26 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default; } + .o_out_of_office { + background-color: #9dd0e8; + padding:8px; + + .o_out_of_office_info { + font-weight: bold; + } + .o_out_of_office_text { + word-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; + } + .o_text_wrap { + white-space: nowrap; + } + .o_out_of_office_read_more_less_button { + display: none; + } + } + .o_mail_thread { flex: 1 1 100%; overflow: auto; diff --git a/addons/mail/static/src/scss/composer.scss b/addons/mail/static/src/scss/composer.scss index 096c527aa16..1f2fffcee60 100644 --- a/addons/mail/static/src/scss/composer.scss +++ b/addons/mail/static/src/scss/composer.scss @@ -55,6 +55,9 @@ font-style: italic; color: gray; } + .fa { + font-size: 0.8em; + } } textarea { resize: none; diff --git a/addons/mail/static/src/scss/discuss.scss b/addons/mail/static/src/scss/discuss.scss index 570dc2dedd6..711180d3544 100644 --- a/addons/mail/static/src/scss/discuss.scss +++ b/addons/mail/static/src/scss/discuss.scss @@ -252,6 +252,11 @@ &.o_user_idle { color: theme-color('warning'); } + &.fa-stack { + width: 1em; + height: 1em; + line-height: 1em; + } } diff --git a/addons/mail/static/src/scss/thread.scss b/addons/mail/static/src/scss/thread.scss index 7c867896150..84a826922ff 100644 --- a/addons/mail/static/src/scss/thread.scss +++ b/addons/mail/static/src/scss/thread.scss @@ -68,6 +68,19 @@ margin-top: 2px; text-align: center; font-size: smaller; + .o_thread_message_sidebar_image { + position: relative; + height: $o-mail-thread-avatar-size; + + .o_updatable_im_status { + width: $o-mail-thread-avatar-size; + } + .o_mail_user_status { + position: absolute; + bottom: 0; + right: 0; + } + } @include media-breakpoint-down(sm) { margin-top: 4px; diff --git a/addons/mail/static/src/xml/composer.xml b/addons/mail/static/src/xml/composer.xml index ff4680d7851..b9de0194e93 100644 --- a/addons/mail/static/src/xml/composer.xml +++ b/addons/mail/static/src/xml/composer.xml @@ -71,6 +71,10 @@ </t> <t t-name="mail.MentionSuggestions.Partner" t-extend="mail.MentionSuggestions"> <t t-jquery=".o_mention_proposition" t-operation="append"> + <t t-call="mail.UserStatus"> + <t t-set="status" t-value="suggestion.im_status"/> + <t t-set="partnerID" t-value="suggestion.id"/> + </t> <span class="o_mention_name"><t t-esc="suggestion.name"/></span> <t t-if="suggestion.email"> <span class="o_mention_info">(<t t-esc="suggestion.email"/>)</span> diff --git a/addons/mail/static/src/xml/discuss.xml b/addons/mail/static/src/xml/discuss.xml index 98430c81ce1..10fb71a5110 100644 --- a/addons/mail/static/src/xml/discuss.xml +++ b/addons/mail/static/src/xml/discuss.xml @@ -154,6 +154,7 @@ <span t-if="displayStatus"> <t t-call="mail.UserStatus"> <t t-set="status" t-value="channel.getStatus()"/> + <t t-set="partnerID" t-value="channel.getDirectPartnerID()"/> </t> </span> <span t-if="displayHash" class="o_mail_hash">#</span> @@ -176,11 +177,14 @@ <!-- @param {string} status + @param {integer|undefined} [partnerID] --> <t t-name="mail.UserStatus"> - <i t-if="status == 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/> - <i t-if="status == 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle" role="img" aria-label="User is idle"/> - <i t-if="status == 'offline'" class="o_mail_user_status fa fa-circle-o" title="Offline" role="img" aria-label="User is offline"/> + <span t-att-class="partnerID ? 'o_updatable_im_status' : ''" t-att-data-partner-id="partnerID"> + <i t-if="status == 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/> + <i t-if="status == 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle" role="img" aria-label="User is idle"/> + <i t-if="status == 'offline'" class="o_mail_user_status fa fa-circle" title="Offline" role="img" aria-label="User is offline"/> + </span> </t> <!-- @@ -300,8 +304,9 @@ t-att-data-preview-id="preview.id" t-att-data-document-id="preview.documentID" t-att-data-document-model="preview.documentModel" t-att-data-unread-counter="preview.unreadCounter" t-att-data-message-ids="preview.messageIDs"> <div t-attf-class="o_mail_preview_image #{preview.documentModel ? 'o_mail_preview_app' : ''}"> <img class="o_mail_preview_image" t-att-src="preview.imageSRC" alt="Preview"/> - <i t-if="preview.status === 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/> - <i t-if="preview.status === 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle" role="img" aria-label="User is idle"/> + <t t-call="mail.UserStatus"> + <t t-set="status" t-value="preview.status"/> + </t> </div> <div class="o_preview_info"> <div class="o_preview_title"> diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml index be73917a1b5..f6d6ad599ec 100644 --- a/addons/mail/static/src/xml/thread.xml +++ b/addons/mail/static/src/xml/thread.xml @@ -258,12 +258,18 @@ <div t-att-class="'o_thread_message ' + (message.getID() === options.selectedMessageID ? 'o_thread_selected_message ' : ' ') + (message.isDiscussion() or message.isNotification() ? ' o_mail_discussion ' : ' o_mail_not_discussion ')" t-att-data-message-id="message.getID()"> <div t-if="options.displayAvatars" class="o_thread_message_sidebar"> <t t-if="message.hasAuthor()"> - <img t-if="displayAuthorMessages[message.getID()]" - alt="" - t-att-src="message.getAvatarSource()" - data-oe-model="res.partner" - t-att-data-oe-id="message.shouldRedirectToAuthor() ? message.getAuthorID() : ''" - t-attf-class="o_thread_message_avatar rounded-circle #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}"/> + <div t-if="displayAuthorMessages[message.getID()]" class="o_thread_message_sidebar_image"> + <img + alt="" + t-att-src="message.getAvatarSource()" + data-oe-model="res.partner" + t-att-data-oe-id="message.shouldRedirectToAuthor() ? message.getAuthorID() : ''" + t-attf-class="o_thread_message_avatar rounded-circle #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}"/> + <t t-call="mail.UserStatus"> + <t t-set="status" t-value="message.getAuthorImStatus()"/> + <t t-set="partnerID" t-value="message.getAuthorID()"/> + </t> + </div> </t> <t t-else=""> <img t-if="displayAuthorMessages[message.getID()]" diff --git a/addons/mail/static/src/xml/thread_window.xml b/addons/mail/static/src/xml/thread_window.xml index 4164bc0abc9..76a130d1ba2 100644 --- a/addons/mail/static/src/xml/thread_window.xml +++ b/addons/mail/static/src/xml/thread_window.xml @@ -6,6 +6,13 @@ --> <t t-name="mail.ThreadWindow" t-extend="mail.AbstractThreadWindow"> <t t-jquery=".o_thread_window_header" t-operation="after"> + <div class="o_out_of_office" t-if="widget.getOutOfOfficeInfo() or widget.getOutOfOfficeMessage()"> + <div class="o_out_of_office_info" t-if="widget.getOutOfOfficeInfo()" t-esc="widget.getOutOfOfficeInfo()"/> + <div class="o_out_of_office_text o_text_wrap" t-if="widget.getOutOfOfficeMessage()"> + <t t-esc="widget.getOutOfOfficeMessage()"/> + <a href="#" class="o_out_of_office_read_more_less_button">Read more</a> + </div> + </div> <div t-if="!widget.hasThread()" class="o_thread_search_input"> <span> To: </span> <input type="text" placeholder="User name"/> diff --git a/addons/mail/static/src/xml/user_menu.xml b/addons/mail/static/src/xml/user_menu.xml new file mode 100644 index 00000000000..b5186c3b056 --- /dev/null +++ b/addons/mail/static/src/xml/user_menu.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-extend="UserMenu"> + <t t-jquery=".oe_topbar_name" t-operation="before"> + <t t-if="widget.outOfOfficeMessage"> + <i class="o_mail_user_status o_user_idle fa fa-minus-circle" title="You have set an out of office message."/> + </t> + </t> + </t> +</templates> |
