[FIX] mail, *: significantly reduce number of useless renders
* = hr_holidays, im_livechat, website_livechat A huge amount of components where rendered for no reason, notably when typing a new message (at every key press), which significantly slowed down the interface. There were 2 causes to this number of renders: - OWL rendering all children automatically, even if their props didn't change, unless `shouldUpdate` is overridden, which is now done. - `useStore` selectors being way too generous in what they observed task-2399731 closes odoo/odoo#64958 X-original-commit: a0472425251d429a0828eff1675c84b3f24527d8 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
});
|
||||
|
||||
+72
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() });
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+40
-30
@@ -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
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
/**
|
||||
|
||||
@@ -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'));
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+7
-2
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
+7
-8
@@ -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: {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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({
|
||||
|
||||
+2
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() });
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</t>
|
||||
<!-- LOADING (if order asc)-->
|
||||
<t t-if="props.order === 'asc' and threadView.messages.length > 0">
|
||||
<t t-if="props.order === 'asc' and orderedMessages.length > 0">
|
||||
<t t-call="mail.MessageList.loadMore"/>
|
||||
</t>
|
||||
<!-- MESSAGES -->
|
||||
@@ -75,7 +75,7 @@
|
||||
</t>
|
||||
</t>
|
||||
<!-- LOADING (if order desc)-->
|
||||
<t t-if="props.order === 'desc' and threadView.messages.length > 0">
|
||||
<t t-if="props.order === 'desc' and orderedMessages.length > 0">
|
||||
<t t-call="mail.MessageList.loadMore"/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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)
|
||||
|
||||
+6
@@ -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)
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+4
-1
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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']"
|
||||
|
||||
@@ -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) {
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
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"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_refs/use_refs.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js"/>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js"/>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_store/use_store.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_update/use_update.js"/>
|
||||
<script type="text/javascript" src="/mail/static/src/components/activity/activity.js"></script>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user