summaryrefslogtreecommitdiff
path: root/addons/mail/static/src
diff options
context:
space:
mode:
authorRobot Odoo <xmo+roboto@odoo.com>2019-02-11 12:57:48 +0100
committerGitHub <noreply@github.com>2019-02-11 12:57:48 +0100
commit47bb7ef7968cb3c5c11f767df06fcf09333a2b13 (patch)
tree1e86b27e45fe2b5803294e886227495fdd68a96b /addons/mail/static/src
parent768844040f887868759309ad101fe50191fc949a (diff)
parent975164473ccd51c00eee5917dddbd8be71b5aefc (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')
-rw-r--r--addons/mail/static/src/js/composers/basic_composer.js10
-rw-r--r--addons/mail/static/src/js/discuss.js9
-rw-r--r--addons/mail/static/src/js/models/messages/abstract_message.js8
-rw-r--r--addons/mail/static/src/js/models/messages/message.js10
-rw-r--r--addons/mail/static/src/js/models/threads/abstract_thread.js16
-rw-r--r--addons/mail/static/src/js/models/threads/dm_chat.js49
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js8
-rw-r--r--addons/mail/static/src/js/services/mail_notification_manager.js18
-rw-r--r--addons/mail/static/src/js/services/mail_service.js1
-rw-r--r--addons/mail/static/src/js/services/mail_status_manager.js197
-rw-r--r--addons/mail/static/src/js/services/mail_window_manager.js41
-rw-r--r--addons/mail/static/src/js/thread_windows/abstract_thread_window.js22
-rw-r--r--addons/mail/static/src/js/thread_windows/thread_window.js24
-rw-r--r--addons/mail/static/src/js/user_menu.js22
-rw-r--r--addons/mail/static/src/js/utils.js10
-rw-r--r--addons/mail/static/src/scss/abstract_thread_window.scss20
-rw-r--r--addons/mail/static/src/scss/composer.scss3
-rw-r--r--addons/mail/static/src/scss/discuss.scss5
-rw-r--r--addons/mail/static/src/scss/thread.scss13
-rw-r--r--addons/mail/static/src/xml/composer.xml4
-rw-r--r--addons/mail/static/src/xml/discuss.xml15
-rw-r--r--addons/mail/static/src/xml/thread.xml18
-rw-r--r--addons/mail/static/src/xml/thread_window.xml7
-rw-r--r--addons/mail/static/src/xml/user_menu.xml11
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>