diff --git a/addons/hr_holidays/static/src/components/thread_view/thread_view.js b/addons/hr_holidays/static/src/components/thread_view/thread_view.js
index c07ab2189a9..73ca28d4d0c 100644
--- a/addons/hr_holidays/static/src/components/thread_view/thread_view.js
+++ b/addons/hr_holidays/static/src/components/thread_view/thread_view.js
@@ -17,11 +17,10 @@ patch(components.ThreadView, 'hr_holidays/static/src/components/thread_view/thre
*/
_useStoreSelector(props) {
const res = this._super(...arguments);
- const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId);
- const thread = threadView ? threadView.thread : undefined;
- const correspondent = thread ? thread.correspondent : undefined;
+ const thread = res.thread;
+ const correspondent = thread && thread.correspondent;
return Object.assign({}, res, {
- correspondent: correspondent ? correspondent.__state : undefined,
+ correspondentOutOfOfficeText: correspondent && correspondent.outOfOfficeText,
});
},
});
diff --git a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
index 2dafeed4c79..35070cb8e6d 100644
--- a/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
+++ b/addons/im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js
@@ -69,15 +69,8 @@ patch(components.DiscussSidebar, 'im_livechat/static/src/components/discuss_side
*/
_useStoreSelector(props) {
return Object.assign(this._super(...arguments), {
- allOrderedAndPinnedLivechats: this.env.models['mail.thread']
- .all(thread =>
- thread.channel_type === 'livechat' &&
- thread.isPinned &&
- thread.model === 'mail.channel'
- )
- .map(livechat => livechat.__state),
- }
- );
+ allOrderedAndPinnedLivechats: this.quickSearchOrderedAndPinnedLivechatList(),
+ });
},
});
diff --git a/addons/mail/static/src/bugfix/bugfix.js b/addons/mail/static/src/bugfix/bugfix.js
index 5a7474b5c59..b1b1d43d03b 100644
--- a/addons/mail/static/src/bugfix/bugfix.js
+++ b/addons/mail/static/src/bugfix/bugfix.js
@@ -4,7 +4,3 @@
* versions of Odoo. Any module that is defined in this file should be isolated
* in its own file in master.
*/
-odoo.define('mail/static/src/bugfix/bugfix.js', function (require) {
-'use strict';
-
-});
diff --git a/addons/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js b/addons/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js
new file mode 100644
index 00000000000..b0757bd5ae0
--- /dev/null
+++ b/addons/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js
@@ -0,0 +1,72 @@
+odoo.define('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js', function (require) {
+'use strict';
+
+const { Component } = owl;
+
+/**
+ * Compares `a` and `b` up to the given `compareDepth`.
+ *
+ * @param {any} a
+ * @param {any} b
+ * @param {Object|integer} compareDepth
+ * @returns {boolean}
+ */
+function isEqual(a, b, compareDepth) {
+ const keys = Object.keys(a);
+ if (Object.keys(b).length !== keys.length) {
+ return false;
+ }
+ for (const key of keys) {
+ // the depth can be given either as a number (for all keys) or as
+ // an object (for each key)
+ let depth;
+ if (typeof compareDepth === 'number') {
+ depth = compareDepth;
+ } else {
+ depth = compareDepth[key] || 0;
+ }
+ if (depth === 0 && a[key] !== b[key]) {
+ return false;
+ }
+ if (depth !== 0) {
+ let nextDepth;
+ if (typeof depth === 'number') {
+ nextDepth = depth - 1;
+ } else {
+ nextDepth = depth;
+ }
+ if (!isEqual(a[key], b[key], nextDepth)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * This hook overrides the `shouldUpdate` method to ensure the component is only
+ * updated if its props actually changed. This is especially useful to use on
+ * components for which an extra render costs proportionally a lot more than
+ * comparing props.
+ *
+ * @param {Object} [param0={}]
+ * @param {Object} [param0.compareDepth={}] allows to specify the comparison
+ * depth to use for each prop. Default is shallow compare (depth = 0).
+ */
+function useShouldUpdateBasedOnProps({ compareDepth = {} } = {}) {
+ const component = Component.current;
+ component.shouldUpdate = nextProps => {
+ const allNewProps = Object.assign({}, nextProps);
+ const defaultProps = component.constructor.defaultProps;
+ for (const key in defaultProps) {
+ if (allNewProps[key] === undefined) {
+ allNewProps[key] = defaultProps[key];
+ }
+ }
+ return !isEqual(component.props, allNewProps, compareDepth);
+ };
+}
+
+return useShouldUpdateBasedOnProps;
+
+});
diff --git a/addons/mail/static/src/components/activity/activity.js b/addons/mail/static/src/components/activity/activity.js
index 1fd16d77464..1ee7ecf394c 100644
--- a/addons/mail/static/src/components/activity/activity.js
+++ b/addons/mail/static/src/components/activity/activity.js
@@ -6,6 +6,7 @@ const components = {
FileUploader: require('mail/static/src/components/file_uploader/file_uploader.js'),
MailTemplate: require('mail/static/src/components/mail_template/mail_template.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const {
@@ -24,6 +25,7 @@ class Activity extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
this.state = useState({
areDetailsVisible: false,
});
diff --git a/addons/mail/static/src/components/activity_box/activity_box.js b/addons/mail/static/src/components/activity_box/activity_box.js
index 8f5c13c94dd..ca19169438b 100644
--- a/addons/mail/static/src/components/activity_box/activity_box.js
+++ b/addons/mail/static/src/components/activity_box/activity_box.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/activity_box/activity_box.js', function
const components = {
Activity: require('mail/static/src/components/activity/activity.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,6 +16,7 @@ class ActivityBox extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId);
const thread = chatter && chatter.thread;
diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js
index e951b5e4737..de1ea5ce232 100644
--- a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js
+++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -13,6 +14,7 @@ class ActivityMarkDonePopover extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const activity = this.env.models['mail.activity'].get(props.activityLocalId);
return {
diff --git a/addons/mail/static/src/components/attachment/attachment.js b/addons/mail/static/src/components/attachment/attachment.js
index f62113c4c41..32b31d58e20 100644
--- a/addons/mail/static/src/components/attachment/attachment.js
+++ b/addons/mail/static/src/components/attachment/attachment.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/attachment/attachment.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const components = {
@@ -16,6 +17,11 @@ class Attachment extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ attachmentLocalIds: 1,
+ },
+ });
useStore(props => {
const attachment = this.env.models['mail.attachment'].get(props.attachmentLocalId);
return {
diff --git a/addons/mail/static/src/components/attachment_box/attachment_box.js b/addons/mail/static/src/components/attachment_box/attachment_box.js
index 6e0c9d7c55e..5bfe7c06463 100644
--- a/addons/mail/static/src/components/attachment_box/attachment_box.js
+++ b/addons/mail/static/src/components/attachment_box/attachment_box.js
@@ -7,6 +7,7 @@ const components = {
FileUploader: require('mail/static/src/components/file_uploader/file_uploader.js'),
};
const useDragVisibleDropZone = require('mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -20,14 +21,19 @@ class AttachmentBox extends Component {
constructor(...args) {
super(...args);
this.isDropZoneVisible = useDragVisibleDropZone();
+ useShouldUpdateBasedOnProps();
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
return {
- attachments: thread
- ? thread.allAttachments.map(attachment => attachment.__state)
- : [],
- thread: thread ? thread.__state : undefined,
+ thread,
+ threadAllAttachments: thread ? thread.allAttachments : [],
+ threadId: thread && thread.id,
+ threadModel: thread && thread.model,
};
+ }, {
+ compareDepth: {
+ threadAllAttachments: 1,
+ },
});
/**
* Reference of the file uploader.
diff --git a/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js b/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js
index 46fa1c65260..ab7e155a809 100644
--- a/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js
+++ b/addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const components = {
@@ -17,6 +18,7 @@ class AttachmentDeleteConfirmDialog extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const attachment = this.env.models['mail.attachment'].get(props.attachmentLocalId);
return {
diff --git a/addons/mail/static/src/components/attachment_list/attachment_list.js b/addons/mail/static/src/components/attachment_list/attachment_list.js
index ecd7e2f47c8..d8658ac83dc 100644
--- a/addons/mail/static/src/components/attachment_list/attachment_list.js
+++ b/addons/mail/static/src/components/attachment_list/attachment_list.js
@@ -4,16 +4,24 @@ odoo.define('mail/static/src/components/attachment_list/attachment_list.js', fun
const components = {
Attachment: require('mail/static/src/components/attachment/attachment.js'),
};
+
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
class AttachmentList extends Component {
+
/**
* @override
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ attachmentLocalIds: 1,
+ },
+ });
useStore(props => {
const attachments = this.env.models['mail.attachment'].all().filter(attachment =>
props.attachmentLocalIds.includes(attachment.localId)
@@ -23,6 +31,10 @@ class AttachmentList extends Component {
? attachments.map(attachment => attachment.__state)
: undefined,
};
+ }, {
+ compareDepth: {
+ attachments: 1,
+ },
});
}
diff --git a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js
index a161b7c5bd0..1cd680df8b0 100644
--- a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js
+++ b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js
@@ -2,6 +2,7 @@ odoo.define('mail/static/src/components/attachment_viewer/attachment_viewer.js',
'use strict';
const useRefs = require('mail/static/src/component_hooks/use_refs/use_refs.js');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component, QWeb } = owl;
@@ -19,6 +20,7 @@ class AttachmentViewer extends Component {
constructor(...args) {
super(...args);
this.MIN_SCALE = MIN_SCALE;
+ useShouldUpdateBasedOnProps();
useStore(props => {
const attachmentViewer = this.env.models['mail.attachment_viewer'].get(props.localId);
return {
diff --git a/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js b/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js
index c80bbafc4c7..8059cc3571a 100644
--- a/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js
+++ b/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js
@@ -1,10 +1,17 @@
odoo.define('mail/static/src/components/autocomplete_input/autocomplete_input.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+
const { Component } = owl;
class AutocompleteInput extends Component {
+ constructor(...args) {
+ super(...args);
+ useShouldUpdateBasedOnProps();
+ }
+
mounted() {
if (this.props.isFocusOnMount) {
this.el.focus();
diff --git a/addons/mail/static/src/components/chat_window/chat_window.js b/addons/mail/static/src/components/chat_window/chat_window.js
index 8ae13de6d92..7e31cd3b6d4 100644
--- a/addons/mail/static/src/components/chat_window/chat_window.js
+++ b/addons/mail/static/src/components/chat_window/chat_window.js
@@ -6,6 +6,7 @@ const components = {
ChatWindowHeader: require('mail/static/src/components/chat_window_header/chat_window_header.js'),
ThreadView: require('mail/static/src/components/thread_view/thread_view.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
const { isEventHandled } = require('mail/static/src/utils/utils.js');
@@ -22,14 +23,24 @@ class ChatWindow extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const chatWindow = this.env.models['mail.chat_window'].get(props.chatWindowLocalId);
const thread = chatWindow ? chatWindow.thread : undefined;
return {
- chatWindow: chatWindow ? chatWindow.__state : undefined,
+ chatWindow,
+ chatWindowHasNewMessageForm: chatWindow && chatWindow.hasNewMessageForm,
+ chatWindowIsDoFocus: chatWindow && chatWindow.isDoFocus,
+ chatWindowIsFocused: chatWindow && chatWindow.isFocused,
+ chatWindowIsFolded: chatWindow && chatWindow.isFolded,
+ chatWindowThreadView: chatWindow && chatWindow.threadView,
+ chatWindowVisibleIndex: chatWindow && chatWindow.visibleIndex,
+ chatWindowVisibleOffset: chatWindow && chatWindow.visibleOffset,
isDeviceMobile: this.env.messaging.device.isMobile,
localeTextDirection: this.env.messaging.locale.textDirection,
- thread: thread ? thread.__state : undefined,
+ thread,
+ threadMassMailing: thread && thread.mass_mailing,
+ threadModel: thread && thread.model,
};
});
useUpdate({ func: () => this._update() });
diff --git a/addons/mail/static/src/components/chat_window_header/chat_window_header.js b/addons/mail/static/src/components/chat_window_header/chat_window_header.js
index 428e97048c0..5b30eb80805 100644
--- a/addons/mail/static/src/components/chat_window_header/chat_window_header.js
+++ b/addons/mail/static/src/components/chat_window_header/chat_window_header.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/chat_window_header/chat_window_header.js
const components = {
ThreadIcon: require('mail/static/src/components/thread_icon/thread_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const {
isEventHandled,
@@ -19,13 +20,21 @@ class ChatWindowHeader extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const chatWindow = this.env.models['mail.chat_window'].get(props.chatWindowLocalId);
const thread = chatWindow && chatWindow.thread;
+ const correspondent = thread && thread.correspondent;
return {
- chatWindow: chatWindow ? chatWindow.__state : undefined,
+ chatWindow,
+ chatWindowHasShiftNext: chatWindow && chatWindow.hasShiftNext,
+ chatWindowHasShiftPrev: chatWindow && chatWindow.hasShiftPrev,
+ chatWindowName: chatWindow && chatWindow.name,
+ correspondent,
isDeviceMobile: this.env.messaging.device.isMobile,
- thread: thread ? thread.__state : undefined,
+ thread,
+ threadLocalMessageUnreadCounter: thread && thread.localMessageUnreadCounter,
+ threadMassMailing: thread && thread.mass_mailing,
};
});
}
diff --git a/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js b/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js
index c6ec0b74e9f..926ca0839d0 100644
--- a/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js
+++ b/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/chat_window_hidden_menu/chat_window_hidd
const components = {
ChatWindowHeader: require('mail/static/src/components/chat_window_header/chat_window_header.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
diff --git a/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js b/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js
index 88a840328ea..a0c49a9614b 100644
--- a/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js
+++ b/addons/mail/static/src/components/chat_window_manager/chat_window_manager.js
@@ -5,6 +5,7 @@ const components = {
ChatWindow: require('mail/static/src/components/chat_window/chat_window.js'),
ChatWindowHiddenMenu: require('mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -16,19 +17,23 @@ class ChatWindowManager extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const chatWindowManager = this.env.messaging && this.env.messaging.chatWindowManager;
const allOrderedVisible = chatWindowManager
? chatWindowManager.allOrderedVisible
: [];
return {
- allOrderedVisible: allOrderedVisible.map(chatWindow => chatWindow ? chatWindow.__state : undefined),
- chatWindowManager: chatWindowManager ? chatWindowManager.__state : undefined,
+ allOrderedVisible,
+ allOrderedVisibleThread: allOrderedVisible.map(chatWindow => chatWindow.thread),
+ chatWindowManager,
+ chatWindowManagerHasHiddenChatWindows: chatWindowManager && chatWindowManager.hasHiddenChatWindows,
isMessagingInitialized: this.env.isMessagingInitialized(),
};
}, {
compareDepth: {
allOrderedVisible: 1,
+ allOrderedVisibleThread: 1,
},
});
}
diff --git a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
index 1da10ea91d7..d97ece53081 100644
--- a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
+++ b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
@@ -855,11 +855,12 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector('.o_NotificationList_preview').click(),
message: "should wait until channel 20 scrolled to its last message after opening it from the messaging menu",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -871,10 +872,11 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a
document.querySelector(`.o_ThreadView_messageList`).scrollTop = 142;
},
message: "should wait until channel 20 scrolled to 142 after setting this value manually",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
},
@@ -890,10 +892,11 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a
eventName: 'o-component-message-list-scrolled',
func: () => this.showHomeMenu(),
message: "should wait until channel 20 restored its scroll to 142 after showing the home menu",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
},
@@ -1623,11 +1626,12 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector('.o_NotificationList_preview').click(),
message: "should wait until channel 20 scrolled to its last message after opening it from the messaging menu",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -1639,10 +1643,11 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f
document.querySelector(`.o_ThreadView_messageList`).scrollTop = 142;
},
message: "should wait until channel 20 scrolled to 142 after setting this value manually",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
},
@@ -1666,10 +1671,11 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector('.o_ChatWindow_header').click(),
message: "should wait until channel 20 restored its scroll position to 142",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
@@ -1823,11 +1829,12 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector('.o_NotificationList_preview').click(),
message: "should wait until channel 20 scrolled to its last message after opening it from the messaging menu",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -1837,10 +1844,11 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector(`.o_ThreadView_messageList`).scrollTop = 142,
message: "should wait until channel 20 scrolled to 142 after setting this value manually",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
},
@@ -1853,10 +1861,11 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector('.o_ChatWindow_header').click(),
message: "should wait until channel 20 restored its scroll to 142 after unfolding it",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
},
@@ -1876,10 +1885,11 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector('.o_ChatWindow_header').click(),
message: "should wait until channel 20 restored its scroll position to the last saved value (142)",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 142
);
},
diff --git a/addons/mail/static/src/components/chatter/chatter.js b/addons/mail/static/src/components/chatter/chatter.js
index 8c19c3ace07..42462e930f9 100644
--- a/addons/mail/static/src/components/chatter/chatter.js
+++ b/addons/mail/static/src/components/chatter/chatter.js
@@ -8,6 +8,7 @@ const components = {
Composer: require('mail/static/src/components/composer/composer.js'),
ThreadView: require('mail/static/src/components/thread_view/thread_view.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -21,6 +22,7 @@ class Chatter extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId);
const thread = chatter ? chatter.thread : undefined;
@@ -31,7 +33,9 @@ class Chatter extends Component {
return {
attachments: attachments.map(attachment => attachment.__state),
chatter: chatter ? chatter.__state : undefined,
- thread: thread ? thread.__state : undefined,
+ composer: thread && thread.composer,
+ thread,
+ threadActivitiesLength: thread && thread.activities.length,
};
}, {
compareDepth: {
diff --git a/addons/mail/static/src/components/chatter_container/chatter_container.js b/addons/mail/static/src/components/chatter_container/chatter_container.js
index 19c847d01d9..2b186e62fcb 100644
--- a/addons/mail/static/src/components/chatter_container/chatter_container.js
+++ b/addons/mail/static/src/components/chatter_container/chatter_container.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/chatter_container/chatter_container.js',
const components = {
Chatter: require('mail/static/src/components/chatter/chatter.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
const { clear } = require('mail/static/src/model/model_field_command.js');
@@ -27,6 +28,7 @@ class ChatterContainer extends Component {
super(...args);
this.chatter = undefined;
this._wasMessagingInitialized = false;
+ useShouldUpdateBasedOnProps();
useStore(props => {
const isMessagingInitialized = this.env.isMessagingInitialized();
// Delay creation of chatter record until messaging is initialized.
diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js
index aaed5266514..41d2a4615f0 100644
--- a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js
+++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.js
@@ -5,6 +5,7 @@ const components = {
FollowButton: require('mail/static/src/components/follow_button/follow_button.js'),
FollowerListMenu: require('mail/static/src/components/follower_list_menu/follower_list_menu.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -16,6 +17,7 @@ class ChatterTopbar extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId);
const thread = chatter ? chatter.thread : undefined;
@@ -23,7 +25,7 @@ class ChatterTopbar extends Component {
return {
areThreadAttachmentsLoaded: thread && thread.areAttachmentsLoaded,
chatter: chatter ? chatter.__state : undefined,
- composer: chatter && chatter.composer ? chatter.composer.__state : undefined,
+ composerIsLog: chatter && chatter.composer && chatter.composer.isLog,
threadAttachmentsAmount: threadAttachments.length,
};
});
diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js
index 991d5fc4482..31654a4f15c 100644
--- a/addons/mail/static/src/components/composer/composer.js
+++ b/addons/mail/static/src/components/composer/composer.js
@@ -11,8 +11,9 @@ const components = {
ThreadTextualTypingStatus: require('mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js'),
};
const useDragVisibleDropZone = require('mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js');
-const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
+const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
const {
isEventHandled,
markEventHandled,
@@ -29,15 +30,33 @@ class Composer extends Component {
constructor(...args) {
super(...args);
this.isDropZoneVisible = useDragVisibleDropZone();
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ textInputSendShortcuts: 1,
+ },
+ });
useStore(props => {
const composer = this.env.models['mail.composer'].get(props.composerLocalId);
+ const thread = composer && composer.thread;
return {
- composer: composer ? composer.__state : undefined,
+ composer,
+ composerAttachments: composer ? composer.attachments : [],
+ composerCanPostMessage: composer && composer.canPostMessage,
+ composerHasFocus: composer && composer.hasFocus,
+ composerIsLog: composer && composer.isLog,
+ composerSubjectContent: composer && composer.subjectContent,
isDeviceMobile: this.env.messaging.device.isMobile,
- thread: composer && composer.thread
- ? composer.thread.__state
- : undefined,
+ thread,
+ threadChannelType: thread && thread.channel_type, // for livechat override
+ threadDisplayName: thread && thread.displayName,
+ threadMassMailing: thread && thread.mass_mailing,
+ threadModel: thread && thread.model,
+ threadName: thread && thread.name,
};
+ }, {
+ compareDepth: {
+ composerAttachments: 1,
+ },
});
useUpdate({ func: () => this._update() });
/**
@@ -198,6 +217,9 @@ class Composer extends Component {
* @private
*/
_update() {
+ if (this.props.isDoFocus) {
+ this.focus();
+ }
if (!this.composer) {
return;
}
@@ -364,7 +386,6 @@ class Composer extends Component {
Object.assign(Composer, {
components,
defaultProps: {
- attachmentLocalIds: [],
hasCurrentPartnerAvatar: true,
hasDiscardButton: false,
hasFollowers: false,
@@ -372,13 +393,10 @@ Object.assign(Composer, {
hasThreadName: false,
hasThreadTyping: false,
isCompact: true,
+ isDoFocus: false,
isExpandable: false,
},
props: {
- attachmentLocalIds: {
- type: Array,
- element: String,
- },
attachmentsDetailsMode: {
type: String,
optional: true,
@@ -394,6 +412,10 @@ Object.assign(Composer, {
hasSendButton: Boolean,
hasThreadName: Boolean,
hasThreadTyping: Boolean,
+ /**
+ * Determines whether this should become focused.
+ */
+ isDoFocus: Boolean,
showAttachmentsExtensions: {
type: Boolean,
optional: true,
@@ -402,15 +424,6 @@ Object.assign(Composer, {
type: Boolean,
optional: true,
},
- initialAttachmentLocalIds: {
- type: Array,
- element: String,
- optional: true,
- },
- initialTextInputHtmlContent: {
- type: String,
- optional: true,
- },
isCompact: Boolean,
isExpandable: Boolean,
/**
diff --git a/addons/mail/static/src/components/composer/composer_tests.js b/addons/mail/static/src/components/composer/composer_tests.js
index b93670aaf97..5a6b786cea5 100644
--- a/addons/mail/static/src/components/composer/composer_tests.js
+++ b/addons/mail/static/src/components/composer/composer_tests.js
@@ -820,8 +820,6 @@ QUnit.test("channel with no commands should not prompt any command suggestions o
await afterNextRender(() => {
document.querySelector('.o_ComposerTextInput_textarea').focus();
document.execCommand('insertText', false, "/");
- });
- await afterNextRender(() => {
const composer_text_input = document.querySelector('.o_ComposerTextInput_textarea');
composer_text_input.dispatchEvent(new window.KeyboardEvent('keydown'));
composer_text_input.dispatchEvent(new window.KeyboardEvent('keyup'));
diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js
index f9b3944454b..efedf6629f8 100644
--- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js
+++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -30,7 +31,7 @@ class ComposerSuggestedRecipient extends Component {
constructor(...args) {
super(...args);
this.id = _.uniqueId('o_ComposerSuggestedRecipient_');
-
+ useShouldUpdateBasedOnProps();
useStore(props => {
const suggestedRecipientInfo = this.env.models['mail.suggested_recipient_info'].get(props.suggestedRecipientLocalId);
const partner = suggestedRecipientInfo && suggestedRecipientInfo.partner;
diff --git a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js
index 330072960bd..61da098c37c 100644
--- a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js
+++ b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -17,15 +18,19 @@ class ComposerSuggestedRecipientList extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
this.state = useState({
hasShowMoreButton: false,
});
-
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
return {
- thread: thread ? thread.__state : undefined,
+ threadSuggestedRecipientInfoList: thread ? thread.suggestedRecipientInfoList : [],
};
+ }, {
+ compareDepth: {
+ threadSuggestedRecipientInfoList: 1,
+ },
});
}
diff --git a/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js b/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js
index 308dee76821..4ae48db623b 100644
--- a/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js
+++ b/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/composer_suggestion/composer_suggestion.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -17,11 +18,12 @@ class ComposerSuggestion extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const composer = this.env.models['mail.composer'].get(this.props.composerLocalId);
const record = this.env.models[props.modelName].get(props.recordLocalId);
return {
- composer: composer && composer.__state,
+ composerHasToScrollToActiveSuggestion: composer && composer.hasToScrollToActiveSuggestion,
record: record ? record.__state : undefined,
};
});
diff --git a/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js b/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js
index c033feb28ed..ea2153dbf6f 100644
--- a/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js
+++ b/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/composer_suggestion_list/composer_sugges
const components = {
ComposerSuggestion: require('mail/static/src/components/composer_suggestion/composer_suggestion.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,6 +16,7 @@ class ComposerSuggestionList extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const composer = this.env.models['mail.composer'].get(props.composerLocalId);
const activeSuggestedRecord = composer
@@ -27,14 +29,11 @@ class ComposerSuggestionList extends Component {
? composer.mainSuggestedRecordsList
: [];
return {
- activeSuggestedRecord: activeSuggestedRecord ? activeSuggestedRecord.__state : undefined,
- composer: composer ? composer.__state : undefined,
- extraSuggestedRecordsList: extraSuggestedRecordsList
- ? extraSuggestedRecordsList.map(record => record.__state)
- : [],
- mainSuggestedRecordsList: mainSuggestedRecordsList
- ? mainSuggestedRecordsList.map(record => record.__state)
- : [],
+ activeSuggestedRecord,
+ composer,
+ composerSuggestionModelName: composer && composer.suggestionModelName,
+ extraSuggestedRecordsList: extraSuggestedRecordsList,
+ mainSuggestedRecordsList: mainSuggestedRecordsList,
};
}, {
compareDepth: {
diff --git a/addons/mail/static/src/components/composer_text_input/composer_text_input.js b/addons/mail/static/src/components/composer_text_input/composer_text_input.js
index 75bce5ac833..bf9ec6bdb59 100644
--- a/addons/mail/static/src/components/composer_text_input/composer_text_input.js
+++ b/addons/mail/static/src/components/composer_text_input/composer_text_input.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/composer_text_input/composer_text_input.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -12,9 +13,6 @@ const { markEventHandled } = require('mail/static/src/utils/utils.js');
const { Component } = owl;
const { useRef } = owl.hooks;
-/**
- * ComposerInput relies on a minimal HTML editor in order to support mentions.
- */
class ComposerTextInput extends Component {
/**
@@ -22,15 +20,29 @@ class ComposerTextInput extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ sendShortcuts: 1,
+ },
+ });
useStore(props => {
const composer = this.env.models['mail.composer'].get(props.composerLocalId);
const thread = composer && composer.thread;
const correspondent = thread ? thread.correspondent : undefined;
return {
- composer: composer ? composer.__state : undefined,
- correspondent: correspondent ? correspondent.__state : undefined,
+ composerHasFocus: composer && composer.hasFocus,
+ composerHasSuggestions: composer && composer.hasSuggestions,
+ composerIsLastStateChangeProgrammatic: composer && composer.isLastStateChangeProgrammatic,
+ composerIsLog: composer && composer.isLog,
+ composerTextInputContent: composer && composer.textInputContent,
+ composerTextInputCursorEnd: composer && composer.textInputCursorEnd,
+ composerTextInputCursorStart: composer && composer.textInputCursorStart,
+ composerTextInputSelectionDirection: composer && composer.textInputSelectionDirection,
+ correspondent,
+ correspondentNameOrDisplayName: correspondent && correspondent.nameOrDisplayName,
isDeviceMobile: this.env.messaging.device.isMobile,
- thread: thread ? thread.__state : undefined,
+ threadDisplayName: thread && thread.displayName,
+ threadModel: thread && thread.model,
};
});
/**
@@ -78,7 +90,7 @@ class ComposerTextInput extends Component {
}
if (this.composer.thread.model === 'mail.channel') {
if (this.composer.thread.correspondent) {
- return _.str.sprintf("Message %s...", this.composer.thread.correspondent.nameOrDisplayName)
+ return _.str.sprintf("Message %s...", this.composer.thread.correspondent.nameOrDisplayName);
}
return _.str.sprintf("Message #%s...", this.composer.thread.displayName);
}
@@ -196,6 +208,7 @@ class ComposerTextInput extends Component {
*/
_onFocusinTextarea() {
this.composer.focus();
+ this.trigger('o-focusin-composer');
}
/**
diff --git a/addons/mail/static/src/components/dialog/dialog.js b/addons/mail/static/src/components/dialog/dialog.js
index fa15f1c4b23..65902c9ce23 100644
--- a/addons/mail/static/src/components/dialog/dialog.js
+++ b/addons/mail/static/src/components/dialog/dialog.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/dialog/dialog.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const patchMixin = require('web.patchMixin');
@@ -21,6 +22,7 @@ class Dialog extends Component {
this._componentRef = useRef('component');
this._onClickGlobal = this._onClickGlobal.bind(this);
this._onKeydownDocument = this._onKeydownDocument.bind(this);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const dialog = this.env.models['mail.dialog'].get(props.dialogLocalId);
return {
diff --git a/addons/mail/static/src/components/dialog_manager/dialog_manager.js b/addons/mail/static/src/components/dialog_manager/dialog_manager.js
index 9b1901577e1..69b64a27ff5 100644
--- a/addons/mail/static/src/components/dialog_manager/dialog_manager.js
+++ b/addons/mail/static/src/components/dialog_manager/dialog_manager.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/dialog_manager/dialog_manager.js', funct
const components = {
Dialog: require('mail/static/src/components/dialog/dialog.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,6 +16,7 @@ class DialogManager extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const dialogManager = this.env.messaging && this.env.messaging.dialogManager;
return {
diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js
index ff577956c75..e303e495015 100644
--- a/addons/mail/static/src/components/discuss/discuss.js
+++ b/addons/mail/static/src/components/discuss/discuss.js
@@ -12,8 +12,10 @@ const components = {
NotificationList: require('mail/static/src/components/notification_list/notification_list.js'),
ThreadView: require('mail/static/src/components/thread_view/thread_view.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
-const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
+
+const patchMixin = require('web.patchMixin');
const { Component } = owl;
const { useRef } = owl.hooks;
@@ -25,27 +27,13 @@ class Discuss extends Component {
*/
constructor(...args) {
super(...args);
- useStore(props => {
- const discuss = this.env.messaging && this.env.messaging.discuss;
- const threadView = discuss && discuss.threadView;
- return {
- checkedMessages: threadView ? threadView.checkedMessages.map(message => message.__state) : [],
- discuss: discuss ? discuss.__state : undefined,
- isDeviceMobile: this.env.messaging && this.env.messaging.device.isMobile,
- isMessagingInitialized: this.env.isMessagingInitialized(),
- thread: discuss && discuss.thread ? discuss.thread.__state : undefined,
- threadCache: (threadView && threadView.threadCache)
- ? threadView.threadCache.__state
- : undefined,
- uncheckedMessages: threadView ? threadView.uncheckedMessages.map(message => message.__state) : [],
- };
- }, {
+ useShouldUpdateBasedOnProps();
+ useStore((...args) => this._useStoreSelector(...args), {
compareDepth: {
checkedMessages: 1,
uncheckedMessages: 1,
},
});
- useUpdate({ func: () => this._update() });
this._updateLocalStoreProps();
/**
* Reference of the composer. Useful to focus it.
@@ -64,7 +52,7 @@ class Discuss extends Component {
this.discuss.update({ isOpen: true });
if (this.discuss.thread) {
this.trigger('o-push-state-action-manager');
- } else if (this.env.messaging.isInitialized) {
+ } else if (this.env.isMessagingInitialized()) {
this.discuss.openInitThread();
}
this._updateLocalStoreProps();
@@ -142,27 +130,6 @@ class Discuss extends Component {
// Private
//--------------------------------------------------------------------------
- /**
- * @private
- */
- _update() {
- if (!this.discuss) {
- return;
- }
- if (this.discuss.isDoFocus) {
- this.discuss.update({ isDoFocus: false });
- const composer = this._composerRef.comp;
- if (composer) {
- composer.focus();
- } else {
- const threadView = this._threadViewRef.comp;
- if (threadView) {
- threadView.focus();
- }
- }
- }
- }
-
/**
* @private
*/
@@ -187,6 +154,50 @@ class Discuss extends Component {
);
}
+ /**
+ * Returns data selected from the store.
+ *
+ * @private
+ * @param {Object} props
+ * @returns {Object}
+ */
+ _useStoreSelector(props) {
+ const discuss = this.env.messaging && this.env.messaging.discuss;
+ const thread = discuss && discuss.thread;
+ const threadView = discuss && discuss.threadView;
+ const replyingToMessage = discuss && discuss.replyingToMessage;
+ const replyingToMessageOriginThread = replyingToMessage && replyingToMessage.originThread;
+ const checkedMessages = threadView ? threadView.checkedMessages : [];
+ return {
+ checkedMessages,
+ checkedMessagesIsModeratedByCurrentPartner: checkedMessages && checkedMessages.some(message => message.isModeratedByCurrentPartner), // for widget
+ discuss,
+ discussActiveId: discuss && discuss.activeId, // for widget
+ discussActiveMobileNavbarTabId: discuss && discuss.activeMobileNavbarTabId,
+ discussHasModerationDiscardDialog: discuss && discuss.hasModerationDiscardDialog,
+ discussHasModerationRejectDialog: discuss && discuss.hasModerationRejectDialog,
+ discussIsAddingChannel: discuss && discuss.isAddingChannel,
+ discussIsAddingChat: discuss && discuss.isAddingChat,
+ discussIsDoFocus: discuss && discuss.isDoFocus,
+ discussReplyingToMessageOriginThreadComposer: replyingToMessageOriginThread && replyingToMessageOriginThread.composer,
+ inbox: this.env.messaging.inbox,
+ isDeviceMobile: this.env.messaging && this.env.messaging.device.isMobile,
+ isMessagingInitialized: this.env.isMessagingInitialized(),
+ replyingToMessage,
+ starred: this.env.messaging.starred, // for widget
+ thread,
+ threadCache: threadView && threadView.threadCache,
+ threadChannelType: thread && thread.channel_type, // for widget
+ threadDisplayName: thread && thread.displayName, // for widget
+ threadCounter: thread && thread.counter,
+ threadModel: thread && thread.model,
+ threadPublic: thread && thread.public, // for widget
+ threadView,
+ threadViewMessagesLength: threadView && threadView.messages.length, // for widget
+ uncheckedMessages: threadView ? threadView.uncheckedMessages : [], // for widget
+ };
+ }
+
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
@@ -205,6 +216,14 @@ class Discuss extends Component {
this.discuss.update({ hasModerationRejectDialog: false });
}
+ /**
+ * @private
+ * @param {CustomEvent} ev
+ */
+ _onFocusinComposer(ev) {
+ this.discuss.update({ isDoFocus: false });
+ }
+
/**
* @private
* @param {CustomEvent} ev
@@ -289,6 +308,6 @@ Object.assign(Discuss, {
template: 'mail.Discuss',
});
-return Discuss;
+return patchMixin(Discuss);
});
diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml
index fa190895e16..1fadb224ce1 100644
--- a/addons/mail/static/src/components/discuss/discuss.xml
+++ b/addons/mail/static/src/components/discuss/discuss.xml
@@ -60,7 +60,9 @@
hasSquashCloseMessages="discuss.thread.model !== 'mail.box'"
haveMessagesMarkAsReadIcon="discuss.thread === env.messaging.inbox"
haveMessagesReplyIcon="discuss.thread === env.messaging.inbox"
+ isDoFocus="discuss.isDoFocus"
threadViewLocalId="discuss.threadView.localId"
+ t-on-o-focusin-composer="_onFocusinComposer"
t-on-o-rendered="_onThreadRendered"
t-ref="threadView"
/>
@@ -91,7 +93,9 @@
hasCurrentPartnerAvatar="!env.messaging.device.isMobile"
hasDiscardButton="true"
hasThreadName="true"
+ isDoFocus="discuss.isDoFocus"
textInputSendShortcuts="['ctrl-enter', 'meta-enter']"
+ t-on-o-focusin-composer="_onFocusinComposer"
t-on-o-message-posted="_onReplyingToMessageMessagePosted"
t-ref="composer"
/>
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
index 97b826487cd..59a2b8cc239 100644
--- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js
+++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
@@ -1709,11 +1709,12 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) {
waitUntilEvent: {
eventName: 'o-component-message-list-scrolled',
message: "should wait until channel 20 scrolled to its last message initially",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -1756,11 +1757,12 @@ QUnit.test('load more messages from channel (auto-load on scroll)', async functi
waitUntilEvent: {
eventName: 'o-component-message-list-scrolled',
message: "should wait until channel 20 scrolled to its last message initially",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -1844,11 +1846,12 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
waitUntilEvent: {
eventName: 'o-component-message-list-scrolled',
message: "should wait until channel 20 scrolled to its last message initially",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -1872,10 +1875,11 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop = 0;
},
message: "should wait until channel scrolled to top",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === 0
);
},
@@ -1912,11 +1916,12 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
messageList.scrollTop = messageList.scrollHeight - messageList.clientHeight;
},
message: "should wait until channel scrolled to bottom",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -1973,8 +1978,8 @@ QUnit.test('restore thread scroll position', async function (assert) {
waitUntilEvent: {
eventName: 'o-component-message-list-scrolled',
message: "should wait until channel 11 scrolled to its last message",
- predicate: ({ threadViewer }) => {
- return threadViewer.thread.model === 'mail.channel' && threadViewer.thread.id === 11;
+ predicate: ({ thread }) => {
+ return thread && thread.model === 'mail.channel' && thread.id === 11;
},
},
});
@@ -1999,8 +2004,8 @@ QUnit.test('restore thread scroll position', async function (assert) {
eventName: 'o-component-message-list-scrolled',
func: () => document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop = 0,
message: "should wait until channel 11 changed its scroll position to top",
- predicate: ({ threadViewer }) => {
- return threadViewer.thread.model === 'mail.channel' && threadViewer.thread.id === 11;
+ predicate: ({ thread }) => {
+ return thread && thread.model === 'mail.channel' && thread.id === 11;
},
});
assert.strictEqual(
@@ -2028,11 +2033,12 @@ QUnit.test('restore thread scroll position', async function (assert) {
`).click();
},
message: "should wait until channel 12 scrolled to its last message",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 12 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 12 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -2060,10 +2066,11 @@ QUnit.test('restore thread scroll position', async function (assert) {
`).click();
},
message: "should wait until channel 11 restored its scroll position",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 11 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 11 &&
scrollTop === 0
);
},
@@ -2089,11 +2096,12 @@ QUnit.test('restore thread scroll position', async function (assert) {
`).click();
},
message: "should wait until channel 12 recovered its scroll position (to bottom)",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 12 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 12 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -3669,11 +3677,12 @@ QUnit.test('load recent messages from thread (already loaded some old messages)'
`).click();
},
message: "should wait until channel scrolled to bottom after opening it from the discuss sidebar",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ scrollTop, thread }) => {
const messageList = document.querySelector('.o_ThreadView_messageList');
return (
- threadViewer.thread.model === 'mail.channel' &&
- threadViewer.thread.id === 20 &&
+ thread &&
+ thread.model === 'mail.channel' &&
+ thread.id === 20 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
@@ -3975,7 +3984,7 @@ QUnit.test('mark a single message as read should only move this message to "Hist
});
QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) {
- assert.expect(4);
+ assert.expect(2);
const messageOffset = 200;
for (let id = messageOffset; id < messageOffset + 40; id++) {
@@ -3998,22 +4007,18 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
waitUntilEvent: {
eventName: 'o-component-message-list-scrolled',
message: "should wait until inbox scrolled to its last message initially",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ orderedMessages, scrollTop, thread }) => {
const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
return (
- threadViewer.thread.model === 'mail.box' &&
- threadViewer.thread.id === 'inbox' &&
+ thread &&
+ thread.model === 'mail.box' &&
+ thread.id === 'inbox' &&
+ orderedMessages.length === 30 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
},
});
- assert.containsN(
- document.body,
- '.o_Message',
- 30,
- "there should be 30 messages that are loaded in Inbox"
- );
await afterNextRender(async () => {
const markAllReadButton = document.querySelector('.o_widget_Discuss_controlPanelButtonMarkAllRead');
@@ -4035,21 +4040,17 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
`).click();
},
message: "should wait until history scrolled to its last message after opening it from the discuss sidebar",
- predicate: ({ scrollTop, threadViewer }) => {
+ predicate: ({ orderedMessages, scrollTop, thread }) => {
const messageList = document.querySelector('.o_MessageList');
return (
- threadViewer.thread.model === 'mail.box' &&
- threadViewer.thread.id === 'history' &&
+ thread &&
+ thread.model === 'mail.box' &&
+ thread.id === 'history' &&
+ orderedMessages.length === 30 &&
scrollTop === messageList.scrollHeight - messageList.clientHeight
);
},
});
- assert.containsN(
- document.body,
- '.o_Message',
- 30,
- "there should be 30 messages in History"
- );
// simulate a scroll to top to load more messages
await this.afterEvent({
diff --git a/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js b/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js
index d2fa4aa936a..b78faa67659 100644
--- a/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js
+++ b/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class DiscussMobileMailboxSelection extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
return {
allOrderedAndPinnedMailboxes: this.orderedMailboxes.map(mailbox => mailbox.__state),
diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js
index 0d567df080b..09b512aaad7 100644
--- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js
+++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js
@@ -5,6 +5,7 @@ const components = {
AutocompleteInput: require('mail/static/src/components/autocomplete_input/autocomplete_input.js'),
DiscussSidebarItem: require('mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -18,9 +19,10 @@ class DiscussSidebar extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(
(...args) => this._useStoreSelector(...args),
- { compareDepth: () => this._useStoreCompareDepth() }
+ { compareDepth: this._useStoreCompareDepth() }
);
useUpdate({ func: () => this._update() });
/**
@@ -150,7 +152,6 @@ class DiscussSidebar extends Component {
allOrderedAndPinnedChats: 1,
allOrderedAndPinnedMailboxes: 1,
allOrderedAndPinnedMultiUserChannels: 1,
- allPinnedChannelAmount: 1,
};
}
@@ -160,33 +161,20 @@ class DiscussSidebar extends Component {
* @returns {Object}
*/
_useStoreSelector(props) {
+ const discuss = this.env.messaging.discuss;
return {
- allOrderedAndPinnedChats:
- this.env.models['mail.thread']
- .all(thread =>
- thread.channel_type === 'chat' &&
- thread.isPinned &&
- thread.model === 'mail.channel'
- )
- .sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1)
- .map(chat => chat.__state),
- allOrderedAndPinnedMailboxes: this.orderedMailboxes.map(mailbox => mailbox.__state),
- allOrderedAndPinnedMultiUserChannels:
- this.env.models['mail.thread']
- .all(thread =>
- thread.channel_type === 'channel' &&
- thread.isPinned &&
- thread.model === 'mail.channel'
- )
- .sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1)
- .map(channel => channel.__state),
+ allOrderedAndPinnedChats: this.quickSearchPinnedAndOrderedChats,
+ allOrderedAndPinnedMailboxes: this.orderedMailboxes,
+ allOrderedAndPinnedMultiUserChannels: this.quickSearchOrderedAndPinnedMultiUserChannels,
allPinnedChannelAmount:
this.env.models['mail.thread']
.all(thread =>
thread.isPinned &&
thread.model === 'mail.channel'
).length,
- discuss: this.env.messaging.discuss.__state,
+ discussIsAddingChannel: discuss && discuss.isAddingChannel,
+ discussIsAddingChat: discuss && discuss.isAddingChat,
+ discussSidebarQuickSearchValue: discuss && discuss.sidebarQuickSearchValue,
};
}
diff --git a/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js b/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
index e513914d2f9..0226035c2ba 100644
--- a/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
+++ b/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
@@ -5,6 +5,7 @@ const components = {
EditableText: require('mail/static/src/components/editable_text/editable_text.js'),
ThreadIcon: require('mail/static/src/components/thread_icon/thread_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { isEventHandled } = require('mail/static/src/utils/utils.js');
@@ -19,13 +20,25 @@ class DiscussSidebarItem extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
+ const discuss = this.env.messaging.discuss;
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
const correspondent = thread ? thread.correspondent : undefined;
return {
- correspondent: correspondent ? correspondent.__state : undefined,
- discuss: this.env.messaging.discuss.__state,
- thread: thread ? thread.__state : undefined,
+ correspondentName: correspondent && correspondent.name,
+ discussIsRenamingThread: discuss && discuss.renamingThreads.includes(thread),
+ isDiscussThread: discuss && discuss.thread === thread,
+ starred: this.env.messaging.starred,
+ thread,
+ threadChannelType: thread && thread.channel_type,
+ threadCounter: thread && thread.counter,
+ threadDisplayName: thread && thread.displayName,
+ threadGroupBasedSubscription: thread && thread.group_based_subscription,
+ threadLocalMessageUnreadCounter: thread && thread.localMessageUnreadCounter,
+ threadMassMailing: thread && thread.mass_mailing,
+ threadMessageNeedactionCounter: thread && thread.message_needaction_counter,
+ threadModel: thread && thread.model,
};
});
}
diff --git a/addons/mail/static/src/components/drop_zone/drop_zone.js b/addons/mail/static/src/components/drop_zone/drop_zone.js
index 2eb4dc8ef74..dcbb701994c 100644
--- a/addons/mail/static/src/components/drop_zone/drop_zone.js
+++ b/addons/mail/static/src/components/drop_zone/drop_zone.js
@@ -1,6 +1,8 @@
odoo.define('mail/static/src/components/drop_zone/drop_zone.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+
const { Component, useState } = owl;
class DropZone extends Component {
@@ -10,6 +12,7 @@ class DropZone extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
this.state = useState({
/**
* Determine whether the user is dragging files over the dropzone.
diff --git a/addons/mail/static/src/components/editable_text/editable_text.js b/addons/mail/static/src/components/editable_text/editable_text.js
index 6e0b682fc53..be7e7ddc506 100644
--- a/addons/mail/static/src/components/editable_text/editable_text.js
+++ b/addons/mail/static/src/components/editable_text/editable_text.js
@@ -2,11 +2,17 @@ odoo.define('mail/static/src/components/editable_text/editable_text.js', functio
'use strict';
const { markEventHandled } = require('mail/static/src/utils/utils.js');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const { Component } = owl;
class EditableText extends Component {
+ constructor(...args) {
+ super(...args);
+ useShouldUpdateBasedOnProps();
+ }
+
mounted() {
this.el.focus();
this.el.setSelectionRange(0, (this.el.value && this.el.value.length) || 0);
diff --git a/addons/mail/static/src/components/emojis_popover/emojis_popover.js b/addons/mail/static/src/components/emojis_popover/emojis_popover.js
index 9bda844a030..a312eed40c7 100644
--- a/addons/mail/static/src/components/emojis_popover/emojis_popover.js
+++ b/addons/mail/static/src/components/emojis_popover/emojis_popover.js
@@ -2,6 +2,7 @@ odoo.define('mail/static/src/components/emojis_popover/emojis_popover.js', funct
'use strict';
const emojis = require('mail.emojis');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
const { Component } = owl;
@@ -14,6 +15,7 @@ class EmojisPopover extends Component {
constructor(...args) {
super(...args);
this.emojis = emojis;
+ useShouldUpdateBasedOnProps();
useUpdate({ func: () => this._update() });
}
diff --git a/addons/mail/static/src/components/file_uploader/file_uploader.js b/addons/mail/static/src/components/file_uploader/file_uploader.js
index bb8c6c5b460..7907d81638b 100644
--- a/addons/mail/static/src/components/file_uploader/file_uploader.js
+++ b/addons/mail/static/src/components/file_uploader/file_uploader.js
@@ -1,6 +1,8 @@
odoo.define('mail/static/src/components/file_uploader/file_uploader.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+
const core = require('web.core');
const { Component } = owl;
@@ -16,6 +18,12 @@ class FileUploader extends Component {
this._fileInputRef = useRef('fileInput');
this._fileUploadId = _.uniqueId('o_FileUploader_fileupload');
this._onAttachmentUploaded = this._onAttachmentUploaded.bind(this);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ attachmentLocalIds: 1,
+ newAttachmentExtraData: 3,
+ },
+ });
}
mounted() {
diff --git a/addons/mail/static/src/components/follow_button/follow_button.js b/addons/mail/static/src/components/follow_button/follow_button.js
index b1639567c14..3c1808cb316 100644
--- a/addons/mail/static/src/components/follow_button/follow_button.js
+++ b/addons/mail/static/src/components/follow_button/follow_button.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/follow_button/follow_button.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class FollowButton extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
this.state = useState({
/**
* Determine whether the unfollow button is highlighted or not.
@@ -21,7 +23,7 @@ class FollowButton extends Component {
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
return {
- thread: thread ? thread.__state : undefined,
+ threadIsCurrentPartnerFollowing: thread && thread.isCurrentPartnerFollowing,
};
});
}
diff --git a/addons/mail/static/src/components/follower/follower.js b/addons/mail/static/src/components/follower/follower.js
index dc3e395f03c..bafcd88afc9 100644
--- a/addons/mail/static/src/components/follower/follower.js
+++ b/addons/mail/static/src/components/follower/follower.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower/follower.js', function (require
const components = {
FollowerSubtypeList: require('mail/static/src/components/follower_subtype_list/follower_subtype_list.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,6 +16,7 @@ class Follower extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const follower = this.env.models['mail.follower'].get(props.followerLocalId);
return [follower ? follower.__state : undefined];
diff --git a/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js b/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js
index 54eac6e284c..996ef1f58a0 100644
--- a/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js
+++ b/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower_list_menu/follower_list_menu.js
const components = {
Follower: require('mail/static/src/components/follower/follower.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,6 +16,7 @@ class FollowerListMenu extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
this.state = useState({
/**
* Determine whether the dropdown is open or not.
@@ -25,8 +27,8 @@ class FollowerListMenu extends Component {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
const followers = thread ? thread.followers : [];
return {
- followers: followers.map(follower => follower.__state),
- thread: thread ? thread.__state : undefined,
+ followers,
+ threadChannelType: thread && thread.channel_type,
};
}, {
compareDepth: {
diff --git a/addons/mail/static/src/components/follower_subtype/follower_subtype.js b/addons/mail/static/src/components/follower_subtype/follower_subtype.js
index 8ff47ada34f..ae3ba3211e1 100644
--- a/addons/mail/static/src/components/follower_subtype/follower_subtype.js
+++ b/addons/mail/static/src/components/follower_subtype/follower_subtype.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/follower_subtype/follower_subtype.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class FollowerSubtype extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const followerSubtype = this.env.models['mail.follower_subtype'].get(props.followerSubtypeLocalId);
return [followerSubtype ? followerSubtype.__state : undefined];
diff --git a/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js b/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js
index 851382861ea..d5cca5b5afe 100644
--- a/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js
+++ b/addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower_subtype_list/follower_subtype_l
const components = {
FollowerSubtype: require('mail/static/src/components/follower_subtype/follower_subtype.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component, QWeb } = owl;
@@ -15,6 +16,7 @@ class FollowerSubtypeList extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const followerSubtypeList = this.env.models['mail.follower_subtype_list'].get(props.localId);
const follower = followerSubtypeList
diff --git a/addons/mail/static/src/components/mail_template/mail_template.js b/addons/mail/static/src/components/mail_template/mail_template.js
index 45dc8c344c6..32c334bebb6 100644
--- a/addons/mail/static/src/components/mail_template/mail_template.js
+++ b/addons/mail/static/src/components/mail_template/mail_template.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/mail_template/mail_template.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class MailTemplate extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const activity = this.env.models['mail.activity'].get(props.activityLocalId);
const mailTemplate = this.env.models['mail.mail_template'].get(props.mailTemplateLocalId);
diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js
index d7800cbfc56..6d6e64e2b18 100644
--- a/addons/mail/static/src/components/message/message.js
+++ b/addons/mail/static/src/components/message/message.js
@@ -10,6 +10,7 @@ const components = {
NotificationPopover: require('mail/static/src/components/notification_popover/notification_popover.js'),
PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -43,6 +44,7 @@ class Message extends Component {
*/
isClicked: false,
});
+ useShouldUpdateBasedOnProps();
useStore(props => {
const message = this.env.models['mail.message'].get(props.messageLocalId);
const author = message ? message.author : undefined;
@@ -50,31 +52,37 @@ class Message extends Component {
const originThread = message ? message.originThread : undefined;
const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId);
const thread = threadView ? threadView.thread : undefined;
- const threadStringifiedDomain = threadView
- ? threadView.stringifiedDomain
- : undefined;
return {
attachments: message
? message.attachments.map(attachment => attachment.__state)
- : undefined,
- author: author ? author.__state : undefined,
+ : [],
+ author,
+ authorAvatarUrl: author && author.avatarUrl,
+ authorImStatus: author && author.im_status,
+ authorNameOrDisplayName: author && author.nameOrDisplayName,
+ correspondent: thread && thread.correspondent,
hasMessageCheckbox: message ? message.hasCheckbox : false,
isDeviceMobile: this.env.messaging.device.isMobile,
isMessageChecked: message && threadView
- ? message.isChecked(thread, threadStringifiedDomain)
+ ? message.isChecked(thread, threadView.stringifiedDomain)
: false,
isMessageSelected: message && threadView && threadView.threadViewer
? threadView.threadViewer.selectedMessage === message
: false,
message: message ? message.__state : undefined,
notifications: message ? message.notifications.map(notif => notif.__state) : [],
- originThread: originThread ? originThread.__state : undefined,
- partnerRoot: partnerRoot ? partnerRoot.__state : undefined,
- thread: thread ? thread.__state : undefined,
- threadView: threadView ? threadView.__state : undefined,
+ originThread,
+ originThreadModel: originThread && originThread.model,
+ originThreadName: originThread && originThread.name,
+ originThreadUrl: originThread && originThread.url,
+ partnerRoot,
+ thread,
+ threadHasSeenIndicators: thread && thread.hasSeenIndicators,
+ threadMassMailing: thread && thread.mass_mailing,
};
}, {
compareDepth: {
+ attachments: 1,
notifications: 1,
},
});
diff --git a/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js b/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js
index 07fb1d054f0..21fb18a5223 100644
--- a/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js
+++ b/addons/mail/static/src/components/message_author_prefix/message_author_prefix.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/message_author_prefix/message_author_prefix.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class MessageAuthorPrefix extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const message = this.env.models['mail.message'].get(props.messageLocalId);
const author = message ? message.author : undefined;
diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js
index 4c3c9408421..75c71cc43f1 100644
--- a/addons/mail/static/src/components/message_list/message_list.js
+++ b/addons/mail/static/src/components/message_list/message_list.js
@@ -6,6 +6,7 @@ const components = {
};
const useRefs = require('mail/static/src/component_hooks/use_refs/use_refs.js');
const useRenderedValues = require('mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -19,22 +20,30 @@ class MessageList extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId);
const thread = threadView ? threadView.thread : undefined;
const threadCache = threadView ? threadView.threadCache : undefined;
return {
isDeviceMobile: this.env.messaging.device.isMobile,
- messages: threadCache
- ? threadCache.orderedMessages.map(message => message.__state)
- : [],
- thread: thread ? thread.__state : undefined,
- threadCache: threadCache ? threadCache.__state : undefined,
- threadView: threadView ? threadView.__state : undefined,
+ thread,
+ threadCache,
+ threadCacheIsAllHistoryLoaded: threadCache && threadCache.isAllHistoryLoaded,
+ threadCacheIsLoaded: threadCache && threadCache.isLoaded,
+ threadCacheIsLoadingMore: threadCache && threadCache.isLoadingMore,
+ threadCacheLastMessage: threadCache && threadCache.lastMessage,
+ threadCacheOrderedMessages: threadCache ? threadCache.orderedMessages : [],
+ threadIsTemporary: thread && thread.isTemporary,
+ threadMainCache: thread && thread.mainCache,
+ threadMessageAfterNewMessageSeparator: thread && thread.messageAfterNewMessageSeparator,
+ threadViewComponentHintList: threadView ? threadView.componentHintList : [],
+ threadViewNonEmptyMessagesLength: threadView && threadView.nonEmptyMessages.length,
};
}, {
compareDepth: {
- messages: 1,
+ threadCacheOrderedMessages: 1,
+ threadViewComponentHintList: 1,
},
});
this._getRefs = useRefs();
@@ -63,13 +72,16 @@ class MessageList extends Component {
this._lastRenderedValues = useRenderedValues(() => {
const threadView = this.threadView;
const thread = threadView && threadView.thread;
+ const threadCache = threadView && threadView.threadCache;
return {
componentHintList: threadView ? [...threadView.componentHintList] : [],
hasAutoScrollOnMessageReceived: threadView && threadView.hasAutoScrollOnMessageReceived,
hasScrollAdjust: this.props.hasScrollAdjust,
mainCache: thread && thread.mainCache,
order: this.props.order,
- threadCache: threadView && threadView.threadCache,
+ orderedMessages: threadCache ? [...threadCache.orderedMessages] : [],
+ thread,
+ threadCache,
threadCacheInitialScrollHeight: threadView && threadView.threadCacheInitialScrollHeight,
threadCacheInitialScrollPosition: threadView && threadView.threadCacheInitialScrollPosition,
threadView,
@@ -493,6 +505,8 @@ class MessageList extends Component {
_onScrollThrottled(ev) {
const {
order,
+ orderedMessages,
+ thread,
threadCache,
threadView,
threadViewer,
@@ -503,7 +517,9 @@ class MessageList extends Component {
}
const scrollTop = this.el.scrollTop;
this.env.messagingBus.trigger('o-component-message-list-scrolled', {
+ orderedMessages,
scrollTop,
+ thread,
threadViewer,
});
if (!this._isLastScrollProgrammatic && threadView && threadView.exists()) {
diff --git a/addons/mail/static/src/components/message_list/message_list.xml b/addons/mail/static/src/components/message_list/message_list.xml
index 97915c5ce56..6d3730fdcea 100644
--- a/addons/mail/static/src/components/message_list/message_list.xml
+++ b/addons/mail/static/src/components/message_list/message_list.xml
@@ -37,7 +37,7 @@
-
+
@@ -75,7 +75,7 @@
-
+
diff --git a/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js b/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js
index 9a3d9172cd1..ed555b0c5a4 100644
--- a/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js
+++ b/addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/message_seen_indicator/message_seen_indicator.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class MessageSeenIndicator extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const message = this.env.models['mail.message'].get(props.messageLocalId);
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
diff --git a/addons/mail/static/src/components/messaging_menu/messaging_menu.js b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
index 89cb6b1fb7c..9eb7fd71a49 100644
--- a/addons/mail/static/src/components/messaging_menu/messaging_menu.js
+++ b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
@@ -6,6 +6,7 @@ const components = {
MobileMessagingNavbar: require('mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js'),
NotificationList: require('mail/static/src/components/notification_list/notification_list.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const patchMixin = require('web.patchMixin');
@@ -25,6 +26,7 @@ class MessagingMenu extends Component {
* item is not considered as a click away from messaging menu in mobile.
*/
this.id = _.uniqueId('o_messagingMenu_');
+ useShouldUpdateBasedOnProps();
useStore(props => {
return {
isDeviceMobile: this.env.messaging && this.env.messaging.device.isMobile,
diff --git a/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js b/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js
index 923480b3f73..45b53e87233 100644
--- a/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js
+++ b/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js
@@ -1,10 +1,21 @@
odoo.define('mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+
const { Component } = owl;
class MobileMessagingNavbar extends Component {
+ constructor(...args) {
+ super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ tabs: 2,
+ },
+ });
+ }
+
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
diff --git a/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js b/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js
index c7a5ed71a65..c96bd902800 100644
--- a/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js
+++ b/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const components = {
@@ -17,6 +18,11 @@ class ModerationBanDialog extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ messageLocalIds: 1,
+ },
+ });
useStore(props => {
const messages = props.messageLocalIds.map(localId =>
this.env.models['mail.message'].get(localId)
diff --git a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js b/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js
index 87e4ec84f94..4c4446837c2 100644
--- a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js
+++ b/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const components = {
@@ -17,6 +18,11 @@ class ModerationDiscardDialog extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ messageLocalIds: 1,
+ },
+ });
useStore(props => {
const messages = props.messageLocalIds.map(localId =>
this.env.models['mail.message'].get(localId)
diff --git a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js b/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js
index e488aea4ea7..44b82bc6526 100644
--- a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js
+++ b/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const components = {
@@ -17,6 +18,11 @@ class ModerationRejectDialog extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ messageLocalIds: 1,
+ },
+ });
this.state = useState({
title: this.env._t("Message Rejected"),
comment: this.env._t("Your message was rejected by moderator."),
diff --git a/addons/mail/static/src/components/notification_alert/notification_alert.js b/addons/mail/static/src/components/notification_alert/notification_alert.js
index 6e582fcdc6b..7ef9e3b12db 100644
--- a/addons/mail/static/src/components/notification_alert/notification_alert.js
+++ b/addons/mail/static/src/components/notification_alert/notification_alert.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/notification_alert/notification_alert.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,6 +13,7 @@ class NotificationAlert extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const isMessagingInitialized = this.env.isMessagingInitialized();
return {
diff --git a/addons/mail/static/src/components/notification_group/notification_group.js b/addons/mail/static/src/components/notification_group/notification_group.js
index 65cfcea3076..17936986574 100644
--- a/addons/mail/static/src/components/notification_group/notification_group.js
+++ b/addons/mail/static/src/components/notification_group/notification_group.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/notification_group/notification_group.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -13,6 +14,7 @@ class NotificationGroup extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const group = this.env.models['mail.notification_group'].get(props.notificationGroupLocalId);
return {
diff --git a/addons/mail/static/src/components/notification_list/notification_list.js b/addons/mail/static/src/components/notification_list/notification_list.js
index ea256c13963..33737ba4c4b 100644
--- a/addons/mail/static/src/components/notification_list/notification_list.js
+++ b/addons/mail/static/src/components/notification_list/notification_list.js
@@ -7,6 +7,7 @@ const components = {
ThreadNeedactionPreview: require('mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js'),
ThreadPreview: require('mail/static/src/components/thread_preview/thread_preview.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -18,6 +19,7 @@ class NotificationList extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
this.storeProps = useStore((...args) => this._useStoreSelector(...args), {
compareDepth: {
// list + notification object created in useStore
diff --git a/addons/mail/static/src/components/notification_popover/notification_popover.js b/addons/mail/static/src/components/notification_popover/notification_popover.js
index 0f3fce77a58..6be3647eada 100644
--- a/addons/mail/static/src/components/notification_popover/notification_popover.js
+++ b/addons/mail/static/src/components/notification_popover/notification_popover.js
@@ -2,6 +2,7 @@ odoo.define('mail/static/src/components/notification_popover/notification_popove
'use strict';
const { Component } = owl;
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
class NotificationPopover extends Component {
@@ -11,6 +12,11 @@ class NotificationPopover extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps({
+ compareDepth: {
+ notificationLocalIds: 1,
+ },
+ });
useStore(props => {
const notifications = props.notificationLocalIds.map(
notificationLocalId => this.env.models['mail.notification'].get(notificationLocalId)
diff --git a/addons/mail/static/src/components/notification_request/notification_request.js b/addons/mail/static/src/components/notification_request/notification_request.js
index 6f1f085813c..2d4b0101a92 100644
--- a/addons/mail/static/src/components/notification_request/notification_request.js
+++ b/addons/mail/static/src/components/notification_request/notification_request.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/notification_request/notification_reques
const components = {
PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,6 +16,7 @@ class NotificationRequest extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
return {
isDeviceMobile: this.env.messaging.device.isMobile,
diff --git a/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js b/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js
index 68fbab4f7c9..e4af9da6a04 100644
--- a/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js
+++ b/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js
@@ -1,6 +1,7 @@
odoo.define('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -12,13 +13,13 @@ class PartnerImStatusIcon extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const partner = this.env.models['mail.partner'].get(props.partnerLocalId);
return {
- partner: partner ? partner.__state : undefined,
- partnerRoot: this.env.messaging.partnerRoot
- ? this.env.messaging.partnerRoot.__state
- : undefined,
+ partner,
+ partnerImStatus: partner && partner.im_status,
+ partnerRoot: this.env.messaging.partnerRoot,
};
});
}
diff --git a/addons/mail/static/src/components/thread_icon/thread_icon.js b/addons/mail/static/src/components/thread_icon/thread_icon.js
index 0b82d0c9510..71017ec0e55 100644
--- a/addons/mail/static/src/components/thread_icon/thread_icon.js
+++ b/addons/mail/static/src/components/thread_icon/thread_icon.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/thread_icon/thread_icon.js', function (r
const components = {
ThreadTypingIcon: require('mail/static/src/components/thread_typing_icon/thread_typing_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,15 +16,24 @@ class ThreadIcon extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
const correspondent = thread ? thread.correspondent : undefined;
return {
- correspondent: correspondent ? correspondent.__state : undefined,
- partnerRoot: this.env.messaging.partnerRoot
- ? this.env.messaging.partnerRoot.__state
- : undefined,
- thread: thread ? thread.__state : undefined,
+ correspondent,
+ correspondentImStatus: correspondent && correspondent.im_status,
+ history: this.env.messaging.history,
+ inbox: this.env.messaging.inbox,
+ moderation: this.env.messaging.moderation,
+ partnerRoot: this.env.messaging.partnerRoot,
+ starred: this.env.messaging.starred,
+ thread,
+ threadChannelType: thread && thread.channel_type,
+ threadModel: thread && thread.model,
+ threadOrderedOtherTypingMembersLength: thread && thread.orderedOtherTypingMembers.length,
+ threadPublic: thread && thread.public,
+ threadTypingStatusText: thread && thread.typingStatusText,
};
});
}
diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
index c313a1dd2c1..d6e30f5a6ff 100644
--- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
+++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
@@ -5,6 +5,7 @@ const components = {
MessageAuthorPrefix: require('mail/static/src/components/message_author_prefix/message_author_prefix.js'),
PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const mailUtils = require('mail.utils');
@@ -18,6 +19,7 @@ class ThreadNeedactionPreview extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
const mainThreadCache = thread ? thread.mainCache : undefined;
diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.js b/addons/mail/static/src/components/thread_preview/thread_preview.js
index 92fcb6101d1..94df29e0e67 100644
--- a/addons/mail/static/src/components/thread_preview/thread_preview.js
+++ b/addons/mail/static/src/components/thread_preview/thread_preview.js
@@ -5,6 +5,7 @@ const components = {
MessageAuthorPrefix: require('mail/static/src/components/message_author_prefix/message_author_prefix.js'),
PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const mailUtils = require('mail.utils');
@@ -18,6 +19,7 @@ class ThreadPreview extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
let lastMessageAuthor;
diff --git a/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js b/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js
index 94f8c2b95bb..f053abc7fb2 100644
--- a/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js
+++ b/addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/thread_textual_typing_status/thread_text
const components = {
ThreadTypingIcon: require('mail/static/src/components/thread_typing_icon/thread_typing_icon.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const { Component } = owl;
@@ -15,10 +16,12 @@ class ThreadTextualTypingStatus extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore(props => {
const thread = this.env.models['mail.thread'].get(props.threadLocalId);
return {
- thread: thread ? thread.__state : undefined,
+ threadOrderedOtherTypingMembersLength: thread && thread.orderedOtherTypingMembersLength,
+ threadTypingStatusText: thread && thread.typingStatusText,
};
});
}
diff --git a/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js b/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js
index 95caee984d4..4c94a749fdd 100644
--- a/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js
+++ b/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js
@@ -1,9 +1,18 @@
odoo.define('mail/static/src/components/thread_typing_icon/thread_typing_icon.js', function (require) {
'use strict';
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+
const { Component } = owl;
-class ThreadTypingIcon extends Component {}
+class ThreadTypingIcon extends Component {
+
+ constructor(...args) {
+ super(...args);
+ useShouldUpdateBasedOnProps();
+ }
+
+}
Object.assign(ThreadTypingIcon, {
defaultProps: {
diff --git a/addons/mail/static/src/components/thread_view/thread_view.js b/addons/mail/static/src/components/thread_view/thread_view.js
index 89334c7d6c3..e4795bc0d45 100644
--- a/addons/mail/static/src/components/thread_view/thread_view.js
+++ b/addons/mail/static/src/components/thread_view/thread_view.js
@@ -5,6 +5,7 @@ const components = {
Composer: require('mail/static/src/components/composer/composer.js'),
MessageList: require('mail/static/src/components/message_list/message_list.js'),
};
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
@@ -18,6 +19,7 @@ class ThreadView extends Component {
*/
constructor(...args) {
super(...args);
+ useShouldUpdateBasedOnProps();
useStore((...args) => this._useStoreSelector(...args));
useUpdate({ func: () => this._update() });
/**
@@ -121,11 +123,18 @@ class ThreadView extends Component {
const threadView = this.env.models['mail.thread_view'].get(props.threadViewLocalId);
const thread = threadView ? threadView.thread : undefined;
const threadCache = threadView ? threadView.threadCache : undefined;
+ const correspondent = thread && thread.correspondent;
return {
+ composer: thread && thread.composer,
+ correspondentId: correspondent && correspondent.id,
isDeviceMobile: this.env.messaging.device.isMobile,
- thread: thread ? thread.__state : undefined,
- threadCache: threadCache ? threadCache.__state : undefined,
- threadView: threadView ? threadView.__state : undefined,
+ thread,
+ threadCacheIsLoaded: threadCache && threadCache.isLoaded,
+ threadIsTemporary: thread && thread.isTemporary,
+ threadMassMailing: thread && thread.mass_mailing,
+ threadModel: thread && thread.model,
+ threadView,
+ threadViewIsLoading: threadView && threadView.isLoading,
};
}
@@ -140,6 +149,7 @@ Object.assign(ThreadView, {
hasSquashCloseMessages: false,
haveMessagesMarkAsReadIcon: false,
haveMessagesReplyIcon: false,
+ isDoFocus: false,
order: 'asc',
showComposerAttachmentsExtensions: true,
showComposerAttachmentsFilenames: true,
@@ -175,6 +185,10 @@ Object.assign(ThreadView, {
hasSquashCloseMessages: Boolean,
haveMessagesMarkAsReadIcon: Boolean,
haveMessagesReplyIcon: Boolean,
+ /**
+ * Determines whether this should become focused.
+ */
+ isDoFocus: Boolean,
order: {
type: String,
validate: prop => ['asc', 'desc'].includes(prop),
diff --git a/addons/mail/static/src/components/thread_view/thread_view.xml b/addons/mail/static/src/components/thread_view/thread_view.xml
index 681920ae952..67234d77eb9 100644
--- a/addons/mail/static/src/components/thread_view/thread_view.xml
+++ b/addons/mail/static/src/components/thread_view/thread_view.xml
@@ -34,6 +34,7 @@
hasSendButton="props.hasComposerSendButton"
hasThreadTyping="props.hasComposerThreadTyping"
isCompact="(threadView.thread.model === 'mail.channel' and threadView.thread.mass_mailing) ? false : undefined"
+ isDoFocus="props.isDoFocus"
showAttachmentsExtensions="props.showComposerAttachmentsExtensions"
showAttachmentsFilenames="props.showComposerAttachmentsFilenames"
textInputSendShortcuts="(threadView.thread.model === 'mail.channel' and threadView.thread.mass_mailing) ? ['ctrl-enter', 'meta-enter'] : ['enter']"
diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js b/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js
index 19f5381be37..90cdb1693b3 100644
--- a/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js
+++ b/addons/mail/static/src/widgets/form_renderer/form_renderer_tests.js
@@ -462,24 +462,16 @@ QUnit.test('basic chatter rendering without messages', async function (assert) {
});
QUnit.test('chatter updating', async function (assert) {
- assert.expect(3);
+ assert.expect(1);
this.data['mail.message'].records.push({ body: "not empty", model: 'res.partner', res_id: 12 });
this.data['res.partner'].records.push(
{ display_name: "first partner", id: 11 },
{ display_name: "second partner", id: 12 }
);
- const messageFetchChannelDef = makeDeferred();
await this.createView({
data: this.data,
hasView: true,
- async mockRPC(route, args) {
- const res = await this._super(...arguments);
- if (route.includes('message_fetch')) {
- messageFetchChannelDef.resolve();
- }
- return res;
- },
// View params
View: FormView,
model: 'res.partner',
@@ -498,27 +490,35 @@ QUnit.test('chatter updating', async function (assert) {
`,
+ waitUntilEvent: {
+ eventName: 'o-thread-view-hint-processed',
+ message: "should wait until partner 11 thread loaded messages initially",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'res.partner' &&
+ threadViewer.thread.id === 11
+ );
+ },
+ }
});
- assert.containsOnce(
- document.body,
- '.o_Chatter',
- "there should be a chatter"
- );
- assert.containsNone(
- document.body,
- '.o_Message',
- "there should be no message"
- );
- await afterNextRender(async () => {
- document.querySelector('.o_pager_next').click();
- // wait until messages are fetched, ignore other renders that are too early
- await messageFetchChannelDef;
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => document.querySelector('.o_pager_next').click(),
+ message: "should wait until partner 12 thread loaded messages after clicking on next",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'res.partner' &&
+ threadViewer.thread.id === 12
+ );
+ },
});
assert.containsOnce(
document.body,
'.o_Message',
- "there should be a message"
+ "there should be a message in partner 12 thread"
);
});
diff --git a/addons/mail/views/assets.xml b/addons/mail/views/assets.xml
index ae43f537318..28d6c1d734a 100644
--- a/addons/mail/views/assets.xml
+++ b/addons/mail/views/assets.xml
@@ -59,6 +59,7 @@
+
diff --git a/addons/website_livechat/static/src/components/discuss/discuss.js b/addons/website_livechat/static/src/components/discuss/discuss.js
index 18b41cea432..778b1513730 100644
--- a/addons/website_livechat/static/src/components/discuss/discuss.js
+++ b/addons/website_livechat/static/src/components/discuss/discuss.js
@@ -6,6 +6,24 @@ const components = {
VisitorBanner: require('website_livechat/static/src/components/visitor_banner/visitor_banner.js'),
};
+components.Discuss.patch('website_livechat/static/src/components/discuss/discuss.js', T =>
+ class extends T {
+
+ /**
+ * @override
+ */
+ _useStoreSelector(props) {
+ const res = super._useStoreSelector(...arguments);
+ const thread = res.thread;
+ const visitor = thread && thread.visitor;
+ return Object.assign({}, res, {
+ visitor,
+ });
+ }
+
+ }
+);
+
Object.assign(components.Discuss.components, {
VisitorBanner: components.VisitorBanner,
});
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
index 0b39134dcfc..4d0b95a82d0 100644
--- a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
@@ -14,7 +14,9 @@ class VisitorBanner extends Component {
super(...args);
useStore(props => {
const visitor = this.env.models['website_livechat.visitor'].get(props.visitorLocalId);
+ const country = visitor && visitor.country;
return {
+ country: country && country.__state,
visitor: visitor ? visitor.__state : undefined,
};
});