summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components
diff options
context:
space:
mode:
authorXavier Dubuc <xdu@odoo.com>2020-09-03 16:24:19 +0000
committerSébastien Theys <seb@odoo.com>2020-09-04 14:59:01 +0000
commit8d3413ff647de8d98c6858062dd07dde4160af95 (patch)
tree3dc134e83c874d5c7f7bf739f13a88ab986ddb1b /addons/mail/static/src/components
parenta5ab687be4e7890262ce9429e18dd011021513d5 (diff)
[FIX] mail, sms, snailmail: prevent message_fetch for hidden threads
Automatically load messages from a thread cache only if the thread cache is used in at least one thread view. Ensure thread viewer don't keep thread view records alive when they are not displayed. This change is particularly important for performance, to avoid spamming RPC at init if many threads are opened in chat windows. task-2310623 closes odoo/odoo#57061 X-original-commit: dd6981c7812134e0588f80a157eaba45512cd68e Related: odoo/enterprise#12936 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Diffstat (limited to 'addons/mail/static/src/components')
-rw-r--r--addons/mail/static/src/components/chat_window/chat_window.js31
-rw-r--r--addons/mail/static/src/components/chat_window/chat_window.xml54
-rw-r--r--addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js116
-rw-r--r--addons/mail/static/src/components/chatter/chatter.xml33
-rw-r--r--addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml2
-rw-r--r--addons/mail/static/src/components/discuss/discuss.js15
-rw-r--r--addons/mail/static/src/components/discuss/discuss.xml2
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js8
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_tests.js86
-rw-r--r--addons/mail/static/src/components/message/message_tests.js86
-rw-r--r--addons/mail/static/src/components/message_list/message_list.js26
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view.xml4
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view_tests.js43
13 files changed, 341 insertions, 165 deletions
diff --git a/addons/mail/static/src/components/chat_window/chat_window.js b/addons/mail/static/src/components/chat_window/chat_window.js
index 43353c6086f..a5d0b2c9a20 100644
--- a/addons/mail/static/src/components/chat_window/chat_window.js
+++ b/addons/mail/static/src/components/chat_window/chat_window.js
@@ -115,12 +115,10 @@ class ChatWindow extends Component {
isDoFocus: false,
isFocused: true,
});
- if (this.chatWindow.isFolded) {
- return;
- }
- if (!this.chatWindow.thread) {
+ if (this._inputRef.comp) {
this._inputRef.comp.focus();
- } else {
+ }
+ if (this._threadRef.comp) {
this._threadRef.comp.focus();
}
}
@@ -134,7 +132,10 @@ class ChatWindow extends Component {
* @private
*/
_saveThreadScrollTop() {
- this.chatWindow.threadView.saveThreadCacheScrollPositionsAsInitial(
+ if (!this._threadRef.comp || !this.chatWindow.threadViewer) {
+ return;
+ }
+ this.chatWindow.threadViewer.saveThreadCacheScrollPositionsAsInitial(
this._threadRef.comp.getScrollTop()
);
}
@@ -248,9 +249,7 @@ class ChatWindow extends Component {
this.chatWindow.unfold();
this.chatWindow.focus();
} else {
- if (this.chatWindow.thread) {
- this._saveThreadScrollTop();
- }
+ this._saveThreadScrollTop();
this.chatWindow.fold();
}
}
@@ -336,12 +335,7 @@ class ChatWindow extends Component {
* @private
*/
async _onWillHideHomeMenu() {
- if (!this.chatWindow.thread) {
- return;
- }
- if (!this.chatWindow.isFolded) {
- this._saveThreadScrollTop();
- }
+ this._saveThreadScrollTop();
}
/**
@@ -353,12 +347,7 @@ class ChatWindow extends Component {
* @private
*/
async _onWillShowHomeMenu() {
- if (!this.chatWindow.thread) {
- return;
- }
- if (!this.chatWindow.isFolded) {
- this._saveThreadScrollTop();
- }
+ this._saveThreadScrollTop();
}
}
diff --git a/addons/mail/static/src/components/chat_window/chat_window.xml b/addons/mail/static/src/components/chat_window/chat_window.xml
index f92a32cf2fe..240cf0f974c 100644
--- a/addons/mail/static/src/components/chat_window/chat_window.xml
+++ b/addons/mail/static/src/components/chat_window/chat_window.xml
@@ -20,35 +20,33 @@
t-on-o-clicked="_onClickedHeader"
t-ref="header"
/>
- <t t-if="!chatWindow.isFolded">
- <t t-if="chatWindow.thread">
- <ThreadView
- class="o_ChatWindow_thread"
- composerAttachmentsDetailsMode="'card'"
- hasComposer="chatWindow.thread.model !== 'mail.box' and (!chatWindow.thread.mass_mailing or env.messaging.device.isMobile)"
- hasComposerCurrentPartnerAvatar="false"
- hasComposerSendButton="env.messaging.device.isMobile"
- hasSquashCloseMessages="chatWindow.thread.model !== 'mail.box'"
- threadViewLocalId="chatWindow.threadView.localId"
- t-on-focusin="_onFocusinThread"
- t-ref="thread"
+ <t t-if="chatWindow.threadView">
+ <ThreadView
+ class="o_ChatWindow_thread"
+ composerAttachmentsDetailsMode="'card'"
+ hasComposer="chatWindow.thread.model !== 'mail.box' and (!chatWindow.thread.mass_mailing or env.messaging.device.isMobile)"
+ hasComposerCurrentPartnerAvatar="false"
+ hasComposerSendButton="env.messaging.device.isMobile"
+ hasSquashCloseMessages="chatWindow.thread.model !== 'mail.box'"
+ threadViewLocalId="chatWindow.threadView.localId"
+ t-on-focusin="_onFocusinThread"
+ t-ref="thread"
+ />
+ </t>
+ <t t-if="chatWindow.hasNewMessageForm">
+ <div class="o_ChatWindow_newMessageForm">
+ <span class="o_ChatWindow_newMessageFormLabel">
+ To:
+ </span>
+ <AutocompleteInput
+ class="o_ChatWindow_newMessageFormInput"
+ isHtml="true"
+ placeholder="newMessageFormInputPlaceholder"
+ select="_onAutocompleteSelect"
+ source="_onAutocompleteSource"
+ t-ref="input"
/>
- </t>
- <t t-else="">
- <div class="o_ChatWindow_newMessageForm">
- <span class="o_ChatWindow_newMessageFormLabel">
- To:
- </span>
- <AutocompleteInput
- class="o_ChatWindow_newMessageFormInput"
- isHtml="true"
- placeholder="newMessageFormInputPlaceholder"
- select="_onAutocompleteSelect"
- source="_onAutocompleteSource"
- t-ref="input"
- />
- </div>
- </t>
+ </div>
</t>
</t>
</div>
diff --git a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
index efba64cecde..220f4278fa9 100644
--- a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
+++ b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
@@ -1753,6 +1753,122 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
);
});
+QUnit.test('chat window does not fetch messages if hidden', async function (assert) {
+ /**
+ * computation uses following info:
+ * ([mocked] global window width: 900px)
+ * (others: @see `mail/static/src/models/chat_window_manager/chat_window_manager.js:visual`)
+ *
+ * - chat window width: 325px
+ * - start/end/between gap width: 10px/10px/5px
+ * - hidden menu width: 200px
+ * - global width: 1080px
+ *
+ * Enough space for 2 visible chat windows, and one hidden chat window:
+ * 3 visible chat windows:
+ * 10 + 325 + 5 + 325 + 5 + 325 + 10 = 1000 > 900
+ * 2 visible chat windows + hidden menu:
+ * 10 + 325 + 5 + 325 + 10 + 200 + 5 = 875 < 900
+ */
+ assert.expect(14);
+
+ // 3 channels are expected to be found in the messaging menu, each with a
+ // random unique id that will be referenced in the test
+ this.data['mail.channel'].records = [
+ {
+ id: 10,
+ is_minimized: true,
+ name: "Channel #10",
+ state: 'open',
+ },
+ {
+ id: 11,
+ is_minimized: true,
+ name: "Channel #11",
+ state: 'open',
+ },
+ {
+ id: 12,
+ is_minimized: true,
+ name: "Channel #12",
+ state: 'open',
+ },
+ ];
+ await this.start({
+ env: {
+ browser: {
+ innerWidth: 900,
+ },
+ },
+ mockRPC(route, args) {
+ if (args.method === 'message_fetch') {
+ // domain should be like [['channel_id', 'in', [X]]] with X the channel id
+ const channel_ids = args.kwargs.domain[0][2];
+ assert.strictEqual(channel_ids.length, 1, "messages should be fetched channel per channel");
+ assert.step(`rpc:message_fetch:${channel_ids[0]}`);
+ }
+ return this._super(...arguments);
+ },
+ });
+
+ assert.containsN(
+ document.body,
+ '.o_ChatWindow',
+ 2,
+ "2 chat windows should be visible"
+ );
+ assert.containsNone(
+ document.body,
+ `.o_ChatWindow[data-thread-local-id="${
+ this.env.models['mail.thread'].find(t =>
+ t.model === 'mail.channel' && t.id === 12
+ ).localId
+ }"]`,
+ "chat window for Channel #12 should be hidden"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ChatWindowHiddenMenu',
+ "chat window hidden menu should be displayed"
+ );
+ assert.verifySteps(
+ ['rpc:message_fetch:10', 'rpc:message_fetch:11'],
+ "messages should be fetched for the two visible chat windows"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_ChatWindowHiddenMenu_dropdownToggle').click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ChatWindowHiddenMenu_chatWindowHeader',
+ "1 hidden chat window should be listed in hidden menu"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_ChatWindowHiddenMenu_chatWindowHeader').click()
+ );
+ assert.containsN(
+ document.body,
+ '.o_ChatWindow',
+ 2,
+ "2 chat windows should still be visible"
+ );
+ assert.containsOnce(
+ document.body,
+ `.o_ChatWindow[data-thread-local-id="${
+ this.env.models['mail.thread'].find(t =>
+ t.model === 'mail.channel' && t.id === 12
+ ).localId
+ }"]`,
+ "chat window for Channel #12 should now be visible"
+ );
+ assert.verifySteps(
+ ['rpc:message_fetch:12'],
+ "messages should now be fetched for Channel #12"
+ );
+});
+
});
});
});
diff --git a/addons/mail/static/src/components/chatter/chatter.xml b/addons/mail/static/src/components/chatter/chatter.xml
index e5601dd7974..53550da516d 100644
--- a/addons/mail/static/src/components/chatter/chatter.xml
+++ b/addons/mail/static/src/components/chatter/chatter.xml
@@ -8,7 +8,7 @@
class="o_Chatter_topbar"
chatterLocalId="chatter.localId"
/>
- <t t-if="chatter.hasThread and chatter.thread and chatter.isComposerVisible">
+ <t t-if="chatter.threadView and chatter.isComposerVisible">
<Composer
class="o_Chatter_composer"
t-att-class="{ 'o-bordered': chatter.hasExternalBorder }"
@@ -27,22 +27,21 @@
threadLocalId="chatter.thread.localId"
/>
</t>
- <t t-if="chatter.thread">
- <t t-if="chatter.hasActivities and chatter.activities.length > 0">
- <ActivityBox
- class="o_Chatter_activityBox"
- chatterLocalId="chatter.localId"
- />
- </t>
- <t t-if="chatter.hasThread">
- <ThreadView
- class="o_Chatter_thread"
- hasComposer="false"
- hasScrollAdjust="chatter.hasMessageListScrollAdjust"
- order="'desc'"
- threadViewLocalId="chatter.threadView.localId"
- />
- </t>
+ <t t-if="chatter.thread and chatter.hasActivities and chatter.activities.length > 0">
+ <ActivityBox
+ class="o_Chatter_activityBox"
+ chatterLocalId="chatter.localId"
+ />
+ </t>
+ <t t-if="chatter.threadView">
+ <ThreadView
+ class="o_Chatter_thread"
+ hasComposer="false"
+ hasScrollAdjust="chatter.hasMessageListScrollAdjust"
+ order="'desc'"
+ threadViewLocalId="chatter.threadView.localId"
+ t-ref="thread"
+ />
</t>
</t>
</div>
diff --git a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml
index 3512f93332e..6911dfb959e 100644
--- a/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml
+++ b/addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml
@@ -5,7 +5,7 @@
<div class="o_ChatterTopbar">
<t t-if="chatter">
<div class="o_ChatterTopbar_actions" t-att-class="{'o-has-active-button': chatter.isComposerVisible }">
- <t t-if="chatter.hasThread">
+ <t t-if="chatter.threadView">
<button class="btn btn-link o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage"
type="button"
t-att-class="{
diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js
index 4c8c4a58ab7..377fde0dd72 100644
--- a/addons/mail/static/src/components/discuss/discuss.js
+++ b/addons/mail/static/src/components/discuss/discuss.js
@@ -26,18 +26,17 @@ class Discuss extends Component {
super(...args);
useStore(props => {
const discuss = this.env.messaging && this.env.messaging.discuss;
+ const threadView = discuss && discuss.threadView;
return {
- checkedMessages: discuss ? discuss.threadView.checkedMessages.map(message => message.__state) : [],
+ 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: (discuss && discuss.threadView && discuss.threadView.threadCache)
- ? discuss.threadView.threadCache.__state
+ threadCache: (threadView && threadView.threadCache)
+ ? threadView.threadCache.__state
: undefined,
- uncheckedMessages: discuss && discuss.threadView
- ? discuss.threadView.uncheckedMessages.map(message => message.__state)
- : [],
+ uncheckedMessages: threadView ? threadView.uncheckedMessages.map(message => message.__state) : [],
};
}, {
compareDepth: {
@@ -78,12 +77,13 @@ class Discuss extends Component {
if (
this.discuss.thread &&
this.discuss.thread === this.env.messaging.inbox &&
+ this.discuss.threadView &&
this._lastThreadCache === this.discuss.threadView.threadCache.localId &&
this._lastThreadCounter > 0 && this.discuss.thread.counter === 0
) {
this.trigger('o-show-rainbow-man');
}
- this._activeThreadCache = this.discuss.threadView.threadCache;
+ this._activeThreadCache = this.discuss.threadView && this.discuss.threadView.threadCache;
this._updateLocalStoreProps();
this._update();
}
@@ -170,6 +170,7 @@ class Discuss extends Component {
* rainbox man on inbox.
*/
this._lastThreadCache = (
+ this.discuss.threadView &&
this.discuss.threadView.threadCache &&
this.discuss.threadView.threadCache.localId
);
diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml
index 79838422f81..39f09da0b5b 100644
--- a/addons/mail/static/src/components/discuss/discuss.xml
+++ b/addons/mail/static/src/components/discuss/discuss.xml
@@ -48,7 +48,7 @@
/>
</div>
</t>
- <t t-if="(env.messaging.device.isMobile and discuss.activeMobileNavbarTabId === 'mailbox' and discuss.thread and discuss.thread.model === 'mail.box') or (!env.messaging.device.isMobile and discuss.thread)">
+ <t t-if="discuss.threadView">
<ThreadView
class="o_Discuss_thread"
t-att-class="{ 'o-mobile': env.messaging.device.isMobile }"
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js
index bb780bad22d..ae6af0995b6 100644
--- a/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js
+++ b/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js
@@ -126,8 +126,8 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async
);
assert.containsOnce(
document.body,
- `.o_Discuss_thread[data-thread-local-id="${this.env.messaging.inbox.localId}"]`,
- "The Inbox is opened in discuss"
+ '.o_Discuss_noThread',
+ "should have no thread opened in discuss"
);
});
@@ -182,8 +182,8 @@ QUnit.test('sidebar: unpin channel from bus', async function (assert) {
});
assert.containsOnce(
document.body,
- `.o_Discuss_thread[data-thread-local-id="${this.env.messaging.inbox.localId}"]`,
- "The Inbox is opened in discuss"
+ '.o_Discuss_noThread',
+ "should have no thread opened in discuss"
);
assert.containsNone(
document.body,
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
index c12e47f02cf..87ad3640919 100644
--- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js
+++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
@@ -3,6 +3,7 @@ odoo.define('mail/static/src/components/discuss/tests/discuss_tests.js', functio
const BusService = require('bus.BusService');
+const { makeDeferred } = require('mail/static/src/utils/deferred/deferred.js');
const {
afterEach,
afterNextRender,
@@ -1115,7 +1116,7 @@ QUnit.test('default thread rendering', async function (assert) {
});
QUnit.test('initially load messages from inbox', async function (assert) {
- assert.expect(5);
+ assert.expect(4);
await this.start({
async mockRPC(route, args) {
@@ -1126,13 +1127,8 @@ QUnit.test('initially load messages from inbox', async function (assert) {
30,
"should fetch up to 30 messages"
);
- assert.strictEqual(
- args.args.length,
- 1,
- "should have a single item in args"
- );
assert.deepEqual(
- args.args[0],
+ args.kwargs.domain,
[["needaction", "=", true]],
"should fetch needaction messages"
);
@@ -1186,7 +1182,7 @@ QUnit.test('auto-select thread in discuss context', async function (assert) {
});
QUnit.test('load single message from channel initially', async function (assert) {
- assert.expect(8);
+ assert.expect(7);
// channel expected to be rendered, with a random unique id that will be referenced in the test
this.data['mail.channel'].records.push({ id: 20 });
@@ -1210,13 +1206,8 @@ QUnit.test('load single message from channel initially', async function (assert)
30,
"should fetch up to 30 messages"
);
- assert.strictEqual(
- args.args.length,
- 1,
- "should have a single item in args"
- );
assert.deepEqual(
- args.args[0],
+ args.kwargs.domain,
[["channel_ids", "in", [20]]],
"should fetch messages from channel"
);
@@ -1829,7 +1820,7 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
});
QUnit.test('restore thread scroll position', async function (assert) {
- assert.expect(4);
+ assert.expect(5);
// channels expected to be rendered, with random unique id that will be referenced in the test
this.data['mail.channel'].records.push({ id: 11 }, { id: 12 });
for (let i = 1; i <= 25; i++) {
@@ -1839,26 +1830,43 @@ QUnit.test('restore thread scroll position', async function (assert) {
res_id: 11,
});
}
- for (let i = 1; i <= 25; i++) {
+ for (let i = 1; i <= 24; i++) {
this.data['mail.message'].records.push({
channel_ids: [12],
model: 'mail.channel',
res_id: 12,
});
}
- await this.start({
- discuss: {
- params: {
- default_active_id: 'mail.channel_11',
+ const messageFetchChannel11Def = makeDeferred();
+ const messageFetchChannel12Def = makeDeferred();
+ await afterNextRender(async () => {
+ await this.start({
+ discuss: {
+ params: {
+ default_active_id: 'mail.channel_11',
+ },
},
- },
+ async mockRPC(route, args) {
+ const res = await this._super(...arguments);
+ // domain should be like [['channel_id', 'in', [X]]] with X the channel id
+ if (route.includes('message_fetch') && args.kwargs.domain[0][2].includes(11)) {
+ messageFetchChannel11Def.resolve();
+ }
+ if (route.includes('message_fetch') && args.kwargs.domain[0][2].includes(12)) {
+ messageFetchChannel12Def.resolve();
+ }
+ return res;
+ },
+ });
+ // wait until messages are fetched, ignore other renders that are too early
+ await messageFetchChannel11Def;
});
assert.strictEqual(
document.querySelectorAll(`
.o_Discuss_thread .o_ThreadView_messageList .o_MessageList_message
`).length,
25,
- "should have 25 messages"
+ "should have 25 messages in channel 11"
);
// scroll to top of channel11
@@ -1871,8 +1879,13 @@ QUnit.test('restore thread scroll position', async function (assert) {
"should have scrolled to top of thread"
);
- // select channel12
- await afterNextRender(() =>
+ const scrollChannel12Def = makeDeferred();
+ const onScroll = () => {
+ scrollChannel12Def.resolve();
+ };
+ document.addEventListener('o-message-list-scrolled', onScroll);
+ // select channel 12
+ await afterNextRender(async () => {
document.querySelector(`
.o_DiscussSidebar_groupChannel
.o_DiscussSidebar_item[data-thread-local-id="${
@@ -1881,9 +1894,26 @@ QUnit.test('restore thread scroll position', async function (assert) {
thread.model === 'mail.channel'
).localId
}"]
- `).click()
+ `).click();
+ // wait until messages are fetched, ignore other renders that are too early
+ await messageFetchChannel12Def;
+ });
+ assert.strictEqual(
+ document.querySelectorAll(`
+ .o_Discuss_thread .o_ThreadView_messageList .o_MessageList_message
+ `).length,
+ 24,
+ "should have 24 messages in channel 12"
);
- // select channel11
+
+ // Ensure scrollIntoView of channel 12 has enough time to complete before
+ // going back to channel 11. Await is needed to prevent the scrollIntoView
+ // initially planned for channel 12 to actually apply on channel 11.
+ // task-2333535
+ await scrollChannel12Def;
+ document.removeEventListener('o-message-list-scrolled', onScroll);
+
+ // select channel 11
await afterNextRender(() =>
document.querySelector(`
.o_DiscussSidebar_groupChannel
@@ -1898,7 +1928,7 @@ QUnit.test('restore thread scroll position', async function (assert) {
assert.strictEqual(
document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`).scrollTop,
0,
- "should have recovered scroll position of channel11 (scroll to top)"
+ "should have recovered scroll position of channel 11 (scroll to top)"
);
// select channel12
@@ -1920,7 +1950,7 @@ QUnit.test('restore thread scroll position', async function (assert) {
assert.strictEqual(
messageList.scrollTop + messageList.clientHeight,
messageList.scrollHeight,
- "should have recovered scroll position of channel12 (scroll to bottom)"
+ "should have recovered scroll position of channel 12 (scroll to bottom)"
);
});
diff --git a/addons/mail/static/src/components/message/message_tests.js b/addons/mail/static/src/components/message/message_tests.js
index df964458fea..e69c40e35aa 100644
--- a/addons/mail/static/src/components/message/message_tests.js
+++ b/addons/mail/static/src/components/message/message_tests.js
@@ -190,7 +190,8 @@ QUnit.test('Notification Sent', async function (assert) {
assert.expect(9);
await this.start();
- const threadView = this.env.models['mail.thread_view'].create({
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
thread: [['create', {
id: 11,
model: 'mail.channel',
@@ -205,10 +206,10 @@ QUnit.test('Notification Sent', async function (assert) {
notification_type: 'email',
partner: [['insert', { id: 12, name: "Someone" }]],
}]],
- originThread: [['link', threadView.thread]],
+ originThread: [['link', threadViewer.thread]],
});
await this.createMessageComponent(message, {
- threadViewLocalId: threadView.localId
+ threadViewLocalId: threadViewer.threadView.localId
});
assert.containsOnce(
@@ -281,7 +282,8 @@ QUnit.test('Notification Error', async function (assert) {
});
await this.start({ env: { bus } });
- const threadView = this.env.models['mail.thread_view'].create({
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
thread: [['create', {
id: 11,
model: 'mail.channel',
@@ -295,10 +297,10 @@ QUnit.test('Notification Error', async function (assert) {
notification_status: 'exception',
notification_type: 'email',
}]],
- originThread: [['link', threadView.thread]],
+ originThread: [['link', threadViewer.thread]],
});
await this.createMessageComponent(message, {
- threadViewLocalId: threadView.localId
+ threadViewLocalId: threadViewer.threadView.localId
});
assert.containsOnce(
@@ -347,7 +349,10 @@ QUnit.test("'channel_fetch' notification received is correctly handled", async f
],
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
const message = this.env.models['mail.message'].create({
author: [['link', currentPartner]],
body: "<p>Test</p>",
@@ -355,7 +360,9 @@ QUnit.test("'channel_fetch' notification received is correctly handled", async f
originThread: [['link', thread]],
});
- await this.createMessageComponent(message, { threadViewLocalId: threadView.localId });
+ await this.createMessageComponent(message, {
+ threadViewLocalId: threadViewer.threadView.localId,
+ });
assert.containsOnce(
document.body,
@@ -403,14 +410,19 @@ QUnit.test("'channel_seen' notification received is correctly handled", async fu
],
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
const message = this.env.models['mail.message'].create({
author: [['link', currentPartner]],
body: "<p>Test</p>",
id: 100,
originThread: [['link', thread]],
});
- await this.createMessageComponent(message, { threadViewLocalId: threadView.localId });
+ await this.createMessageComponent(message, {
+ threadViewLocalId: threadViewer.threadView.localId,
+ });
assert.containsOnce(
document.body,
@@ -458,14 +470,19 @@ QUnit.test("'channel_fetch' notification then 'channel_seen' received are corre
],
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
const message = this.env.models['mail.message'].create({
author: [['link', currentPartner]],
body: "<p>Test</p>",
id: 100,
originThread: [['link', thread]],
});
- await this.createMessageComponent(message, { threadViewLocalId: threadView.localId });
+ await this.createMessageComponent(message, {
+ threadViewLocalId: threadViewer.threadView.localId,
+ });
assert.containsOnce(
document.body,
@@ -527,25 +544,28 @@ QUnit.test('do not show messaging seen indicator if not authored by me', async f
partnerSeenInfos: [['create', [
{
id: this.env.session.partner_id,
- lastFetchedMessage: [['insert', {id: 100}]],
- partner: [['insert', {id: this.env.messaging.currentPartner.id}]],
+ lastFetchedMessage: [['insert', { id: 100 }]],
+ partner: [['insert', { id: this.env.messaging.currentPartner.id }]],
},
{
id: 100,
- lastFetchedMessage: [['insert', {id: 100}]],
+ lastFetchedMessage: [['insert', { id: 100 }]],
partner: [['link', author]],
},
]]],
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
const message = this.env.models['mail.message'].insert({
author: [['link', author]],
body: "<p>Test</p>",
id: 100,
originThread: [['link', thread]],
});
- await this.createMessageComponent(message, { threadViewLocalId: threadView.localId });
+ await this.createMessageComponent(message, { threadViewLocalId: threadViewer.threadView.localId });
assert.containsOnce(
document.body,
@@ -571,11 +591,14 @@ QUnit.test('do not show messaging seen indicator if before last seen by all mess
id: 11,
messageSeenIndicators: [['insert', {
id: this.env.models['mail.message_seen_indicator'].computeId(99, 11),
- message: [['insert', {id: 99}]],
+ message: [['insert', { id: 99 }]],
}]],
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
const lastSeenMessage = this.env.models['mail.message'].create({
author: [['link', currentPartner]],
body: "<p>You already saw me</p>",
@@ -598,11 +621,13 @@ QUnit.test('do not show messaging seen indicator if before last seen by all mess
{
id: 100,
lastSeenMessage: [['link', lastSeenMessage]],
- partner: [['insert', {id: 100}]],
+ partner: [['insert', { id: 100 }]],
},
]]],
});
- await this.createMessageComponent(message, { threadViewLocalId: threadView.localId });
+ await this.createMessageComponent(message, {
+ threadViewLocalId: threadViewer.threadView.localId,
+ });
assert.containsOnce(
document.body,
@@ -634,30 +659,35 @@ QUnit.test('only show messaging seen indicator if authored by me, after last see
partnerSeenInfos: [['create', [
{
id: currentPartner.id,
- lastSeenMessage: [['insert', {id: 100}]],
+ lastSeenMessage: [['insert', { id: 100 }]],
partner: [['link', currentPartner]],
},
{
id: 100,
- partner: [['insert', {id: 100}]],
- lastFetchedMessage: [['insert', {id: 100}]],
- lastSeenMessage: [['insert', {id: 99}]],
+ partner: [['insert', { id: 100 }]],
+ lastFetchedMessage: [['insert', { id: 100 }]],
+ lastSeenMessage: [['insert', { id: 99 }]],
},
]]],
messageSeenIndicators: [['insert', {
id: this.env.models['mail.message_seen_indicator'].computeId(100, 11),
- message: [['insert', {id: 100}]],
+ message: [['insert', { id: 100 }]],
}]],
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
const message = this.env.models['mail.message'].insert({
author: [['link', currentPartner]],
body: "<p>Test</p>",
id: 100,
originThread: [['link', thread]],
});
- await this.createMessageComponent(message, { threadViewLocalId: threadView.localId });
+ await this.createMessageComponent(message, {
+ threadViewLocalId: threadViewer.threadView.localId,
+ });
assert.containsOnce(
document.body,
diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js
index 966bff9a5ba..17531eabbca 100644
--- a/addons/mail/static/src/components/message_list/message_list.js
+++ b/addons/mail/static/src/components/message_list/message_list.js
@@ -52,17 +52,6 @@ class MessageList extends Component {
*/
this._loadMoreRef = useRef('loadMore');
/**
- * Tracked last thread cache rendered. Useful to determine scroll
- * position on patch if it is on the same thread cache or not.
- */
- this._renderedThreadCache = null;
- /**
- * Tracked last selected message. Useful to determine when patch comes
- * from a message selection on a given thread cache, so that it
- * auto-scroll to that message.
- */
- this._selectedMessage = null;
- /**
* Snapshot computed during willPatch, which is used by patched.
*/
this._willPatchSnapshot = undefined;
@@ -121,14 +110,14 @@ class MessageList extends Component {
* fixed height, which is the case for the moment.
*/
async adjustFromComponentHints() {
+ if (!this.threadView) {
+ return;
+ }
for (const hint of this.threadView.componentHintList) {
switch (hint.type) {
case 'change-of-thread-cache':
this._adjustFromChangeOfThreadCache(hint);
break;
- case 'chat-window-unfolded':
- this._adjustFromChatWindowUnfolded(hint);
- break;
case 'current-partner-just-posted-message':
this._adjustFromCurrentPartnerJustPostedMessage(hint);
break;
@@ -427,6 +416,9 @@ class MessageList extends Component {
* @private
*/
_checkMostRecentMessageIsVisible() {
+ if (!this.threadView) {
+ return;
+ }
const thread = this.threadView.thread;
const threadCache = this.threadView.threadCache;
const lastMessageIsVisible =
@@ -531,7 +523,11 @@ class MessageList extends Component {
// could be unmounted in the meantime (due to throttled behavior)
return;
}
- this.threadView.saveThreadCacheScrollPositionsAsInitial(this.el.scrollTop);
+ if (!this.threadView || !this.threadView.threadViewer) {
+ return;
+ }
+ this.trigger('o-message-list-scrolled');
+ this.threadView.threadViewer.saveThreadCacheScrollPositionsAsInitial(this.el.scrollTop);
if (!this._isAutoLoadOnScrollActive) {
return;
}
diff --git a/addons/mail/static/src/components/thread_view/thread_view.xml b/addons/mail/static/src/components/thread_view/thread_view.xml
index e7dd82ccf43..25e93400368 100644
--- a/addons/mail/static/src/components/thread_view/thread_view.xml
+++ b/addons/mail/static/src/components/thread_view/thread_view.xml
@@ -4,12 +4,12 @@
<t t-name="mail.ThreadView" owl="1">
<div class="o_ThreadView" t-att-data-correspondent-id="threadView and threadView.thread and threadView.thread.correspondent and threadView.thread.correspondent.id" t-att-data-thread-local-id="threadView and threadView.thread and threadView.thread.localId">
<t t-if="threadView">
- <t t-if="threadView.isShowingLoading" name="loadingCondition">
+ <t t-if="threadView.isLoading" name="loadingCondition">
<div class="o_ThreadView_loading">
<span><i class="o_ThreadView_loadingIcon fa fa-spinner fa-spin" title="Loading..." role="img"/>Loading...</span>
</div>
</t>
- <t t-elif="threadView.threadCache.isLoaded">
+ <t t-elif="threadView.threadCache.isLoaded or threadView.thread.isTemporary">
<MessageList
class="o_ThreadView_messageList"
hasMessageCheckbox="props.hasMessageCheckbox"
diff --git a/addons/mail/static/src/components/thread_view/thread_view_tests.js b/addons/mail/static/src/components/thread_view/thread_view_tests.js
index c8f41632ae3..2d0b3207ae1 100644
--- a/addons/mail/static/src/components/thread_view/thread_view_tests.js
+++ b/addons/mail/static/src/components/thread_view/thread_view_tests.js
@@ -81,8 +81,11 @@ QUnit.test('dragover files on thread with composer', async function (assert) {
name: "General",
public: 'public',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
- await this.createThreadViewComponent(threadView, { hasComposer: true });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.createThreadViewComponent(threadViewer.threadView, { hasComposer: true });
await afterNextRender(() =>
dragenterFiles(document.querySelector('.o_ThreadView'))
);
@@ -122,8 +125,11 @@ QUnit.test('message list desc order', async function (assert) {
name: "General",
public: 'public',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
- await this.createThreadViewComponent(threadView, { order: 'desc' }, { isFixedSize: true });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.createThreadViewComponent(threadViewer.threadView, { order: 'desc' }, { isFixedSize: true });
const messageItems = document.querySelectorAll(`.o_MessageList_item`);
assert.notOk(
messageItems[0].classList.contains("o_MessageList_loadMore"),
@@ -190,8 +196,11 @@ QUnit.test('message list asc order', async function (assert) {
name: "General",
public: 'public',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
- await this.createThreadViewComponent(threadView, { order: 'asc' }, { isFixedSize: true });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.createThreadViewComponent(threadViewer.threadView, { order: 'asc' }, { isFixedSize: true });
const messageItems = document.querySelectorAll(`.o_MessageList_item`);
assert.notOk(
messageItems[messageItems.length - 1].classList.contains("o_MessageList_loadMore"),
@@ -283,8 +292,11 @@ QUnit.test('mark channel as fetched when a new message is loaded and as seen whe
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
- await this.createThreadViewComponent(threadView, { hasComposer: true });
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.createThreadViewComponent(threadViewer.threadView, { hasComposer: true });
const notifications = [
[['myDB', 'mail.channel', 100], {
channelId: 100,
@@ -353,9 +365,11 @@ QUnit.test('mark channel as fetched and seen when a new message is loaded if com
model: 'mail.channel',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
- await this.createThreadViewComponent(threadView, { hasComposer: true });
-
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.createThreadViewComponent(threadViewer.threadView, { hasComposer: true });
document.querySelector('.o_ComposerTextInput_textarea').focus();
const notifications = [
[['myDB', 'mail.channel', 100], {
@@ -394,8 +408,11 @@ QUnit.test('show message subject if thread is mailing channel', async function (
name: "General",
public: 'public',
});
- const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
- await this.createThreadViewComponent(threadView);
+ const threadViewer = this.env.models['mail.thread_viewer'].create({
+ hasThreadView: true,
+ thread: [['link', thread]],
+ });
+ await this.createThreadViewComponent(threadViewer.threadView);
assert.containsOnce(
document.body,