[IMP] mail, *: convert ThreadPreview to use Channel instead of Thread

* = im_livechat, rating, test_mail_full

Part of task-2948676.

closes odoo/odoo#99342

Related: odoo/enterprise#30921
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Louis Wicket (wil)
2022-09-05 16:53:52 +02:00
parent 9beb2b13d3
commit ff49527fb5
22 changed files with 244 additions and 259 deletions
@@ -1,25 +0,0 @@
/** @odoo-module **/
import { ThreadPreview } from '@mail/components/channel_preview_view/channel_preview_view';
import { patch } from 'web.utils';
const components = { ThreadPreview };
patch(components.ThreadPreview.prototype, 'im_livechat/static/src/components/channel_preview_view/channel_preview_view.js', {
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
image(...args) {
if (this.threadPreviewView.thread.channel.channel_type === 'livechat') {
return '/mail/static/src/img/smiley/avatar.jpg';
}
return this._super(...args);
}
});
@@ -0,0 +1,17 @@
/** @odoo-module **/
import { patchRecordMethods } from '@mail/model/model_core';
// ensure that the model definition is loaded before the patch
import '@mail/models/channel_preview_view';
patchRecordMethods('ChannelPreviewView', {
/**
* @override
*/
_computeImageUrl() {
if (this.channel.channel_type === 'livechat') {
return '/mail/static/src/img/smiley/avatar.jpg';
}
return this._super();
},
});
@@ -7,12 +7,11 @@ patchRecordMethods('NotificationListView', {
/**
* @override
*/
_computeFilteredThreads() {
_computeFilteredChannels() {
if (this.filter === 'livechat') {
return this.messaging.models['Thread'].all(thread =>
thread.channel &&
thread.channel.channel_type === 'livechat' &&
thread.isPinned
return this.messaging.models['Channel'].all(channel =>
channel.channel_type === 'livechat' &&
channel.thread.isPinned
);
}
return this._super();
@@ -48,7 +48,7 @@ QUnit.test('livechats should be in "chat" filter', async function (assert) {
);
assert.containsOnce(
document.body,
`.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]`,
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
"livechat should be listed in 'all' tab/filter of messaging menu"
);
@@ -62,7 +62,7 @@ QUnit.test('livechats should be in "chat" filter', async function (assert) {
);
assert.containsOnce(
document.body,
`.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]`,
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
"livechat should be listed in 'chat' tab/filter of messaging menu"
);
});
@@ -5,7 +5,7 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class ThreadPreview extends Component {
export class ChannelPreviewView extends Component {
/**
* @override
@@ -15,37 +15,18 @@ export class ThreadPreview extends Component {
useRefToModel({ fieldName: 'markAsReadRef', refName: 'markAsRead' });
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Get the image route of the thread.
*
* @returns {string}
* @returns {ChannelPreviewView}
*/
image() {
if (!this.threadPreviewView.thread.channel) {
return '/mail/static/src/img/smiley/avatar.jpg';
}
if (this.threadPreviewView.thread.channel.correspondent) {
return this.threadPreviewView.thread.channel.correspondent.avatarUrl;
}
return `/web/image/mail.channel/${this.threadPreviewView.thread.id}/avatar_128?unique=${this.threadPreviewView.thread.channel.avatarCacheKey}`;
}
/**
* @returns {ThreadPreviewView}
*/
get threadPreviewView() {
get channelPreviewView() {
return this.props.record;
}
}
Object.assign(ThreadPreview, {
Object.assign(ChannelPreviewView, {
props: { record: Object },
template: 'mail.ThreadPreview',
template: 'mail.ChannelPreviewView',
});
registerMessagingComponent(ThreadPreview);
registerMessagingComponent(ChannelPreviewView);
@@ -1,71 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ThreadPreview" owl="1">
<t t-if="threadPreviewView">
<t t-name="mail.ChannelPreviewView" owl="1">
<t t-if="channelPreviewView">
<!--
The preview template is used by the discuss in mobile, and by the systray
menu in order to show preview of threads.
-->
<div class="o_NotificationListItem o_ThreadPreview d-flex flex-shrink-0 align-items-center p-1 cursor-pointer"
t-att-class="{ 'o-muted': threadPreviewView.thread.channel.localMessageUnreadCounter === 0 }"
<div class="o_NotificationListItem o_ChannelPreviewView d-flex flex-shrink-0 align-items-center p-1 cursor-pointer"
t-att-class="{ 'o-muted': channelPreviewView.channel.localMessageUnreadCounter === 0 }"
t-attf-class="{{ className }}"
t-on-click="threadPreviewView.onClick"
t-att-data-thread-id="threadPreviewView.thread.id"
t-att-data-thread-model="threadPreviewView.thread.model"
t-on-click="channelPreviewView.onClick"
t-att-data-channel-id="channelPreviewView.channel.id"
t-ref="root"
>
<div class="o_NotificationListItem_sidebar o_ThreadPreview_sidebar m-1">
<div class="o_NotificationListItem_imageContainer o_ThreadPreview_imageContainer o_ThreadPreview_sidebarItem position-relative">
<img class="o_NotificationListItem_image o_ThreadPreview_image w-100 h-100 rounded-circle" t-att-src="image()" alt="Thread Image"/>
<t t-if="threadPreviewView.personaImStatusIconView">
<div class="o_NotificationListItem_sidebar o_ChannelPreviewView_sidebar m-1">
<div class="o_NotificationListItem_imageContainer o_ChannelPreviewView_imageContainer o_ChannelPreviewView_sidebarItem position-relative">
<img class="o_NotificationListItem_image o_ChannelPreviewView_image w-100 h-100 rounded-circle" t-att-src="channelPreviewView.imageUrl" alt="Thread Image"/>
<t t-if="channelPreviewView.personaImStatusIconView">
<PersonaImStatusIcon
className="'o_NotificationListItem_personaImStatusIcon o_ThreadPreview_personaImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center'"
className="'o_NotificationListItem_personaImStatusIcon o_ChannelPreviewView_personaImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center'"
classNameObj="{
'o-isDeviceSmall': messaging.device.isSmall,
'small': !messaging.device.isSmall,
'o-muted': threadPreviewView.thread.channel.localMessageUnreadCounter === 0,
'o-muted': channelPreviewView.channel.localMessageUnreadCounter === 0,
}"
record="threadPreviewView.personaImStatusIconView"
record="channelPreviewView.personaImStatusIconView"
/>
</t>
</div>
</div>
<div class="o_NotificationListItem_content o_ThreadPreview_content d-flex flex-column flex-grow-1 align-self-start m-2">
<div class="o_NotificationListItem_header o_ThreadPreview_header d-flex align-items-baseline">
<span class="o_NotificationListItem_name o_ThreadPreview_name text-truncate fw-bold" t-att-class="{ 'o-isDeviceSmall fs-5': messaging.device.isSmall, 'o-muted text-600': threadPreviewView.thread.channel.localMessageUnreadCounter === 0 }">
<t t-esc="threadPreviewView.thread.displayName"/>
<div class="o_NotificationListItem_content o_ChannelPreviewView_content d-flex flex-column flex-grow-1 align-self-start m-2">
<div class="o_NotificationListItem_header o_ChannelPreviewView_header d-flex align-items-baseline">
<span class="o_NotificationListItem_name o_ChannelPreviewView_name text-truncate fw-bold" t-att-class="{ 'o-isDeviceSmall fs-5': messaging.device.isSmall, 'o-muted text-600': channelPreviewView.channel.localMessageUnreadCounter === 0 }">
<t t-esc="channelPreviewView.channel.displayName"/>
</span>
<t t-if="threadPreviewView.thread.channel.localMessageUnreadCounter > 1">
<span class="o_NotificationListItem_counter o_ThreadPreview_counter mx-1 fw-bold">
(<t t-esc="threadPreviewView.thread.channel.localMessageUnreadCounter"/>)
<t t-if="channelPreviewView.channel.localMessageUnreadCounter > 1">
<span class="o_NotificationListItem_counter o_ChannelPreviewView_counter mx-1 fw-bold">
(<t t-esc="channelPreviewView.channel.localMessageUnreadCounter"/>)
</span>
</t>
<t t-if="threadPreviewView.thread.rtcSessions.length > 0">
<span class="o_ThreadPreview_callIndicator fa fa-volume-up mx-2" t-att-class="{ 'o-isCalling text-danger': threadPreviewView.thread.rtc }"/>
<t t-if="channelPreviewView.thread.rtcSessions.length > 0">
<span class="o_ChannelPreviewView_callIndicator fa fa-volume-up mx-2" t-att-class="{ 'o-isCalling text-danger': channelPreviewView.thread.rtc }"/>
</t>
<span class="flex-grow-1"/>
<t t-if="threadPreviewView.thread.lastMessage and threadPreviewView.thread.lastMessage.date">
<small class="o_NotificationListItem_date o_ThreadPreview_date flex-shrink-0 text-500" t-att-class="{ 'o-muted': threadPreviewView.thread.channel.localMessageUnreadCounter === 0 }">
<t t-esc="threadPreviewView.thread.lastMessage.date.fromNow()"/>
<t t-if="channelPreviewView.thread.lastMessage and channelPreviewView.thread.lastMessage.date">
<small class="o_NotificationListItem_date o_ChannelPreviewView_date flex-shrink-0 text-500" t-att-class="{ 'o-muted': channelPreviewView.channel.localMessageUnreadCounter === 0 }">
<t t-esc="channelPreviewView.thread.lastMessage.date.fromNow()"/>
</small>
</t>
</div>
<div class="o_ThreadPreview_core d-flex align-items-baseline">
<span class="o_NotificationListItem_coreItem o_NotificationListItem_inlineText o_ThreadPreview_coreItem o_ThreadPreview_inlineText me-2 text-truncate" t-att-class="{ 'o-empty': threadPreviewView.isEmpty }">
<t t-if="threadPreviewView.lastTrackingValue">
<TrackingValue value="threadPreviewView.lastTrackingValue"/>
<div class="o_ChannelPreviewView_core d-flex align-items-baseline">
<span class="o_NotificationListItem_coreItem o_NotificationListItem_inlineText o_ChannelPreviewView_coreItem o_ChannelPreviewView_inlineText me-2 text-truncate" t-att-class="{ 'o-empty': channelPreviewView.isEmpty }">
<t t-if="channelPreviewView.lastTrackingValue">
<TrackingValue value="channelPreviewView.lastTrackingValue"/>
</t>
<t t-else="">
<t t-if="threadPreviewView.messageAuthorPrefixView">
<MessageAuthorPrefix record="threadPreviewView.messageAuthorPrefixView"/>
<t t-if="channelPreviewView.messageAuthorPrefixView">
<MessageAuthorPrefix record="channelPreviewView.messageAuthorPrefixView"/>
</t>
<span class="o_ThreadPreview_messageBody" t-esc="threadPreviewView.inlineLastMessageBody"/>
<span class="o_ChannelPreviewView_messageBody" t-esc="channelPreviewView.inlineLastMessageBody"/>
</t>
</span>
<span class="flex-grow-1"/>
<t t-if="threadPreviewView.thread.channel.localMessageUnreadCounter > 0">
<span class="o_NotificationListItem_coreItem o_NotificationListItem_markAsRead o_ThreadPreview_coreItem o_ThreadPreview_markAsRead fa fa-check d-flex flex-shrink-0 ms-2 text-600 opacity-50 opacity-100-hover" title="Mark as Read" t-on-click="threadPreviewView.onClickMarkAsRead" t-ref="markAsRead"/>
<t t-if="channelPreviewView.channel.localMessageUnreadCounter > 0">
<span class="o_NotificationListItem_coreItem o_NotificationListItem_markAsRead o_ChannelPreviewView_coreItem o_ChannelPreviewView_markAsRead fa fa-check d-flex flex-shrink-0 ms-2 text-600 opacity-50 opacity-100-hover" title="Mark as Read" t-on-click="channelPreviewView.onClickMarkAsRead" t-ref="markAsRead"/>
</t>
</div>
</div>
@@ -11,8 +11,8 @@
</t>
<t t-else="">
<t t-foreach="notificationListView.notificationViews" t-as="notificationView" t-key="notificationView.localId">
<t t-if="notificationView.constructor.name === 'ThreadPreviewView'">
<ThreadPreview
<t t-if="notificationView.constructor.name === 'ChannelPreviewView'">
<ChannelPreviewView
className="'o_NotificationList_preview'"
classNameObj="{ 'o-isDeviceSmall p-2': messaging.device.isSmall }"
record="notificationView"
+4
View File
@@ -224,6 +224,10 @@ registerModel({
inverse: 'channel',
isCausal: true,
}),
channelPreviewViews: many('ChannelPreviewView', {
inverse: 'channel',
isCausal: true,
}),
channel_type: attr(),
correspondent: one('Partner', {
compute: '_computeCorrespondent',
@@ -6,7 +6,7 @@ import { clear } from '@mail/model/model_field_command';
import { htmlToTextContentInline } from '@mail/js/utils';
registerModel({
name: 'ThreadPreviewView',
name: 'ChannelPreviewView',
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -33,12 +33,21 @@ registerModel({
this.thread.markAsSeen(this.thread.lastNonTransientMessage);
}
},
/**
* @returns {string}
*/
_computeImageUrl() {
if (this.channel.correspondent) {
return this.channel.correspondent.avatarUrl;
}
return `/web/image/mail.channel/${this.channel.id}/avatar_128?unique=${this.channel.avatarCacheKey}`;
},
/**
* @private
* @returns {string|FieldCommand}
*/
_computeInlineLastMessageBody() {
if (!this.thread.lastMessage) {
if (!this.thread || !this.thread.lastMessage) {
return clear();
}
return htmlToTextContentInline(this.thread.lastMessage.prettyBody);
@@ -52,20 +61,21 @@ registerModel({
},
/**
* @private
* @returns {FieldCommand}
* @returns {TrackingValue|FieldCommand}
*/
_computeLastTrackingValue() {
if (this.thread.lastMessage && this.thread.lastMessage.lastTrackingValue) {
if (this.thread && this.thread.lastMessage && this.thread.lastMessage.lastTrackingValue) {
return this.thread.lastMessage.lastTrackingValue;
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
* @returns {Object|FieldCommand}
*/
_computeMessageAuthorPrefixView() {
if (
this.thread &&
this.thread.lastMessage &&
this.thread.lastMessage.author
) {
@@ -78,16 +88,23 @@ registerModel({
* @returns {Object|FieldCommand}
*/
_computePersonaImStatusIconView() {
if (!this.thread.channel.correspondent) {
if (!this.channel.correspondent) {
return clear();
}
if (this.thread.channel.correspondent.isImStatusSet) {
if (this.channel.correspondent.isImStatusSet) {
return {};
}
return clear();
},
},
fields: {
channel: one('Channel', {
identifying: true,
inverse: 'channelPreviewViews',
}),
imageUrl: attr({
compute: '_computeImageUrl',
}),
inlineLastMessageBody: attr({
compute: '_computeInlineLastMessageBody',
default: "",
@@ -105,21 +122,20 @@ registerModel({
markAsReadRef: attr(),
messageAuthorPrefixView: one('MessageAuthorPrefixView', {
compute: '_computeMessageAuthorPrefixView',
inverse: 'threadPreviewViewOwner',
inverse: 'channelPreviewViewOwner',
isCausal: true,
}),
notificationListViewOwner: one('NotificationListView', {
identifying: true,
inverse: 'threadPreviewViews',
inverse: 'channelPreviewViews',
}),
personaImStatusIconView: one('PersonaImStatusIconView', {
compute: '_computePersonaImStatusIconView',
inverse: 'threadPreviewViewOwner',
inverse: 'channelPreviewViewOwner',
isCausal: true,
}),
thread: one('Thread', {
identifying: true,
inverse: 'threadPreviewViews',
related: 'channel.thread',
}),
},
});
@@ -13,12 +13,12 @@ registerModel({
* @returns {FieldCommand}
*/
_computeMessage() {
if (this.channelPreviewViewOwner) {
return this.channelPreviewViewOwner.thread.lastMessage;
}
if (this.threadNeedactionPreviewViewOwner) {
return this.threadNeedactionPreviewViewOwner.thread.lastNeedactionMessageAsOriginThread;
}
if (this.threadPreviewViewOwner) {
return this.threadPreviewViewOwner.thread.lastMessage;
}
return clear();
},
/**
@@ -26,16 +26,20 @@ registerModel({
* @returns {FieldCommand}
*/
_computeThread() {
if (this.channelPreviewViewOwner) {
return this.channelPreviewViewOwner.thread;
}
if (this.threadNeedactionPreviewViewOwner) {
return this.threadNeedactionPreviewViewOwner.thread;
}
if (this.threadPreviewViewOwner) {
return this.threadPreviewViewOwner.thread;
}
return clear();
},
},
fields: {
channelPreviewViewOwner: one('ChannelPreviewView', {
identifying: true,
inverse: 'messageAuthorPrefixView',
}),
message: one('Message', {
compute: '_computeMessage',
}),
@@ -46,9 +50,5 @@ registerModel({
identifying: true,
inverse: 'messageAuthorPrefixView',
}),
threadPreviewViewOwner: one('ThreadPreviewView', {
identifying: true,
inverse: 'messageAuthorPrefixView',
}),
},
});
@@ -13,6 +13,32 @@ registerModel({
},
},
recordMethods: {
/**
* @private
* @returns {Object[]}
*/
_computeChannelPreviewViews() {
return this.filteredChannels
.sort((c1, c2) => {
if (c1.localMessageUnreadCounter > 0 && c2.localMessageUnreadCounter === 0) {
return -1;
}
if (c1.localMessageUnreadCounter === 0 && c2.localMessageUnreadCounter > 0) {
return 1;
}
if (c1.thread.lastMessage && c2.thread.lastMessage) {
return c1.thread.lastMessage.id < c2.thread.lastMessage.id ? 1 : -1;
}
if (c1.thread.lastMessage) {
return -1;
}
if (c2.thread.lastMessage) {
return 1;
}
return c1.id < c2.id ? -1 : 1;
})
.map(channel => ({ channel }));
},
/**
* @private
* @returns {string|FieldCommand}
@@ -28,32 +54,30 @@ registerModel({
},
/**
* @private
* @returns {FieldCommand}
* @returns {Channel[]|FieldCommand}
*/
_computeFilteredThreads() {
_computeFilteredChannels() {
switch (this.filter) {
case 'channel': {
return this.messaging.models['Thread']
.all(thread =>
thread.channel &&
thread.channel.channel_type === 'channel' &&
thread.isPinned
return this.messaging.models['Channel']
.all(channel =>
channel.channel_type === 'channel' &&
channel.thread.isPinned
)
.sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1);
}
case 'chat': {
return this.messaging.models['Thread']
.all(thread =>
thread.isChatChannel &&
thread.isPinned &&
thread.model === 'mail.channel'
return this.messaging.models['Channel']
.all(channel =>
channel.thread.isChatChannel &&
channel.thread.isPinned
)
.sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1);
}
case 'all': {
// "All" filter is for channels and chats
return this.messaging.models['Thread']
.all(thread => thread.isPinned && thread.model === 'mail.channel')
return this.messaging.models['Channel']
.all(channel => channel.thread.isPinned)
.sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1);
}
}
@@ -90,7 +114,7 @@ registerModel({
}
notifications.push(...this.notificationGroupViews);
notifications.push(...this.threadNeedactionPreviewViews);
notifications.push(...this.threadPreviewViews);
notifications.push(...this.channelPreviewViews);
return notifications;
},
/**
@@ -123,32 +147,6 @@ registerModel({
})
.map(thread => ({ thread }));
},
/**
* @private
* @returns {FieldCommand}
*/
_computeThreadPreviewViews() {
return this.filteredThreads
.sort((t1, t2) => {
if (t1.channel.localMessageUnreadCounter > 0 && t2.channel.localMessageUnreadCounter === 0) {
return -1;
}
if (t1.channel.localMessageUnreadCounter === 0 && t2.channel.localMessageUnreadCounter > 0) {
return 1;
}
if (t1.lastMessage && t2.lastMessage) {
return t1.lastMessage.id < t2.lastMessage.id ? 1 : -1;
}
if (t1.lastMessage) {
return -1;
}
if (t2.lastMessage) {
return 1;
}
return t1.id < t2.id ? -1 : 1;
})
.map(thread => ({ thread }));
},
/**
* Load previews of given thread. Basically consists of fetching all missing
* last messages of each thread.
@@ -156,12 +154,17 @@ registerModel({
* @private
*/
async _loadPreviews() {
const threads = this.threadPreviewViews
.map(threadPreviewView => threadPreviewView.thread);
const threads = this.channelPreviewViews
.map(channelPreviewView => channelPreviewView.thread);
this.messaging.models['Thread'].loadPreviews(threads);
},
},
fields: {
channelPreviewViews: many('ChannelPreviewView', {
compute: '_computeChannelPreviewViews',
inverse: 'notificationListViewOwner',
isCausal: true,
}),
discussOwner: one('Discuss', {
identifying: true,
inverse: 'notificationListView',
@@ -169,8 +172,8 @@ registerModel({
filter: attr({
compute: '_computeFilter',
}),
filteredThreads: many('Thread', {
compute: '_computeFilteredThreads',
filteredChannels: many('Channel', {
compute: '_computeFilteredChannels',
}),
messagingMenuOwner: one('MessagingMenu', {
identifying: true,
@@ -195,10 +198,5 @@ registerModel({
inverse: 'notificationListViewOwner',
isCausal: true,
}),
threadPreviewViews: many('ThreadPreviewView', {
compute: '_computeThreadPreviewViews',
inverse: 'notificationListViewOwner',
isCausal: true,
}),
},
});
@@ -19,6 +19,9 @@ registerModel({
if (this.channelMemberViewOwner) {
return this.channelMemberViewOwner.channelMember.persona;
}
if (this.channelPreviewViewOwner) {
return this.channelPreviewViewOwner.channel.correspondent.persona;
}
if (this.composerSuggestionViewOwner) {
return this.composerSuggestionViewOwner.suggestable.partner.persona;
}
@@ -36,9 +39,6 @@ registerModel({
if (this.threadNeedactionPreviewViewOwner) {
return this.threadNeedactionPreviewViewOwner.thread.channel.correspondent.persona;
}
if (this.threadPreviewViewOwner) {
return this.threadPreviewViewOwner.thread.channel.correspondent.persona;
}
return clear();
},
},
@@ -51,6 +51,10 @@ registerModel({
identifying: true,
inverse: 'personaImStatusIconView',
}),
channelPreviewViewOwner: one('ChannelPreviewView', {
identifying: true,
inverse: 'personaImStatusIconView',
}),
composerSuggestionViewOwner: one('ComposerSuggestionView', {
identifying: true,
inverse: 'personaImStatusIconView',
@@ -63,17 +67,13 @@ registerModel({
identifying: true,
inverse: 'personaImStatusIconView',
}),
threadNeedactionPreviewViewOwner: one('ThreadNeedactionPreviewView', {
identifying: true,
inverse: 'personaImStatusIconView',
}),
threadPreviewViewOwner: one('ThreadPreviewView', {
identifying: true,
inverse: 'personaImStatusIconView',
}),
persona: one('Persona', {
compute: '_computePersona',
required: true,
}),
threadNeedactionPreviewViewOwner: one('ThreadNeedactionPreviewView', {
identifying: true,
inverse: 'personaImStatusIconView',
}),
},
});
-4
View File
@@ -2110,10 +2110,6 @@ registerModel({
inverse: 'thread',
isCausal: true,
}),
threadPreviewViews: many('ThreadPreviewView', {
inverse: 'thread',
isCausal: true,
}),
/**
* Members that are currently typing something in the composer of this
* thread, including current partner.
@@ -39,23 +39,23 @@ QUnit.test('mark as read', async function (assert) {
await click('.o_MessagingMenu_toggler');
assert.containsOnce(
document.body,
'.o_ThreadPreview_markAsRead',
'.o_ChannelPreviewView_markAsRead',
"should have the mark as read button"
);
await click('.o_ThreadPreview_markAsRead');
await click('.o_ChannelPreviewView_markAsRead');
assert.verifySteps(
['set_last_seen_message'],
"should have marked the thread as seen"
);
assert.hasClass(
document.querySelector('.o_ThreadPreview'),
document.querySelector('.o_ChannelPreviewView'),
'o-muted',
"should be muted once marked as read"
);
assert.containsNone(
document.body,
'.o_ThreadPreview_markAsRead',
'.o_ChannelPreviewView_markAsRead',
"should no longer have the mark as read button"
);
assert.containsNone(
@@ -274,7 +274,7 @@ QUnit.test('open chat from "new message" chat window should open chat in place o
// open channel-2
await click(`.o_MessagingMenu_toggler`);
await click(`.o_NotificationListItem[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]`);
await click(`.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]`);
assert.containsN(
document.body,
'.o_ChatWindow',
@@ -1015,7 +1015,7 @@ QUnit.test('open 2 different chat windows: enough screen width [REQUIRE FOCUS]',
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`);
assert.strictEqual(
document.querySelectorAll(`.o_ChatWindow`).length,
@@ -1039,7 +1039,7 @@ QUnit.test('open 2 different chat windows: enough screen width [REQUIRE FOCUS]',
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`);
assert.strictEqual(
document.querySelectorAll(`.o_ChatWindow`).length,
@@ -1106,7 +1106,7 @@ QUnit.test('open 3 different chat windows: not enough screen width', async funct
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`);
assert.strictEqual(
document.querySelectorAll(`.o_ChatWindow`).length,
@@ -1127,7 +1127,7 @@ QUnit.test('open 3 different chat windows: not enough screen width', async funct
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`);
assert.strictEqual(
document.querySelectorAll(`.o_ChatWindow`).length,
@@ -1148,7 +1148,7 @@ QUnit.test('open 3 different chat windows: not enough screen width', async funct
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId3}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId3}"]
`);
assert.strictEqual(
document.querySelectorAll(`.o_ChatWindow`).length,
@@ -1197,7 +1197,7 @@ QUnit.test('chat window: switch on TAB', async function (assert) {
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]`
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`
);
assert.containsOnce(document.body, '.o_ChatWindow', "Only 1 chatWindow must be opened");
@@ -1229,7 +1229,7 @@ QUnit.test('chat window: switch on TAB', async function (assert) {
await click(`.o_MessagingMenu_toggler`);
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]`
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]`
);
assert.containsN(document.body, '.o_ChatWindow', 2, "2 chatWindows must be opened");
@@ -1955,12 +1955,12 @@ QUnit.test('should not have chat window hidden menu in mobile (transition from 2
await click('.o_MessagingMenu_toggler');
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`);
await click('.o_ChatWindowHeader_commandBack');
await click(`
.o_MessagingMenu_dropdownMenu
.o_NotificationList_preview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`);
// simulate resize to go into mobile
await afterNextRender(
@@ -440,7 +440,7 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
await click(`.o_MessagingMenu_toggler`);
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu .o_ThreadPreview
.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView
`).length,
1,
"should have one preview"
@@ -448,7 +448,7 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_sidebar
.o_ChannelPreviewView_sidebar
`).length,
1,
"preview should have a sidebar"
@@ -456,7 +456,7 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_content
.o_ChannelPreviewView_content
`).length,
1,
"preview should have some content"
@@ -464,7 +464,7 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_header
.o_ChannelPreviewView_header
`).length,
1,
"preview should have header in content"
@@ -472,8 +472,8 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_header
.o_ThreadPreview_name
.o_ChannelPreviewView_header
.o_ChannelPreviewView_name
`).length,
1,
"preview should have name in header of content"
@@ -481,15 +481,15 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelector(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_name
.o_ChannelPreviewView_name
`).textContent,
"General", "preview should have name of channel"
);
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_content
.o_ThreadPreview_core
.o_ChannelPreviewView_content
.o_ChannelPreviewView_core
`).length,
1,
"preview should have core in content"
@@ -497,8 +497,8 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_core
.o_ThreadPreview_inlineText
.o_ChannelPreviewView_core
.o_ChannelPreviewView_inlineText
`).length,
1,
"preview should have inline text in core of content"
@@ -506,8 +506,8 @@ QUnit.test('channel preview: basic rendering', async function (assert) {
assert.strictEqual(
document.querySelector(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview_core
.o_ThreadPreview_inlineText
.o_ChannelPreviewView_core
.o_ChannelPreviewView_inlineText
`).textContent.trim(),
"Demo: test",
"preview should have message content as inline text of core content"
@@ -536,14 +536,14 @@ QUnit.test('filtered previews', async function (assert) {
await click(`.o_MessagingMenu_toggler`);
assert.strictEqual(
document.querySelectorAll(`.o_MessagingMenu_dropdownMenu .o_ThreadPreview`).length,
document.querySelectorAll(`.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView`).length,
2,
"should have 2 previews"
);
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`).length,
1,
"should have preview of chat"
@@ -551,7 +551,7 @@ QUnit.test('filtered previews', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`).length,
1,
"should have preview of channel"
@@ -559,14 +559,14 @@ QUnit.test('filtered previews', async function (assert) {
await click('.o_MessagingMenuTab[data-tab-id="chat"]');
assert.strictEqual(
document.querySelectorAll(`.o_MessagingMenu_dropdownMenu .o_ThreadPreview`).length,
document.querySelectorAll(`.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView`).length,
1,
"should have one preview"
);
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`).length,
1,
"should have preview of chat"
@@ -574,7 +574,7 @@ QUnit.test('filtered previews', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`).length,
0,
"should not have preview of channel"
@@ -584,7 +584,7 @@ QUnit.test('filtered previews', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview
.o_ChannelPreviewView
`).length,
1,
"should have one preview"
@@ -592,7 +592,7 @@ QUnit.test('filtered previews', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`).length,
0,
"should not have preview of chat"
@@ -600,7 +600,7 @@ QUnit.test('filtered previews', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`).length,
1,
"should have preview of channel"
@@ -608,14 +608,14 @@ QUnit.test('filtered previews', async function (assert) {
await click('.o_MessagingMenuTab[data-tab-id="all"]');
assert.strictEqual(
document.querySelectorAll(`.o_MessagingMenu_dropdownMenu .o_ThreadPreview`).length,
document.querySelectorAll(`.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView`).length,
2,
"should have 2 previews"
);
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`).length,
1,
"should have preview of chat"
@@ -623,7 +623,7 @@ QUnit.test('filtered previews', async function (assert) {
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId2}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId2}"]
`).length,
1,
"should have preview of channel"
@@ -638,7 +638,7 @@ QUnit.test('open chat window from preview', async function (assert) {
const { click } = await start();
await click(`.o_MessagingMenu_toggler`);
await click(`.o_MessagingMenu_dropdownMenu .o_ThreadPreview`);
await click(`.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView`);
assert.strictEqual(
document.querySelectorAll(`.o_ChatWindow`).length,
1,
@@ -661,27 +661,27 @@ QUnit.test('no code injection in message body preview', async function (assert)
await click(`.o_MessagingMenu_toggler`);
assert.containsOnce(
document.body,
'.o_MessagingMenu_dropdownMenu .o_ThreadPreview',
'.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView',
"should display a preview",
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_core',
'.o_ChannelPreviewView_core',
"preview should have core in content",
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_inlineText',
'.o_ChannelPreviewView_inlineText',
"preview should have inline text in core of content",
);
assert.strictEqual(
document.querySelector('.o_ThreadPreview_inlineText')
document.querySelector('.o_ChannelPreviewView_inlineText')
.textContent.replace(/\s/g, ""),
"You:&shoulnotberaisedthrownewError('CodeInjectionError');",
"should display correct uninjected last message inline content"
);
assert.containsNone(
document.querySelector('.o_ThreadPreview_inlineText'),
document.querySelector('.o_ChannelPreviewView_inlineText'),
'script',
"last message inline content should not have any code injection"
);
@@ -702,27 +702,27 @@ QUnit.test('no code injection in message body preview from sanitized message', a
await click(`.o_MessagingMenu_toggler`);
assert.containsOnce(
document.body,
'.o_MessagingMenu_dropdownMenu .o_ThreadPreview',
'.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView',
"should display a preview",
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_core',
'.o_ChannelPreviewView_core',
"preview should have core in content",
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_inlineText',
'.o_ChannelPreviewView_inlineText',
"preview should have inline text in core of content",
);
assert.strictEqual(
document.querySelector('.o_ThreadPreview_inlineText')
document.querySelector('.o_ChannelPreviewView_inlineText')
.textContent.replace(/\s/g, ""),
"You:<em>&shoulnotberaised</em><script>thrownewError('CodeInjectionError');</script>",
"should display correct uninjected last message inline content"
);
assert.containsNone(
document.querySelector('.o_ThreadPreview_inlineText'),
document.querySelector('.o_ChannelPreviewView_inlineText'),
'script',
"last message inline content should not have any code injection"
);
@@ -743,21 +743,21 @@ QUnit.test('<br/> tags in message body preview are transformed in spaces', async
await click(`.o_MessagingMenu_toggler`);
assert.containsOnce(
document.body,
'.o_MessagingMenu_dropdownMenu .o_ThreadPreview',
'.o_MessagingMenu_dropdownMenu .o_ChannelPreviewView',
"should display a preview",
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_core',
'.o_ChannelPreviewView_core',
"preview should have core in content",
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_inlineText',
'.o_ChannelPreviewView_inlineText',
"preview should have inline text in core of content",
);
assert.strictEqual(
document.querySelector('.o_ThreadPreview_inlineText').textContent,
document.querySelector('.o_ChannelPreviewView_inlineText').textContent,
"You: a b c d",
"should display correct last message inline content with brs replaced by spaces"
);
@@ -902,7 +902,7 @@ QUnit.test('Group chat should be displayed inside the chat section of the messag
assert.strictEqual(
document.querySelectorAll(`
.o_MessagingMenu_dropdownMenu
.o_ThreadPreview[data-thread-id="${mailChannelId1}"][data-thread-model="mail.channel"]
.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]
`).length,
1,
"should have one preview of group"
@@ -30,18 +30,18 @@ QUnit.test('marked as read thread notifications are ordered by last message date
await click('.o_MessagingMenu_toggler');
assert.containsN(
document.body,
'.o_ThreadPreview',
'.o_ChannelPreviewView',
2,
"there should be two thread previews"
);
const threadPreviewElList = document.querySelectorAll('.o_ThreadPreview');
const channelPreviewViewElList = document.querySelectorAll('.o_ChannelPreviewView');
assert.strictEqual(
threadPreviewElList[0].querySelector(':scope .o_ThreadPreview_name').textContent,
channelPreviewViewElList[0].querySelector(':scope .o_ChannelPreviewView_name').textContent,
'Channel 2020',
"First channel in the list should be the channel of 2020 (more recent last message)"
);
assert.strictEqual(
threadPreviewElList[1].querySelector(':scope .o_ThreadPreview_name').textContent,
channelPreviewViewElList[1].querySelector(':scope .o_ChannelPreviewView_name').textContent,
'Channel 2019',
"Second channel in the list should be the channel of 2019 (least recent last message)"
);
@@ -71,7 +71,7 @@ QUnit.test('thread notifications are re-ordered on receiving a new message', asy
await click('.o_MessagingMenu_toggler');
assert.containsN(
document.body,
'.o_ThreadPreview',
'.o_ChannelPreviewView',
2,
"there should be two thread previews"
);
@@ -94,18 +94,18 @@ QUnit.test('thread notifications are re-ordered on receiving a new message', asy
});
assert.containsN(
document.body,
'.o_ThreadPreview',
'.o_ChannelPreviewView',
2,
"there should still be two thread previews"
);
const threadPreviewElList = document.querySelectorAll('.o_ThreadPreview');
const channelPreviewViewElList = document.querySelectorAll('.o_ChannelPreviewView');
assert.strictEqual(
threadPreviewElList[0].querySelector(':scope .o_ThreadPreview_name').textContent,
channelPreviewViewElList[0].querySelector(':scope .o_ChannelPreviewView_name').textContent,
'Channel 2019',
"First channel in the list should now be 'Channel 2019'"
);
assert.strictEqual(
threadPreviewElList[1].querySelector(':scope .o_ThreadPreview_name').textContent,
channelPreviewViewElList[1].querySelector(':scope .o_ChannelPreviewView_name').textContent,
'Channel 2020',
"Second channel in the list should now be 'Channel 2020'"
);
@@ -42,7 +42,7 @@ QUnit.test('no conflicts between file uploaders', async function (assert) {
// Uploading file in the second thread: mail.channel in chatWindow.
await click(`.o_MessagingMenu_toggler`);
await click(`.o_NotificationListItem[data-thread-id="${channelId}"][data-thread-model="mail.channel"]`);
await click(`.o_ChannelPreviewView[data-channel-id="${channelId}"]`);
const file2 = await createFile({
name: 'text2.txt',
content: 'hello, world',
@@ -1,4 +1,4 @@
.o_ThreadPreview_ratingImage {
.o_ChannelPreviewView_ratingImage {
height: $font-size-base;
width: $font-size-base;
}
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.ThreadPreview" t-inherit-mode="extension">
<xpath expr="//*[hasclass('o_ThreadPreview_messageBody')]" position="replace">
<t t-if="threadPreviewView.isRating">
<span class="o_ThreadPreview_ratingText">Rating:</span>
<img class="o_ThreadPreview_ratingImage ms-2" t-att-src="threadPreviewView.thread.lastMessage.rating.ratingImageUrl" t-att-alt="threadPreviewView.thread.lastMessage.rating.ratingText"/>
<t t-inherit="mail.ChannelPreviewView" t-inherit-mode="extension">
<xpath expr="//*[hasclass('o_ChannelPreviewView_messageBody')]" position="replace">
<t t-if="channelPreviewView.isRating">
<span class="o_ChannelPreviewView_ratingText">Rating:</span>
<img class="o_ChannelPreviewView_ratingImage ms-2" t-att-src="channelPreviewView.thread.lastMessage.rating.ratingImageUrl" t-att-alt="channelPreviewView.thread.lastMessage.rating.ratingText"/>
</t>
<t t-else="">$0</t>
</xpath>
@@ -5,7 +5,7 @@ import { attr } from '@mail/model/model_field';
// ensure the model definition is loaded before the patch
import '@mail/models/channel_preview_view';
patchRecordMethods('ThreadPreviewView', {
patchRecordMethods('ChannelPreviewView', {
/**
* @override
*/
@@ -14,7 +14,7 @@ patchRecordMethods('ThreadPreviewView', {
},
});
addRecordMethods('ThreadPreviewView', {
addRecordMethods('ChannelPreviewView', {
/**
* @private
*/
@@ -23,7 +23,7 @@ addRecordMethods('ThreadPreviewView', {
},
});
addFields('ThreadPreviewView', {
addFields('ChannelPreviewView', {
isRating: attr({
compute: '_computeIsRating',
}),
@@ -31,22 +31,22 @@ QUnit.test('rating value displayed on the thread preview', async function (asser
},
}));
assert.strictEqual(
document.querySelector('.o_ThreadPreview_ratingText').textContent,
document.querySelector('.o_ChannelPreviewView_ratingText').textContent,
"Rating:",
"should display the correct content (Rating:)"
);
assert.containsOnce(
document.body,
'.o_ThreadPreview_ratingImage',
'.o_ChannelPreviewView_ratingImage',
"should have a rating image in the body"
);
assert.strictEqual(
$('.o_ThreadPreview_ratingImage').attr('data-src'),
$('.o_ChannelPreviewView_ratingImage').attr('data-src'),
"/rating/static/src/img/rating_5.png",
"should contain the correct rating image"
);
assert.strictEqual(
$('.o_ThreadPreview_ratingImage').attr('data-alt'),
$('.o_ChannelPreviewView_ratingImage').attr('data-alt'),
"top",
"should contain the correct rating text"
);