[FIX] mail: several discuss improvements (following MILK redesign)

closes odoo/odoo#121948

Forward-port-of: #121683
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2023-05-22 16:30:12 +02:00
parent 13c6e34635
commit 11b4355a7b
39 changed files with 168 additions and 148 deletions
@@ -3,8 +3,8 @@
<t t-inherit="mail.ImStatus" t-inherit-mode="extension">
<xpath expr="//*[@name='icon']" position="replace">
<i t-if="props.persona.im_status === 'leave_online'" class="fa fa-plane text-success" title="Online" role="img" aria-label="User is online"/>
<i t-if="props.persona.im_status === 'leave_away'" class="fa fa-plane text-warning" title="Idle" role="img" aria-label="User is idle"/>
<i t-if="props.persona.im_status === 'leave_offline'" class="fa fa-plane text-700" title="Out of office" role="img" aria-label="User is out of office"/>
<i t-elif="props.persona.im_status === 'leave_away'" class="fa fa-plane text-warning" title="Idle" role="img" aria-label="User is idle"/>
<i t-elif="props.persona.im_status === 'leave_offline'" class="fa fa-plane text-700" title="Out of office" role="img" aria-label="User is out of office"/>
<t t-else="">$0</t>
</xpath>
</t>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="im_livechat.Discuss" t-inherit="mail.Discuss" t-inherit-mode="extension" owl="1">
<xpath expr="//*[contains(@class, 'o-mail-Discuss-threadAvatar')]" position="attributes">
<attribute name="t-if">thread and thread.model === 'discuss.channel' and thread.type !== 'livechat'</attribute>
</xpath>
</t>
</templates>
+1 -1
View File
@@ -118,6 +118,6 @@ class BinaryController(http.Controller):
raise NotFound()
return (
request.env["ir.binary"]
._get_image_stream_from(attachment_sudo, width=width, height=height)
._get_image_stream_from(attachment_sudo, width=int(width), height=int(height))
.get_response(as_attachment=kwargs.get("download"))
)
+1 -1
View File
@@ -38,7 +38,7 @@ class ChannelController(http.Controller):
except UserError:
raise NotFound()
@http.route("/discuss/channel/update_avatar", methods=["POST"], type="json", auth="public")
@http.route("/discuss/channel/update_avatar", methods=["POST"], type="json")
def discuss_channel_avatar_update(self, channel_id, data):
channel = request.env["discuss.channel"].browse(int(channel_id)).exists()
if not channel or not data:
+7
View File
@@ -130,6 +130,13 @@ msgstr ""
msgid "%s and %s are typing..."
msgstr ""
#. module: mail
#. odoo-javascript
#: code:addons/mail/static/src/core_ui/message_seen_indicator.js:0
#, python-format
msgid "Seen by everyone"
msgstr ""
#. module: mail
#. odoo-javascript
#: code:addons/mail/static/src/core_ui/message_reactions.js:0
@@ -115,8 +115,6 @@ class Channel(models.Model):
for channel in self:
if channel.channel_type == 'channel':
channel.is_editable = self.sudo(False).env.is_admin() or channel.create_uid.id == self.env.user.id
elif channel.channel_type == 'chat':
channel.is_editable = True
elif channel.channel_type == 'group':
channel.is_editable = channel.is_member and not self.sudo(False).env.user._is_public()
else:
+4 -4
View File
@@ -62,10 +62,10 @@
</div>
<div class="o-mail-Composer-actions d-flex bg-view"
t-att-class="{
'ms-1' : compact and ui.isSmall,
'mx-1 me-2' : compact and !ui.isSmall,
'ms-3' : normal,
'mx-3 border-top p-1' : extended,
'ms-1': compact and ui.isSmall,
'mx-1 me-2': compact and !ui.isSmall,
'ms-3': normal,
'mx-3 border-top p-1': extended,
}"
>
<div class="d-flex flex-grow-1 align-items-center" >
@@ -1089,6 +1089,18 @@ export class ThreadService {
}
return DEFAULT_AVATAR;
}
/**
* @param {number} threadId
* @param {string} data base64 representation of the binary
* @returns
*/
async notifyThreadAvatarToServer(threadId, data) {
return this.rpc("/discuss/channel/update_avatar", {
channel_id: threadId,
data,
});
}
}
export const threadService = {
+1 -1
View File
@@ -54,7 +54,7 @@
}
button:hover, .focus {
background-color: $o-gray-300 !important;
background-color: $o-gray-200 !important;
}
}
+13 -13
View File
@@ -36,7 +36,7 @@
<small t-if="isActive" class="o-mail-Message-date text-muted opacity-50 ms-2">
<t t-esc="messageService.dateSimple(message)"/>
</small>
<div t-att-class="{ 'position-absolute bottom-0 mb-1': !env.inChatter }">
<div t-else="" class="ms-2">
<MessageSeenIndicator
t-if="props.message.isSelfAuthored and props.thread"
message="props.message"
@@ -134,21 +134,21 @@
'flex-row-reverse': env.inChatWindow and isAlignedRight,
}"
>
<button t-if="canAddReaction" class="btn px-1 py-0" tabindex="1" title="Add a Reaction" aria-label="Add a Reaction" t-ref="emoji-picker"><i class="fa fa-lg fa-smile-o"/></button>
<button t-if="canReplyTo" class="btn px-1 py-0" t-on-click.stop="onClickReplyTo" tabindex="1" title="Reply" aria-label="Reply"><i class="fa fa-lg fa-reply"/></button>
<button t-if="canToggleStar" class="btn px-1 py-0" t-on-click="() => messageService.toggleStar(props.message)" tabindex="0" title="Mark as Todo" aria-label="Mark as Todo"><i class="fa fa-lg" t-att-class="message.isStarred ? 'fa-star o-mail-Message-starred' : 'fa-star-o'"/></button>
<Dropdown t-if="editable or deletable or isInInbox or props.thread?.model === 'discuss.channel'" onStateChanged="state => this.state.expandOptions = state.open" position="props.thread?.newestMessage === props.message ? 'top-start' : 'bottom-start'" togglerClass="`btn p-0 ${ state.expandOptions ? 'bg-200' : '' }`" menuClass="'d-flex flex-column py-0 o-mail-Message-moreMenu'" class="'d-flex'" title="expandText">
<button t-if="canAddReaction" class="btn px-1 py-0 rounded-0" tabindex="1" title="Add a Reaction" aria-label="Add a Reaction" t-ref="emoji-picker"><i class="fa fa-lg fa-smile-o"/></button>
<button t-if="canReplyTo" class="btn px-1 py-0 rounded-0" t-on-click.stop="onClickReplyTo" tabindex="1" title="Reply" aria-label="Reply"><i class="fa fa-lg fa-reply"/></button>
<button t-if="canToggleStar" class="btn px-1 py-0 rounded-0" t-on-click="() => messageService.toggleStar(props.message)" tabindex="0" title="Mark as Todo" aria-label="Mark as Todo"><i class="fa fa-lg" t-att-class="message.isStarred ? 'fa-star o-mail-Message-starred' : 'fa-star-o'"/></button>
<Dropdown t-if="editable or deletable or isInInbox or props.thread?.model === 'discuss.channel'" onStateChanged="state => this.state.expandOptions = state.open" position="props.thread?.newestMessage === props.message ? 'top-start' : 'bottom-start'" togglerClass="`btn p-0 ${ state.expandOptions ? 'bg-200' : '' }`" menuClass="'d-flex flex-column py-0 o-mail-Message-moreMenu'" class="'d-flex rounded-0'" title="expandText">
<t t-set-slot="toggler">
<i class="btn px-1 py-0 fa fa-lg fa-ellipsis-h" t-att-class="{ 'order-1': props.isInChatWindow }" tabindex="1"/>
<i class="btn px-1 py-0 fa fa-lg fa-ellipsis-h rounded-0" t-att-class="{ 'order-1': props.isInChatWindow }" tabindex="1"/>
</t>
<t t-set-slot="default">
<DropdownItem t-if="message.reactions.length" class="'px-2 d-flex align-items-center'" title="'Reactions'" onSelected="() => this.openReactionMenu()"><i class="fa fa-lg fa-smile-o pe-2"/>View Reactions</DropdownItem>
<DropdownItem t-if="isInInbox" class="'px-2 d-flex align-items-center'" title="'Mark as Read'" onSelected="() => messageService.setDone(props.message)"><i class="fa fa-lg fa-check pe-2"/>Mark as Read</DropdownItem>
<DropdownItem t-if="canUnfollow" class="'px-2 d-flex align-items-center'" title="'Unfollow'" onSelected="() => messageService.unfollow(props.message)"><i class="fa fa-lg fa-user-times pe-2"/>Unfollow</DropdownItem>
<DropdownItem t-if="store.user and props.thread.model === 'discuss.channel'" class="'px-2 d-flex align-items-center'" title="pinOptionText" onSelected="() => this.onClickPin()"><i class="fa fa-lg fa-thumb-tack pe-2"/><t t-esc="pinOptionText"/></DropdownItem>
<DropdownItem t-if="props.thread.model === 'discuss.channel'" class="'px-2 d-flex align-items-center'" title="'Mark as Unread'" onSelected="() => this.onClickMarkAsUnread()"><i class="fa fa-lg fa-eye-slash pe-2"/>Mark as Unread</DropdownItem>
<DropdownItem t-if="editable" class="'px-2 d-flex align-items-center'" title="'Edit'" onSelected="() => this.onClickEdit()"><i class="fa fa-lg fa-pencil pe-2"/>Edit</DropdownItem>
<DropdownItem t-if="deletable" class="'px-2 d-flex align-items-center'" title="'Delete'" onSelected="() => this.onClickDelete()"><i class="fa fa-lg fa-trash pe-2"/>Delete</DropdownItem>
<DropdownItem t-if="message.reactions.length" class="'px-2 d-flex align-items-center rounded-0'" title="'Reactions'" onSelected="() => this.openReactionMenu()"><i class="fa fa-lg fa-smile-o pe-2"/>View Reactions</DropdownItem>
<DropdownItem t-if="isInInbox" class="'px-2 d-flex align-items-center rounded-0'" title="'Mark as Read'" onSelected="() => messageService.setDone(props.message)"><i class="fa fa-lg fa-check pe-2"/>Mark as Read</DropdownItem>
<DropdownItem t-if="canUnfollow" class="'px-2 d-flex align-items-center rounded-0'" title="'Unfollow'" onSelected="() => messageService.unfollow(props.message)"><i class="fa fa-lg fa-user-times pe-2"/>Unfollow</DropdownItem>
<DropdownItem t-if="store.user and props.thread.model === 'discuss.channel'" class="'px-2 d-flex align-items-center rounded-0'" title="pinOptionText" onSelected="() => this.onClickPin()"><i class="fa fa-lg fa-thumb-tack pe-2"/><t t-esc="pinOptionText"/></DropdownItem>
<DropdownItem t-if="props.thread.model === 'discuss.channel'" class="'px-2 d-flex align-items-center rounded-0'" title="'Mark as Unread'" onSelected="() => this.onClickMarkAsUnread()"><i class="fa fa-lg fa-eye-slash pe-2"/>Mark as Unread</DropdownItem>
<DropdownItem t-if="editable" class="'px-2 d-flex align-items-center rounded-0'" title="'Edit'" onSelected="() => this.onClickEdit()"><i class="fa fa-lg fa-pencil pe-2"/>Edit</DropdownItem>
<DropdownItem t-if="deletable" class="'px-2 d-flex align-items-center rounded-0'" title="'Delete'" onSelected="() => this.onClickDelete()"><i class="fa fa-lg fa-trash pe-2"/>Delete</DropdownItem>
</t>
</Dropdown>
</div>
@@ -8,7 +8,7 @@
}">
<small class="position-relative d-block text-small mb-1" t-attf-class="{{ env.inChatWindow and props.alignedRight ? 'justify-content-end pe-5': 'ps-5' }}" t-on-click="() => this.props.onClick?.()">
<span class="o-mail-MessageInReply-corner position-absolute bottom-0 top-50 pe-4 border-top text-300" t-attf-class="{{ env.inChatWindow and props.alignedRight ? 'o-isRightAlign border-end' : 'o-isLeftAlign border-start' }}" t-att-class="{ 'ms-n2': store.discuss.isActive }"/>
<span t-if="!message.parentMessage.isEmpty" class="d-inline-flex align-items-center text-muted opacity-75" t-att-class="{ 'cursor-pointer opacity-100-hover': props.onClick }" t-attf-class="{{ env.inChatWindow and props.alignedRight ? 'pe-3': 'ps-3' }}">
<span t-if="!message.parentMessage.isEmpty" class="d-inline-flex align-items-center text-muted opacity-75" t-att-class="{ 'cursor-pointer opacity-100-hover': props.onClick }">
<img class="o-mail-MessageInReply-avatar me-2 rounded" t-att-src="authorAvatarUrl" t-att-title="message.parentMessage.author.name" alt="Avatar"/>
<span class="o-mail-MessageInReply-content overflow-hidden">
<b>@<t t-out="message.parentMessage.author.name"/></b>:
@@ -27,6 +27,15 @@ export class MessageSeenIndicator extends Component {
return otherDidNotSee.length === 0;
}
get hasEveryoneReceived() {
return !([...this.props.thread.seenInfos].some((seenInfo) => {
return (
seenInfo.partner.id !== this.props.message.author.id &&
(!seenInfo.lastFetchedMessage || seenInfo.lastFetchedMessage.id < this.props.message.id)
);
}));
}
get isMessagePreviousToLastSelfMessageSeenByEveryone() {
if (!this.props.thread.lastSelfMessageSeenByEveryone) {
return false;
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.MessageSeenIndicator" owl="1">
<span class="o-mail-MessageSeenIndicator position-relative d-flex opacity-75-hover opacity-50" t-att-class="{ 'o-all-seen text-odoo': hasEveryoneSeen }" t-attf-class="{{ props.className }}" t-att-title="text">
<span class="o-mail-MessageSeenIndicator position-relative d-flex opacity-50" t-att-class="{ 'o-all-seen text-primary': hasEveryoneSeen }" t-attf-class="{{ props.className }}">
<t t-if="!isMessagePreviousToLastSelfMessageSeenByEveryone">
<i t-if="hasSomeoneFetched or hasSomeoneSeen" class="fa fa-check ps-1"/>
<i t-if="hasSomeoneSeen" class="o-second fa fa-check position-absolute"/>
@@ -13,6 +13,7 @@ $o-mail-Message-sidebarWidth: 48px !default;
$o-mail-NavigableList-zIndex: 11;
$o-mail-Chatter-minWidth: 530px !default;
$o-mail-Discuss-inspector: 250px !default;
$o-mail-Discuss-headerHeight: 48px !default;
@mixin o-mail-AttachmentViewer-arrow {
background-color: rgba(black, 0.4);
@@ -1,3 +1,7 @@
.o-mail-Thread-date hr {
opacity: $hr-opacity / 2;
}
.o-mail-Thread-newMessage {
transition: opacity 0.5s;
@@ -25,3 +29,7 @@
background-color: mix(map-get($theme-colors, 'info'), $o-webclient-background-color, 5%);
z-index: $o-mail-NavigableList-zIndex - 2;
}
.o-mail-NotificationMessage p {
margin-bottom: 0;
}
+5 -5
View File
@@ -20,15 +20,15 @@
<t t-foreach="props.order === 'asc' ? props.thread.nonEmptyMessages : [...props.thread.nonEmptyMessages].reverse()" t-as="msg" t-key="msg.id">
<t t-if="msg.dateDay !== currentDay">
<div class="o-mail-Thread-date d-flex align-items-center fw-bolder pt-4">
<hr class="flex-grow-1"/>
<span class="px-3"><t t-esc="msg.dateDay"/></span>
<hr class="flex-grow-1"/>
<hr class="ms-3 flex-grow-1"/>
<span class="px-3 text-muted"><t t-esc="msg.dateDay"/></span>
<hr class="me-3 flex-grow-1"/>
</div>
<t t-set="currentDay" t-value="msg.dateDay"/>
</t>
<Transition t-if="props.thread.isChannel" visible="props.thread.seen_message_id === (prevMsg?.id ?? false)" name="'o-fade'" t-slot-scope="transition">
<div class="o-mail-Thread-newMessage d-flex align-items-center fw-bolder" t-att-class="{ 'opacity-0': transition.className.includes('o-fade-leave') }">
<hr class="flex-grow-1 border opacity-50"/><span class="px-3">New messages</span>
<hr class="ms-3 flex-grow-1 border opacity-50"/><span class="px-3 me-3">New messages</span>
</div>
</Transition>
<t t-if="msg.isNotification">
@@ -114,7 +114,7 @@
</t>
<t t-name="mail.NotificationMessage" owl="1">
<div class="mx-auto text-500 small px-3 text-center" t-on-click="onClickNotification" t-att-class="{
<div class="o-mail-NotificationMessage mx-auto text-500 small px-3 text-center" t-on-click="onClickNotification" t-att-class="{
'o-mail-Thread-noPinnedPanel': env.inChatWindow and !ui.isSmall,
'mt-2': prevMsg and !prevMsg.isNotification,
}">
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="discuss.CallSettings" owl="1">
<div class="o-discuss-CallSettings py-2 user-select-none bg-light" t-attf-class="{{ props.className }}" t-ref="root">
<div class="o-discuss-CallSettings py-2 user-select-none bg-view" t-attf-class="{{ props.className }}" t-ref="root">
<div class="d-flex flex-column px-3 overflow-auto">
<div class="py-2 fw-bolder text-700 text-truncate text-uppercase">Voice Settings</div>
<div class="mb-3 d-flex align-items-center flex-wrap">
@@ -1,5 +1,5 @@
.o-discuss-ChannelMember:hover {
@extend .bg-300;
.o-discuss-ChannelMember.cursor-pointer:hover {
@extend .bg-200;
}
.o-discuss-ChannelMember-avatar {
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="discuss.ChannelMemberList" owl="1">
<div class="o-discuss-ChannelMemberList d-flex flex-column overflow-auto bg-light" t-att-class="props.className">
<div class="o-discuss-ChannelMemberList d-flex flex-column overflow-auto bg-view" t-att-class="props.className">
<t t-if="props.thread.onlineMembers.length > 0">
<h6 class="m-2">
Online -
@@ -26,11 +26,11 @@
</t>
<t t-name="discuss.channel_member" owl="1">
<div class="o-discuss-ChannelMember d-flex align-items-center mx-2 p-2 bg-light" t-att-class="{ 'cursor-pointer': canOpenChatWith(member) }" t-on-click.stop="() => this.openChatAvatar(member)">
<div class="o-discuss-ChannelMember d-flex align-items-center p-2 bg-view" t-att-class="{ 'cursor-pointer': canOpenChatWith(member) }" t-on-click.stop="() => this.openChatAvatar(member)">
<div class="o-bg-inherit o-discuss-ChannelMember-avatar position-relative d-flex ms-4 flex-shrink-0">
<img class="w-100 h-100 rounded-circle o_object_fit_cover o_redirect"
t-att-src="threadService.avatarUrl(member.persona, props.thread)"/>
<ImStatus persona="member.persona"/>
<ImStatus persona="member.persona" className="'position-absolute top-100 start-100 translate-middle mt-n1 ms-n1'"/>
</div>
<span class="ms-2 text-truncate" t-esc="member.persona.name"/>
<span class="ms-auto">
@@ -3,7 +3,7 @@
<t t-name="mail.AutoresizeInput" owl="1">
<input
class="o-mail-AutoresizeInput text-truncate px-1 border-1 bg-view border-transparent"
class="o-mail-AutoresizeInput text-truncate px-1 border-1 border-transparent"
t-attf-class="{{ props.className }}"
t-attf-placeholder="{{ props.placeholder }}"
t-att-disabled="!props.enabled"
@@ -109,7 +109,9 @@ export class Discuss extends Component {
get channelAvatar() {
return this.props.public
? url(`/discuss/channel/${this.thread.id}/avatar_128`)
? url(
`/discuss/channel/${this.thread.id}/avatar_128?unique=${this.thread?.avatarCacheKey}`
)
: this.thread.imgUrl;
}
@@ -121,7 +123,7 @@ export class Discuss extends Component {
}
async onFileUploaded(file) {
await this.threadService.updateAvatar(this.thread.id, file.data);
await this.threadService.notifyThreadAvatarToServer(this.thread.id, file.data);
this.notification.add(_t("The avatar has been updated!"), { type: "success" });
}
+13 -13
View File
@@ -2,10 +2,6 @@
.o-mail-Thread {
flex-grow: 1;
}
.o-mail-Discuss-header button:not(.btn-secondary):hover {
background-color: $gray-400;
}
}
.o-mail-Discuss-selfAvatar {
@@ -17,15 +13,19 @@
max-width: 75%;
}
.o-mail-Discuss-header button {
&:not(.o-isActive):hover {
background-color: $gray-300;
}
&.o-isActive {
background-color: $gray-300;
}
&.o-isActive:hover {
background-color: $gray-400;
.o-mail-Discuss-header {
height: $o-mail-Discuss-headerHeight;
button {
&:not(.o-isActive):hover {
background-color: $gray-200;
}
&.o-isActive {
background-color: $gray-300;
}
&.o-isActive:hover {
background-color: $gray-300;
}
}
}
+10 -10
View File
@@ -5,10 +5,10 @@
<div class="o-mail-Discuss d-flex h-100 flex-grow-1 border-top" t-att-class="{ 'flex-column align-items-center bg-view': ui.isSmall }" t-ref="root">
<div t-if="ui.isSmall and store.discuss.activeTab === 'mailbox'" class="w-100 border-bottom" t-call="mail.Discuss.mobileTopbar" t-ref="mobileTopbar"/>
<div t-if="thread and !(ui.isSmall and ['chat', 'channel'].includes(store.discuss.activeTab))" class="o-mail-Discuss-content d-flex flex-column h-100 w-100 bg-view overflow-auto" t-ref="content">
<div class="o-mail-Discuss-header px-3 bg-view border-bottom d-flex flex-shrink-0 align-items-center" style="height:46px;">
<div t-if="thread and thread.model === 'discuss.channel'" class="o-mail-Discuss-threadAvatar position-relative align-self-center align-items-center mt-2 mb-2 me-2">
<div class="o-mail-Discuss-header px-3 bg-view border-bottom d-flex flex-shrink-0 align-items-center">
<div t-if="thread and ['channel', 'group', 'chat'].includes(thread.type)" class="o-mail-Discuss-threadAvatar position-relative align-self-center align-items-center mt-2 mb-2 me-2">
<img class="rounded" t-att-src="channelAvatar" alt="Thread Image"/>
<FileUploader t-if="thread.is_editable and thread.type !== 'chat'" acceptedFileExtensions="'.bmp, .jpg, .jpeg, .png, .svg'" showUploadingText="false" multiUpload="false" onUploaded="(data) => this.onFileUploaded(data)">
<FileUploader t-if="thread.is_editable" acceptedFileExtensions="'.bmp, .jpg, .jpeg, .png, .svg'" showUploadingText="false" multiUpload="false" onUploaded="(data) => this.onFileUploaded(data)">
<t t-set-slot="toggler">
<a href="#" class="position-absolute z-index-1 h-100 w-100 rounded start-0 bottom-0" title="Upload Avatar">
<i class="position-absolute top-50 start-50 fa fa-sm fa-pencil text-white"/>
@@ -22,8 +22,8 @@
<ImStatus t-if="thread and thread.type === 'chat' and thread.correspondent" className="'me-1'" persona="thread.correspondent"/>
<div class="d-flex flex-grow-1 align-self-center align-items-center h-100 py-2">
<AutoresizeInput
className="'o-mail-Discuss-threadName lead fw-bold flex-shrink-1 text-dark py-0'"
enabled="thread.is_editable"
className="'o-mail-Discuss-threadName lead fw-bold flex-shrink-1 text-dark bg-view py-0'"
enabled="thread.is_editable or thread.type === 'chat'"
onValidate.bind="renameThread"
value="thread.displayName"
/>
@@ -31,7 +31,7 @@
<div class="flex-shrink-0 mx-2 py-2 border-start"/>
<t t-set="autogrowDescriptionPlaceholder">Add a description</t>
<AutoresizeInput
className="'o-mail-Discuss-threadDescription flex-shrink-1 py-1'"
className="'o-mail-Discuss-threadDescription flex-shrink-1 py-1 bg-view'"
enabled="thread.is_editable"
onValidate.bind="updateThreadDescription"
placeholder="thread.is_editable ? autogrowDescriptionPlaceholder : ''"
@@ -40,8 +40,8 @@
</t>
</div>
<div class="o-mail-Discuss-header flex-shrink-0 d-flex align-items-center ms-1">
<button t-if="thread.id === 'inbox'" class="btn btn-secondary" t-att-disabled="thread.isEmpty" t-on-click="markAllAsRead">Mark all read</button>
<button t-if="thread.id === 'starred'" class="btn btn-secondary" t-att-disabled="thread.isEmpty" t-on-click="messageService.unstarAll">Unstar all</button>
<button t-if="thread.id === 'inbox'" class="btn btn-link" t-att-disabled="thread.isEmpty" t-on-click="markAllAsRead">Mark all read</button>
<button t-if="thread.id === 'starred'" class="btn btn-link" t-att-disabled="thread.isEmpty" t-on-click="messageService.unstarAll">Unstar all</button>
<button t-if="thread.model === 'discuss.channel'" class="btn rounded px-2 shadow-none" t-att-class="{ 'o-isActive': state.activeMode === MODES.PINNED_MESSAGES }" title="Pinned messages" t-on-click.stop="togglePinMenu">
<i class="fa fa-lg fa-thumb-tack text-700"/>
</button>
@@ -63,8 +63,8 @@
</div>
</div>
</div>
<div class="overflow-auto bg-100 d-flex flex-grow-1">
<div class="bg-view d-flex flex-column flex-grow-1">
<div class="overflow-auto bg-view d-flex flex-grow-1">
<div class="d-flex flex-column flex-grow-1">
<Call t-if="Object.keys(thread.rtcSessions).length > 0" thread="thread"/>
<Thread thread="thread" t-key="thread.localId" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo"/>
<Composer t-if="thread.type !== 'mailbox' or messageToReplyTo.thread === thread" t-key="thread.localId" composer="thread.composer" autofocus="true" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo" onDiscardCallback="() => messageToReplyTo.cancel()" dropzoneRef="contentRef"/>
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.PinnedMessagesPanel" owl="1">
<div class="o-mail-PinnedMessagesPanel overflow-auto d-flex flex-column flex-shrink-0 bg-100 position-relative border-start" t-att-class="{
<div class="o-mail-PinnedMessagesPanel overflow-auto d-flex flex-column flex-shrink-0 bg-view position-relative border-start" t-att-class="{
'flex-grow-1': env.inChatWindow,
}">
<p class="o-mail-PinnedMessagesPanel-title position-sticky top-0 py-3 mb-0 fw-bolder text-center text-uppercase text-700 bg-100">
<p class="o-mail-PinnedMessagesPanel-title position-sticky top-0 py-3 mb-0 fw-bolder text-center text-uppercase text-700">
Pinned messages
</p>
<div class="d-flex flex-column" t-att-class="{ 'justify-content-center flex-grow-1': props.thread.pinnedMessages.length === 0 }" t-ref="message-list">
@@ -13,7 +13,7 @@
width: 30px;
font-size: 1rem;
&:hover {
background-color: $gray-300;
background-color: $gray-200;
}
}
@@ -60,7 +60,7 @@
</t>
<t t-name="mail.EmojiPicker.tab" owl="1">
<span class="o-mail-Emoji text-center p-1 cursor-pointer" t-att-class="{'o-isActive bg-300': category.sortId === state.categoryId}" t-att-title="category.name" t-att-data-id="category.sortId" t-on-click="selectCategory">
<span class="o-mail-Emoji text-center p-1 cursor-pointer" t-att-class="{'o-isActive bg-200': category.sortId === state.categoryId}" t-att-title="category.name" t-att-data-id="category.sortId" t-on-click="selectCategory">
<t t-esc="category.title"/>
</span>
</t>
@@ -71,7 +71,7 @@
</t>
<t t-name="mail.EmojiPicker.emoji" owl="1">
<span class="o-mail-Emoji cursor-pointer d-flex justify-content-center fs-2" t-att-class="{ 'bg-300': state.activeEmojiIndex === itemIndex }" t-att-title="emoji.name" t-att-data-codepoints="emoji.codepoints" t-att-data-index="itemIndex" t-att-data-category="recentCategory.sortId" t-on-click="selectEmoji">
<span class="o-mail-Emoji cursor-pointer d-flex justify-content-center fs-2" t-att-class="{ 'bg-200': state.activeEmojiIndex === itemIndex }" t-att-title="emoji.name" t-att-data-codepoints="emoji.codepoints" t-att-data-index="itemIndex" t-att-data-category="recentCategory.sortId" t-on-click="selectEmoji">
<span t-esc="emoji.codepoints"/>
</span>
</t>
@@ -6,7 +6,7 @@
outline: none;
&.o-folded {
height: 48px;
height: $o-mail-Discuss-headerHeight;
}
.o-mail-ChatWindow-command:hover {
@@ -15,12 +15,10 @@
}
.o-mail-ChatWindow-header {
height: 48px;
height: $o-mail-Discuss-headerHeight;
.o-mail-ChatWindow-threadAvatar {
img {
height: 28px;
width: 28px;
}
.o-mail-ChatWindow-threadAvatar img {
height: 28px;
width: 28px;
}
}
@@ -24,7 +24,7 @@
<ThreadIcon t-if="thread" thread="thread" className="'ms-3 me-1 my-0'"/>
</t>
<ImStatus t-if="thread and thread.type === 'chat' and thread.correspondent" className="'me-1'" persona="thread.correspondent"/>
<span class="o-mail-ChatWindow-name text-truncate" t-att-title="props.chatWindow.displayName" t-esc="props.chatWindow.displayName" t-att-class="{
<span class="o-mail-ChatWindow-name text-truncate fw-bold" t-att-title="props.chatWindow.displayName" t-esc="props.chatWindow.displayName" t-att-class="{
'ms-3 me-1 my-0': !thread,
}"
/>
@@ -7,7 +7,7 @@
}
.o-mail-DiscussSidebar-item:hover {
background-color: $o-gray-300 !important;
background-color: $o-gray-200 !important;
.o-mail-DiscussCategoryItem-commands {
display: flex !important;
@@ -8,7 +8,7 @@
Start a meeting
</button>
</div>
<hr class="w-100"/>
<hr class="w-100 opacity-0"/>
<div class="d-flex flex-column flex-grow-0">
<t t-call="mail.Mailbox">
<t t-set="mailbox" t-value="store.discuss.inbox"/>
@@ -20,7 +20,7 @@
<t t-set="mailbox" t-value="store.discuss.history"/>
</t>
</div>
<hr class="w-100"/>
<hr class="w-100 opacity-0"/>
<input t-if="hasQuickSearch" class="form-control mx-4 mb-2 rounded-3 w-auto" placeholder="Quick search..." t-model="state.quickSearchVal"/>
<t t-call="mail.DiscussCategory">
<t t-set="category" t-value="store.discuss.channels"/>
@@ -35,11 +35,11 @@
<button class="o-mail-DiscussSidebar-item btn d-flex align-items-center py-1 px-0 border-0 rounded-0 fw-normal text-reset"
t-att-class="{
'o-bg-inherit': mailbox.localId !== store.discuss.threadLocalId,
'o-active bg-300': mailbox.localId === store.discuss.threadLocalId,
'o-active bg-200': mailbox.localId === store.discuss.threadLocalId,
}"
t-on-click="(ev) => this.openThread(ev, mailbox)"
>
<ThreadIcon className="'ms-4 me-2'" thread="mailbox"/>
<ThreadIcon className="'ms-4 me-2 o-bg-inherit'" thread="mailbox"/>
<div class="me-2 text-truncate">
<t t-esc="mailbox.name"/>
</div>
@@ -54,7 +54,7 @@
<t t-set="hover_class" t-value="'btn p-0 text-start text-700 opacity-100-hover opacity-75'"/>
<div class="o-mail-DiscussCategory d-flex align-items-center my-1" t-att-class="category.extraClass">
<div t-attf-class="d-flex align-items-baseline mx-1 {{ hover_class }}" t-on-click="() => this.toggleCategory(category)">
<i class="o-mail-DiscussCategory-icon small" t-att-class="category.isOpen ? 'oi oi-chevron-down' : 'oi oi-chevron-right'"/>
<i class="o-mail-DiscussCategory-icon small me-1" t-att-class="category.isOpen ? 'oi oi-chevron-down' : 'oi oi-chevron-right'"/>
<span class="btn-sm p-0 text-uppercase fw-bolder"><t t-esc="category.name"/></span>
</div>
<div class="flex-grow-1"/>
@@ -85,14 +85,14 @@
<button class="o-mail-DiscussCategoryItem o-mail-DiscussSidebar-item btn btn-secondary d-flex align-items-center w-100 px-0 py-2 border-0 rounded-0 text-reset"
t-att-class="{
'o-bg-inherit': threadLocalId !== store.discuss.threadLocalId,
'o-active bg-300': threadLocalId === store.discuss.threadLocalId,
'o-active bg-200': threadLocalId === store.discuss.threadLocalId,
'o-unread': thread.message_unread_counter > 0,
}"
t-on-click="(ev) => this.openThread(ev, thread)"
>
<div class="o-bg-inherit position-relative d-flex ms-4 flex-shrink-0" style="width:30px;height:30px">
<img class="w-100 h-100 rounded" t-att-src="thread.imgUrl" alt="Thread Image"/>
<ThreadIcon t-if="thread.type === 'chat' or (thread.type === 'channel' and !thread.authorizedGroupFullName)" thread="thread" size="'small'" className="'o-mail-DiscussCategoryItem-threadIcon position-absolute bottom-0 end-0 p-1 me-n1 mb-n1 d-flex align-items-center justify-content-center rounded-circle bg-100'"/>
<ThreadIcon t-if="thread.type === 'chat' or (thread.type === 'channel' and !thread.authorizedGroupFullName)" thread="thread" size="'small'" className="'o-mail-DiscussCategoryItem-threadIcon position-absolute bottom-0 end-0 p-1 me-n1 mb-n1 d-flex align-items-center justify-content-center rounded-circle o-bg-inherit'"/>
</div>
<span class="ms-3 me-2 text-truncate" t-att-class="{ 'o-item-unread fw-bolder': thread.message_unread_counter > 0 }">
<t t-esc="thread.displayName"/>
@@ -102,7 +102,7 @@
<i t-if="thread.type === 'channel'" t-attf-class="fa fa-cog {{ hover_class }}" title="Channel settings" t-on-click.stop="() => this.openSettings(thread)" role="img"/>
<div t-if="threadService.canLeave(thread)" class="fa fa-times ms-1" t-attf-class="{{ hover_class }}"
t-on-click.stop="() => this.leaveChannel(thread)" title="Leave this channel" role="img"/>
<div t-if="threadService.canUnpin(thread)" t-attf-class="fa fa-times ms-1" t-on-click.stop="() => threadService.unpin(thread)" title="Unpin Conversation" role="img"/>
<div t-if="threadService.canUnpin(thread)" t-attf-class="fa fa-times ms-1 {{ hover_class }}" t-on-click.stop="() => threadService.unpin(thread)" title="Unpin Conversation" role="img"/>
</div>
<div t-if="Object.keys(thread.rtcSessions).length > 0" class="fa fa-volume-up ms-1 me-3" t-att-class="{ 'text-danger': rtc.state.channel?.id === thread.id }"/>
<div t-if="counter > 0" t-attf-class="o-mail-DiscussCategoryItem-counter badge rounded-pill text-bg-primary ms-1 me-3">
@@ -2,9 +2,10 @@
width: 450px;
min-height: 50px;
max-height: Min(calc(100vh - 140px), 630px);
// z-index: 1055;
// left: auto;
// right: 0;
hr {
opacity: $hr-opacity / 2;
}
}
.o-mail-MessagingMenu-navbar {
@@ -21,9 +21,9 @@
'o-mail-MessagingMenu-header border-bottom d-flex flex-shrink-0 p-2': !env.inDiscussApp,
}">
<t t-if="!ui.isSmall">
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'all' ? 'fw-bolder' : 'text-500'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'all'">All</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'chat' ? 'fw-bolder' : 'text-500'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'chat'">Chats</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'channel' ? 'fw-bolder' : 'text-500'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'channel'">Channels</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'all' ? 'fw-bolder' : 'text-muted'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'all'">All</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'chat' ? 'fw-bolder' : 'text-muted'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'chat'">Chats</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'channel' ? 'fw-bolder' : 'text-muted'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'channel'">Channels</button>
</t>
<div class="flex-grow-1"></div>
<t t-if="!store.discuss.isActive or ui.isSmall and store.discuss.activeTab !== 'mailbox'">
@@ -49,30 +49,33 @@
<div class="d-flex justify-content-center py-4 px-2 text-muted" t-if="!hasPreviews">
No conversation yet...
</div>
<NotificationItem t-if="notificationRequest.isShown"
body="notificationRequest.body"
displayName="notificationRequest.displayName"
iconSrc="notificationRequest.iconSrc"
isLast="notificationRequest.isLast"
onClick="() => notification.requestPermission()"
>
<t t-set-slot="icon">
<ImStatus persona="notificationRequest.partner" className="'position-absolute top-100 start-100 translate-middle mt-n1 ms-n1'"/>
</t>
</NotificationItem>
<t t-if="store.discuss.activeTab === 'all'">
<t t-if="notificationRequest.isShown">
<NotificationItem
t-foreach="failures" t-as="failure" t-key="failure.id"
body="failure.body"
counter="failure.notifications.length > 1 ? failure.notifications.length : undefined"
datetime="failure.datetime"
displayName="failure.modelName"
iconSrc="failure.iconSrc"
isLast="threads.length === 0 and failure_last"
hasMarkAsReadButton="true"
onClick="(isMarkAsRead) => isMarkAsRead ? this.cancelNotifications(failure) : this.onClickFailure(failure)"
onSwipeRight="hasTouch() ? { action: () => this.cancelNotifications(failure), icon: 'fa-times-circle', bgColor: 'bg-warning' } : undefined"
/>
body="notificationRequest.body"
displayName="notificationRequest.displayName"
iconSrc="notificationRequest.iconSrc"
onClick="() => notification.requestPermission()"
>
<t t-set-slot="icon">
<ImStatus persona="notificationRequest.partner" className="'position-absolute top-100 start-100 translate-middle mt-n1 ms-n1'"/>
</t>
</NotificationItem>
<hr t-if="!notificationRequest.isLast" class="my-0"/>
</t>
<t t-if="store.discuss.activeTab === 'all'">
<t t-foreach="failures" t-as="failure" t-key="failure.id">
<NotificationItem
body="failure.body"
counter="failure.notifications.length > 1 ? failure.notifications.length : undefined"
datetime="failure.datetime"
displayName="failure.modelName"
iconSrc="failure.iconSrc"
hasMarkAsReadButton="true"
onClick="(isMarkAsRead) => isMarkAsRead ? this.cancelNotifications(failure) : this.onClickFailure(failure)"
onSwipeRight="hasTouch() ? { action: () => this.cancelNotifications(failure), icon: 'fa-times-circle', bgColor: 'bg-warning' } : undefined"
/>
<hr t-if="threads.length !== 0 or !failure_last" class="my-0"/>
</t>
</t>
<t t-foreach="threads" t-as="thread" t-key="thread.localId">
<t t-set="message" t-value="thread.isChatChannel or (thread.type === 'channel' and !thread.hasNeedactionMessages) ? thread.newestMessage : thread.newestNeedactionMessage"/>
@@ -83,7 +86,6 @@
displayName="thread.displayName"
iconSrc="thread.imgUrl"
hasMarkAsReadButton="thread.isUnread"
isLast="thread_last"
onClick="(isMarkAsRead) => this.onClickThread(isMarkAsRead, thread)"
onSwipeRight="hasTouch() and thread.isUnread ? { action: () => this.markAsRead(thread), icon: 'fa-check-circle', bgColor: 'bg-success' } : undefined"
onSwipeLeft="hasTouch() and threadService.canUnpin(thread) ? { action: () => this.threadService.unpin(thread), icon: 'fa-times-circle', bgColor: 'bg-danger' } : undefined"
@@ -100,6 +102,7 @@
</t>
</t>
</NotificationItem>
<hr t-if="!thread_last" class="my-0"/>
</t>
</div>
</div>
@@ -14,7 +14,6 @@ export class NotificationItem extends Component {
"displayName",
"hasMarkAsReadButton?",
"iconSrc?",
"isLast",
"onClick",
"onSwipeLeft?",
"onSwipeRight?",
@@ -3,7 +3,7 @@
<t t-name="mail.NotificationItem" owl="1">
<ActionSwiper onLeftSwipe="props.onSwipeLeft ? props.onSwipeLeft : undefined" onRightSwipe="props.onSwipeRight ? props.onSwipeRight : undefined">
<div class="o-mail-NotificationItem d-flex cursor-pointer align-items-center p-1" t-att-class="{ 'border-bottom': !props.isLast, 'o-muted': !props.hasMarkAsReadButton }" t-on-click="onClick">
<div class="o-mail-NotificationItem d-flex cursor-pointer align-items-center p-1" t-att-class="{ 'o-muted': !props.hasMarkAsReadButton }" t-on-click="onClick">
<div class="position-relative o-bg-inherit m-1 flex-shrink-0" style="width:40px;height:40px;">
<img class="o_avatar w-100 h-100 rounded" alt="Notification Item Image" t-att-src="props.iconSrc"/>
<t t-slot="icon"/>
@@ -24,7 +24,7 @@
<t t-if="props.body" t-esc="props.body" name="notificationBody"/>
</div>
<div class="flex-grow-1"/>
<i t-if="props.hasMarkAsReadButton" class="o-mail-NotificationItem-markAsRead fa fa-check d-flex ms-2 text-600 opacity-50 opacity-100-hover" title="Mark As Read" t-ref="markAsRead"/>
<i t-if="props.hasMarkAsReadButton" class="o-mail-NotificationItem-markAsRead fa fa-check d-flex ms-2 text-600 opacity-50 opacity-100-hover cursor-pointer" title="Mark As Read" t-ref="markAsRead"/>
</div>
</div>
</div>
@@ -199,13 +199,6 @@ patch(ThreadService.prototype, "mail/web", {
}
delete this.store.followers[follower.id];
},
async updateAvatar(threadId, data) {
return this.rpc("/discuss/channel/update_avatar", {
channel_id: threadId,
data,
});
},
});
patch(threadService, "mail/web", {
@@ -49,22 +49,22 @@ QUnit.test("Basic keyboard navigation", async (assert) => {
const { openDiscuss } = await start();
await openDiscuss(channelId);
await click("button[aria-label='Emojis']");
assert.containsOnce($, ".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=0].bg-300"); // bg-300 means active
assert.containsOnce($, ".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=0].bg-200"); // bg-200 means active
await afterNextRender(() => triggerHotkey("ArrowRight"));
assert.containsOnce($, ".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=1].bg-300");
assert.containsOnce($, ".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=1].bg-200");
await afterNextRender(() => triggerHotkey("ArrowDown"));
assert.containsOnce(
$,
`.o-mail-EmojiPicker-content .o-mail-Emoji[data-index=${EMOJI_PER_ROW + 1}].bg-300`
`.o-mail-EmojiPicker-content .o-mail-Emoji[data-index=${EMOJI_PER_ROW + 1}].bg-200`
);
await afterNextRender(() => triggerHotkey("ArrowLeft"));
assert.containsOnce(
$,
`.o-mail-EmojiPicker-content .o-mail-Emoji[data-index=${EMOJI_PER_ROW}].bg-300`
`.o-mail-EmojiPicker-content .o-mail-Emoji[data-index=${EMOJI_PER_ROW}].bg-200`
);
await afterNextRender(() => triggerHotkey("ArrowUp"));
assert.containsOnce($, ".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=0].bg-300");
const codepoints = $(".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=0].bg-300").data(
assert.containsOnce($, ".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=0].bg-200");
const codepoints = $(".o-mail-EmojiPicker-content .o-mail-Emoji[data-index=0].bg-200").data(
"codepoints"
);
await afterNextRender(() => triggerHotkey("Enter"));
@@ -148,7 +148,7 @@ QUnit.test("first category should be highlight by default", async (assert) => {
const { openDiscuss } = await start();
await openDiscuss(channelId);
await click("button[aria-label='Emojis']");
assert.containsOnce($, ".o-mail-EmojiPicker-header .o-mail-Emoji:eq(0).bg-300");
assert.containsOnce($, ".o-mail-EmojiPicker-header .o-mail-Emoji:eq(0).bg-200");
});
QUnit.test(
@@ -633,9 +633,6 @@ patch(MockServer.prototype, "mail/models/discuss_channel", {
case "channel":
is_editable = channel.create_uid === this.pyEnv.currentPartnerId;
break;
case "chat":
is_editable = true;
break;
case "group":
is_editable = memberOfCurrentUser;
break;
+1 -1
View File
@@ -52,7 +52,7 @@
<div class="oe_button_box" name="button_box"/>
<field name="avatar_128" invisible="1"/>
<field name="is_editable" invisible="1"/>
<field name="image_128" widget="image" class="oe_avatar" options="{'size': [90, 90], 'preview_image':'avatar_128'}"/>
<field name="image_128" widget="image" class="oe_avatar" options="{'size': [90, 90], 'preview_image':'avatar_128'}" attrs="{'readonly': [('is_editable', '!=', 'True')]}"/>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title">
<label for="name" string="Group Name"/>
@@ -508,7 +508,7 @@ class TestDiscussFullPerformance(TransactionCase):
'group_based_subscription': False,
'id': self.channel_chat_1.id,
'invitedMembers': [('insert', [])],
'is_editable': True,
'is_editable': False,
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
@@ -594,7 +594,7 @@ class TestDiscussFullPerformance(TransactionCase):
'group_based_subscription': False,
'id': self.channel_chat_2.id,
'invitedMembers': [('insert', [])],
'is_editable': True,
'is_editable': False,
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
@@ -680,7 +680,7 @@ class TestDiscussFullPerformance(TransactionCase):
'group_based_subscription': False,
'id': self.channel_chat_3.id,
'invitedMembers': [('insert', [])],
'is_editable': True,
'is_editable': False,
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
@@ -766,7 +766,7 @@ class TestDiscussFullPerformance(TransactionCase):
'group_based_subscription': False,
'id': self.channel_chat_4.id,
'invitedMembers': [('insert', [])],
'is_editable': True,
'is_editable': False,
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),