[IMP] mail: Message, redesign

This commit redesigns the `Message` component for the ChatWindow,
Discuss and the Chatter. The purpose is to make the chats more
user-friendly.

task-2823627

Part-of: odoo/odoo#92872
This commit is contained in:
Brieuc-brd
2022-07-29 15:53:06 +02:00
parent 15039761eb
commit 9b995e5b7d
17 changed files with 423 additions and 113 deletions
@@ -3,7 +3,7 @@
<t t-name="mail.Activity" owl="1">
<t t-if="activityView">
<div class="o_Activity d-flex p-2" t-attf-class="{{ className }}" t-on-click="activityView.onClickActivity" t-ref="root">
<div class="o_Activity d-flex py-2 px-3" t-attf-class="{{ className }}" t-on-click="activityView.onClickActivity" t-ref="root">
<div class="o_Activity_sidebar me-3">
<div class="o_Activity_user position-relative h-100 w-100">
<t t-if="activityView.activity.assignee">
@@ -3,7 +3,7 @@
<t t-name="mail.AttachmentCard" owl="1">
<t t-if="attachmentCard">
<div t-attf-class="{{ className }}" t-ref="root">
<div class="o_AttachmentCard o-has-card-details d-flex rounded bg-300"
<div class="o_AttachmentCard o-has-card-details d-flex rounded border bg-200"
t-att-class="{
'o-isUploading': attachmentCard.attachment.isUploading,
'o-viewable': attachmentCard.attachment.isViewable,
@@ -42,13 +42,13 @@
</t>
<!-- Remove button -->
<t t-if="attachmentCard.attachment.isDeletable">
<button class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemUnlink btn top-0 justify-content-center align-items-center d-flex w-100 h-100 rounded-0" t-attf-class="{{ attachmentCard.attachmentList.composerViewOwner ? 'o-pretty position-absolute btn-primary' : 'bg-300' }}" t-on-click="attachmentCard.onClickUnlink" title="Remove">
<button class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemUnlink btn top-0 justify-content-center align-items-center d-flex w-100 h-100 rounded-0" t-attf-class="{{ attachmentCard.attachmentList.composerViewOwner ? 'o-pretty position-absolute btn-primary' : 'bg-200' }}" t-on-click="attachmentCard.onClickUnlink" title="Remove">
<i class="fa fa-trash" role="img" aria-label="Remove"/>
</button>
</t>
<!-- Download button -->
<t t-if="!attachmentCard.attachmentList.composerViewOwner and !attachmentCard.attachment.isUploading">
<button class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemDownload btn d-flex justify-content-center align-items-center w-100 h-100 rounded-0 bg-300" t-on-click="attachmentCard.attachment.onClickDownload" title="Download">
<button class="o_AttachmentCard_asideItem o_AttachmentCard_asideItemDownload btn d-flex justify-content-center align-items-center w-100 h-100 rounded-0 bg-200" t-on-click="attachmentCard.attachment.onClickDownload" title="Download">
<i class="fa fa-download" role="img" aria-label="Download"/>
</button>
</t>
@@ -3,7 +3,7 @@
<t t-name="mail.AttachmentImage" owl="1">
<t t-if="attachmentImage">
<div t-attf-class="{{ className }}" role="menu" t-att-aria-label="attachmentImage.attachment.displayName" t-ref="root">
<div class="o_AttachmentImage d-flex position-relative m-1 flex-shrink-0"
<div class="o_AttachmentImage d-flex position-relative flex-shrink-0"
t-att-class="{
'o-isUploading': attachmentImage.attachment.isUploading,
}"
@@ -3,15 +3,15 @@
<t t-name="mail.AttachmentList" owl="1">
<t t-if="attachmentList">
<div class="o_AttachmentList d-flex flex-column justify-content-start" t-attf-class="{{ className }}" t-ref="root">
<div class="o_AttachmentList_partialList o_AttachmentList_partialListImages d-flex flex-grow-1 flex-wrap">
<div class="o_AttachmentList d-flex flex-column mt-1" t-att-class="{ 'me-5 pe-5': attachmentList.isInChatWindowAndIsAlignedLeft and !attachmentList.composerViewOwner, 'ms-5 ps-5': attachmentList.isInChatWindowAndIsAlignedRight and !attachmentList.composerViewOwner }" t-attf-class="{{ className }}" t-ref="root">
<div t-if="attachmentList.attachmentImages.length > 0" class="o_AttachmentList_partialList o_AttachmentList_partialListImages d-flex flex-grow-1 flex-wrap" t-att-class="{ 'justify-content-end': attachmentList.isInChatWindowAndIsAlignedRight and !attachmentList.composerViewOwner }">
<t t-foreach="attachmentList.attachmentImages" t-as="attachmentImage" t-key="attachmentImage.localId">
<AttachmentImage className="'o_AttachmentList_attachment my-1 me-1 mw-100'" record="attachmentImage"/>
<AttachmentImage className="'o_AttachmentList_attachment mw-100 mb-1'" classNameObj="{ 'ms-1': attachmentList.isInChatWindowAndIsAlignedRight, 'me-1': !attachmentList.isInChatWindowAndIsAlignedRight }" record="attachmentImage"/>
</t>
</div>
<div class="o_AttachmentList_partialList o_AttachmentList_partialListNonImages d-flex flex-grow-1 flex-wrap justify-content-start m-1">
<div t-if="attachmentList.attachmentCards.length > 0" class="o_AttachmentList_partialList o_AttachmentList_partialListNonImages d-flex flex-grow-1 flex-wrap mt-1" t-att-class="{ 'justify-content-end': attachmentList.isInChatWindowAndIsAlignedRight and !attachmentList.composerViewOwner }">
<t t-foreach="attachmentList.attachmentCards" t-as="attachmentCard" t-key="attachmentCard.localId">
<AttachmentCard className="'o_AttachmentList_attachment my-1 me-1 mw-100'" record="attachmentCard"/>
<AttachmentCard className="'o_AttachmentList_attachment mw-100 mb-1'" classNameObj="{ 'ms-1': attachmentList.isInChatWindowAndIsAlignedRight, 'me-1': !attachmentList.isInChatWindowAndIsAlignedRight }" record="attachmentCard"/>
</t>
</div>
</div>
@@ -13,7 +13,7 @@
grid-template-rows: auto 1fr auto;
&.o-has-current-partner-avatar {
grid-template-columns: 50px 1fr;
grid-template-columns: $o-mail-message-sidebar-width 1fr;
}
}
@@ -68,7 +68,3 @@
.o_Composer_buttonDiscard {
border: $border-width solid lighten($gray-400, 5%);
}
.o_Composer_toolButtons:last-child:not(.o-composer-has-current-partner-avatar) {
border-right: $border-width solid lighten($gray-400, 5%);
}
@@ -28,7 +28,7 @@
/>
</t>
<t t-if="composerView.hasHeader">
<div class="o_Composer_coreHeader text-truncate">
<div class="o_Composer_coreHeader text-truncate" t-att-class="{ 'small p-2': composerView.isInChatWindow or composerView.isInDiscuss }">
<t t-if="composerView.threadView and composerView.threadView.replyingToMessageView">
<span class="o_cursor_pointer" t-on-click="composerView.onClickReplyingToMessage">
Replying to <b t-esc="composerView.threadView.replyingToMessageView.message.authorName"/>
@@ -87,22 +87,20 @@
record="composerView"
t-key="composerView.localId"
/>
<div class="o_Composer_buttons d-flex align-items-stretch align-self-stretch flex-shrink-0" t-att-class="{ 'o-composer-is-compact': composerView.isCompact, 'h-auto w-100 px-3': !composerView.isCompact, 'o-isDeviceSmall': messaging.device.isSmall, 'o-messaging-in-editing border-end': composerView and composerView.messageViewInEditing }">
<div class="o_Composer_buttons d-flex align-items-stretch align-self-stretch flex-shrink-0" t-att-class="{ 'o-composer-is-compact': composerView.isCompact, 'h-auto w-100 px-3': !composerView.isCompact, 'o-isDeviceSmall': messaging.device.isSmall, 'o-messaging-in-editing': composerView and composerView.messageViewInEditing, 'border-end': composerView.messageViewInEditing and !composerView.hasCurrentPartnerAvatar }">
<div class="o_Composer_toolButtons d-flex py-1 border-top bg-white"
t-att-class="{
'o-composer-has-current-partner-avatar': composerView.hasCurrentPartnerAvatar,
'o-composer-is-compact border-bottom': composerView.isCompact,
'flex-row flex-grow-1 justify-content-between': !composerView.isCompact,
}">
<t t-if="composerView.isCompact">
<div class="o_Composer_toolButtonSeparator flex-shrink-0 my-2 border-start"/>
</t>
<div class="o_Composer_primaryToolButtons d-flex align-items-center" t-att-class="{ 'o-composer-is-compact px-2': composerView.isCompact }">
<button class="o_Composer_button o_Composer_buttonEmojis o_Composer_toolButton btn btn-light mx-1 border-0 rounded-pill bg-white"
<div class="o_Composer_primaryToolButtons d-flex align-items-center" t-att-class="{ 'o-composer-is-compact px-2': composerView.isCompact, 'flex-column': composerView.messageViewInEditing and composerView.messageViewInEditing.isInChatWindow }">
<button class="o_Composer_button o_Composer_buttonEmojis o_Composer_toolButton btn btn-light border-0 rounded-pill bg-white"
t-att-class="{
'o-open bg-200': composerView.emojisPopoverView,
'o-isDeviceSmall': messaging.device.isSmall,
}"
t-attf-class="{{ composerView.messageViewInEditing ? 'my-1' : 'mx-1' }}"
t-att-aria-expanded="composerView.emojisPopoverView ? 'true' : 'false'"
t-on-keydown="composerView.onKeydownButtonEmojis"
t-on-click="composerView.onClickButtonEmojis"
@@ -110,7 +108,7 @@
>
<i class="fa fa-smile-o" role="img" aria-label="Emojis"/>
</button>
<button class="o_Composer_button o_Composer_buttonAttachment o_Composer_toolButton btn btn-light fa fa-paperclip mx-1 border-0 rounded-pill bg-white" t-att-class="{ 'o-isDeviceSmall': messaging.device.isSmall }" title="Add attachment" aria-label="Add attachment" type="button" t-on-click="composerView.onClickAddAttachment"/>
<button class="o_Composer_button o_Composer_buttonAttachment o_Composer_toolButton btn btn-light fa fa-paperclip border-0 rounded-pill bg-white" t-att-class="{ 'o-isDeviceSmall': messaging.device.isSmall }" t-attf-class="{{ composerView.messageViewInEditing ? 'my-1' : 'mx-1' }}" title="Add attachment" aria-label="Add attachment" type="button" t-on-click="composerView.onClickAddAttachment"/>
</div>
<t t-if="composerView.isExpandable">
<div class="o_Composer_secondaryToolButtons">
@@ -3,7 +3,7 @@
<t t-name="mail.ComposerTextInput" owl="1">
<t t-if="composerView">
<div class="o_ComposerTextInput position-relative bg-white" t-attf-class="{{ className }} {{ composerView.isCompact ? 'border border-end-0' : 'border-0' }}" t-on-paste="composerView.onPasteTextInput" t-ref="root">
<div class="o_ComposerTextInput position-relative bg-white" t-attf-class="{{ className }} {{ composerView.isCompact ? 'border border-end-0' : 'border-0' }}" t-att-class="{ 'border-start-0': composerView.isInChatWindow }" t-on-paste="composerView.onPasteTextInput" t-ref="root">
<t t-if="composerView.composerSuggestionListView">
<ComposerSuggestionListView record="composerView.composerSuggestionListView"/>
</t>
@@ -33,14 +33,12 @@
min-width: 0; // allows this flex child to shrink more than its content
}
.o_Message_highlightIndicator {
width: $o-mail-discuss-message-highlight-indicator-width;
transition: background-color .5s ease-out;
}
.o_Message_prettyBody > p {
margin-bottom: 0;
}
.o_Message_prettyBody {
p:last-child,
> p {
margin-bottom: 0;
}
}
.o_Message_sidebar {
flex-basis: $o-mail-message-sidebar-width;
@@ -57,16 +55,26 @@
&.o-not-discussion.o-selected {
border-bottom: 1px solid darken(map-get($grays, '400'), 5%);
}
}
&.o-highlighted {
background-color: rgba($o-brand-primary, .1);
&.o_Message_active {
background-color: rgba($o-brand-primary, .2);
}
.o_Message_background {
&.o-isAuthorNotCurrentUserOrGuest {
opacity: .15;
}
}
.o_Message_prettyBody {
a.o_mail_redirect {
padding: map-get($spacers, 1);
border-radius: $border-radius;
background: rgba($primary, .2);
}
}
.o_Message_background {
transition: opacity .5s ease-out;
}
// Used to hide buttons on rating emails in chatter
// FIXME: should use a better approach for not having such buttons
// in chatter of such messages, but keep having them in emails.
@@ -3,7 +3,7 @@
<t t-name="mail.Message" owl="1">
<t t-if="messageView">
<div class="o_Message position-relative pt-1"
<div class="o_Message position-relative py-1"
t-att-class="{
'o_Message_active': messageView.isActive,
'o-clicked': messageView.messagingAsClickedMessageView,
@@ -14,13 +14,13 @@
'o-not-discussion': !messageView.message.isDiscussionOrNotification,
'o-notification': messageView.message.message_type === 'notification',
'o-selected': messageView.isSelected,
'o-squashed': messageView.isSquashed,
'o-squashed pt-1': messageView.isSquashed,
'o-starred': messageView.message.isStarred,
'mt-3': !messageView.isSquashed and messageView.messageListViewMessageViewItemOwner,
'bg-white': messageView.message.isDiscussionOrNotification and !messageView.isSelected and !(messageView.message.isHighlighted or messageView.isHighlighted) and !messageView.isActive,
'bg-100': (!messageView.message.isDiscussionOrNotification and !messageView.isActive and !messageView.isSelected) or (messageView.isActive and !messageView.isSelected and messageView.message.isDiscussionOrNotification and !(messageView.message.isHighlighted or messageView.isHighlighted)),
'bg-200': !messageView.message.isDiscussionOrNotification and messageView.isActive and !messageView.isSelected,
'bg-400': messageView.isSelected and !(messageView.message.isHighlighted or messageView.isHighlighted),
'o-currentAuthor': messageView.message.isCurrentUserOrGuestAuthor,
'mt-1': !messageView.isSquashed,
'mt-5': !messageView.isSquashed and messageView.messageListViewOwner,
'px-3': !messageView.isInChatWindow,
'px-1': messageView.isInChatWindow,
'opacity-50': (messageView.messageListViewMessageViewItemOwner and messageView.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.replyingToMessageView) and !messageView.isSelected,
}" t-attf-class="{{ messageView.extraClass }} {{ className }}" role="group" t-att-aria-label="messageView.message.messageTypeText" t-on-click="messageView.onClick" t-on-mouseenter="messageView.onMouseenter" t-on-mouseleave="messageView.onMouseleave" t-att-data-id="messageView.message.id" t-att-data-message-local-id="messageView.message.localId"
t-ref="root"
@@ -29,21 +29,19 @@
<div t-if="messageView.isActive and messageView.messageActionList" class="o_Message_actionListContainer position-absolute top-0 end-0 mt-n3 px-3" t-att-class="{ 'o-squashed': messageView.isSquashed }">
<MessageActionList record="messageView.messageActionList"/>
</div>
<div class="d-flex flex-shrink-0">
<div class="o_Message_highlightIndicator position-absolute top-0 start-0 h-100" t-att-class="{ 'o-active bg-primary': messageView.message.isHighlighted or messageView.isHighlighted }"/>
<div class="o_Message_sidebar d-flex justify-content-center flex-shrink-0" t-att-class="{ 'o-message-squashed align-items-start': messageView.isSquashed }">
<div class="position-relative d-flex flex-shrink-0" t-att-class="{ 'flex-row-reverse': messageView.isInChatWindowAndIsAlignedRight }">
<div class="o_Message_sidebar d-flex flex-shrink-0" t-att-class="{ 'o-message-squashed align-items-start': messageView.isSquashed, 'justify-content-center': messageView.isInChatWindow }">
<t t-if="!messageView.isSquashed">
<div class="o_Message_authorAvatarContainer o_Message_sidebarItem position-relative mx-1">
<div class="o_Message_authorAvatarContainer o_Message_sidebarItem position-relative">
<img class="o_Message_authorAvatar w-100 h-100 rounded-circle o_object_fit_cover" t-att-class="{ 'o_cursor_pointer': messageView.hasAuthorOpenChat, o_redirect: messageView.hasAuthorOpenChat }" t-att-src="messageView.message.avatarUrl" t-att-role="messageView.hasAuthorOpenChat ? 'button' : ''" t-att-tabindex="messageView.hasAuthorOpenChat ? '0' : ''" t-on-click="messageView.onClickAuthorAvatar" t-att-title="messageView.authorTitleText" t-att-alt="messageView.authorTitleText"/>
<t t-if="messageView.personaImStatusIconView">
<PersonaImStatusIcon
className="'o_Message_personaImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center'"
className="'o_Message_personaImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center text-white'"
classNameObj="{
'o-message-selected text-400': messageView.isSelected,
'o-message-selected': messageView.isSelected,
'o_Message_personaImStatusIcon-mobile': messaging.device.isSmall,
'small': !messaging.device.isSmall,
'text-white': !messageView.isSelected and messageView.message.isDiscussionOrNotification,
'text-100': !messageView.message.isDiscussionOrNotification and !messageView.isSelected,
'text-100': messageView.message.is_note and !messageView.isSelected,
}"
hasOpenChat="messageView.hasAuthorOpenChat"
record="messageView.personaImStatusIconView"
@@ -53,20 +51,22 @@
</t>
<t t-else="">
<t t-if="messageView.isActive and messageView.message.date">
<small class="o_Message_date o_Message_sidebarItem mt-1 mx-1" t-att-class="{ 'o-message-selected text-600': messageView.isSelected, 'text-500': !messageView.isSelected }">
<small class="o_Message_date o_Message_sidebarItem mt-3 mx-1 text-muted opacity-50">
<t t-esc="messageView.message.shortTime"/>
</small>
</t>
<t t-if="!messageView.isActive and messageView.messageSeenIndicatorView">
<MessageSeenIndicator className="'o_Message_seenIndicator me-1'" record="messageView.messageSeenIndicatorView"/>
<div t-att-class="{ 'position-absolute bottom-0 mb-1': !messageView.isInChatter }">
<MessageSeenIndicator className="'o_Message_seenIndicator me-1'" record="messageView.messageSeenIndicatorView"/>
</div>
</t>
</t>
</div>
<div class="o_Message_core flex-grow-1 me-4">
<div class="o_Message_core flex-grow-1">
<t t-if="!messageView.isSquashed">
<div class="o_Message_header d-flex flex-wrap align-items-baseline ms-2">
<div class="o_Message_header d-flex flex-wrap align-items-baseline" t-att-class="{ 'justify-content-end': messageView.isInChatWindowAndIsAlignedRight }">
<t t-if="messageView.message.author">
<strong class="o_Message_authorName o_redirect me-2 text-truncate" t-att-class="{'o_cursor_pointer': messageView.hasAuthorOpenChat}" t-att-role="messageView.hasAuthorOpenChat ? 'button' : ''" t-att-tabindex="messageView.hasAuthorOpenChat ? '0' : ''" t-on-click="messageView.onClickAuthorName" t-att-title="messageView.authorTitleText">
<strong t-if="messageView.isShowingAuthorName" class="o_Message_authorName o_redirect me-1 text-truncate" t-att-class="{ 'o_cursor_pointer': messageView.hasAuthorOpenChat }" t-att-role="messageView.hasAuthorOpenChat ? 'button' : ''" t-att-tabindex="messageView.hasAuthorOpenChat ? '0' : ''" t-on-click="messageView.onClickAuthorName" t-att-title="messageView.authorTitleText">
<t t-if="messageView.message.originThread">
<t t-esc="messageView.message.originThread.getMemberName(messageView.message.author)"/>
</t>
@@ -76,29 +76,29 @@
</strong>
</t>
<t t-elif="messageView.message.guestAuthor">
<strong class="o_Message_authorName me-2 text-truncate">
<strong class="o_Message_authorName me-2 text-truncate text-muted">
<t t-esc="messageView.message.guestAuthor.name"/>
</strong>
</t>
<t t-elif="messageView.message.email_from">
<a class="o_Message_authorName me-2 text-truncate" t-attf-href="mailto:{{ messageView.message.email_from }}?subject=Re: {{ messageView.message.subject ? messageView.message.subject : '' }}">
<a class="o_Message_authorName me-2 text-truncate text-muted" t-attf-href="mailto:{{ messageView.message.email_from }}?subject=Re: {{ messageView.message.subject ? messageView.message.subject : '' }}">
<strong>
<t t-esc="messageView.message.email_from"/>
</strong>
</a>
</t>
<t t-else="">
<strong class="o_Message_authorName me-2 text-truncate">
<strong class="o_Message_authorName me-2 text-truncate text-muted">
Anonymous
</strong>
</t>
<t t-if="messageView.message.date">
<small class="o_Message_date o_Message_headerDate me-2 text-500" t-att-class="{ 'o-message-selected text-600': messageView.isSelected, 'text-500': !messageView.isSelected }" t-att-title="messageView.message.datetime">
- <t t-esc="messageView.dateFromNow"/>
<small class="o_Message_date o_Message_headerDate text-muted opacity-50" t-att-class="{ 'o-message-selected': messageView.isSelected, 'me-2': !(messageView.isInChatWindowAndIsAlignedRight) }" t-att-title="messageView.message.datetime">
<span t-if="messageView.isShowingAuthorName">-</span> <t t-esc="messageView.dateFromNow"/>
</small>
</t>
<t t-if="messageView.messageSeenIndicatorView">
<MessageSeenIndicator className="'o_Message_seenIndicator me-1'" record="messageView.messageSeenIndicatorView"/>
<MessageSeenIndicator className="'o_Message_seenIndicator ms-1'" record="messageView.messageSeenIndicatorView"/>
</t>
<t t-if="messageView.messageListViewMessageViewItemOwner and messageView.message.originThread and messageView.message.originThread !== messageView.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.thread">
<small class="o_Message_originThread me-2" t-att-class="{ 'o-message-selected text-600': messageView.isSelected, 'text-500': !messageView.isSelected }">
@@ -111,54 +111,110 @@
</small>
</t>
<t t-if="messageView.messageListViewMessageViewItemOwner and messageView.message.originThread and messageView.message.originThread === messageView.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.thread and messageView.message.notifications.length > 0">
<t t-if="messageView.message.failureNotifications.length > 0">
<span class="o_Message_notificationIconClickable o-error o_cursor_pointer text-danger" role="button" tabindex="0" t-on-click="messageView.onClickFailure">
<i name="failureIcon" class="o_Message_notificationIcon fa fa-envelope" role="img" aria-label="Delivery failure"/>
</span>
</t>
<t t-else="">
<Popover>
<span class="o_Message_notificationIconClickable o_cursor_pointer text-600">
<i name="notificationIcon" class="o_Message_notificationIcon fa fa-envelope-o"/>
<div t-att-class="{ 'ms-2': messageView.isInChatWindowAndIsAlignedRight }">
<t t-if="messageView.message.failureNotifications.length > 0">
<span class="o_Message_notificationIconClickable o-error o_cursor_pointer text-danger" role="button" tabindex="0" t-on-click="messageView.onClickFailure">
<i name="failureIcon" class="o_Message_notificationIcon fa fa-envelope" role="img" aria-label="Delivery failure"/>
</span>
<t t-set-slot="opened">
<MessageNotificationPopoverContent record="messageView"/>
</t>
</Popover>
</t>
</t>
<t t-else="">
<Popover>
<span class="o_Message_notificationIconClickable text-600 o_cursor_pointer">
<i name="notificationIcon" class="o_Message_notificationIcon fa fa-envelope-o"/>
</span>
<t t-set-slot="opened">
<MessageNotificationPopoverContent record="messageView"/>
</t>
</Popover>
</t>
</div>
</t>
</div>
</t>
<t t-if="messageView.message.subject and !messageView.message.isSubjectSimilarToOriginThreadName">
<em class="o_Message_subject mx-2 mb-1">Subject: <t t-esc="messageView.message.subject"/></em>
</t>
<div class="o_Message_content mx-2 text-break" t-ref="content">
<t t-if="!messageView.composerViewInEditing">
<div class="o_Message_prettyBody" t-ref="prettyBody"/><!-- messageView.message.prettyBody is inserted here from _update() -->
</t>
<t t-if="messageView.composerViewInEditing">
<Composer
className="'o_Message_composer mb-1'"
record="messageView.composerViewInEditing"
<div
class="o_Message_bubbleWrap position-relative d-flex align-items-start"
t-att-class="{
'd-flex justify-content-end': messageView.isInChatWindowAndIsAlignedRight,
'pe-5': messageView.isInChatWindowAndIsAlignedLeft and !messageView.composerViewInEditing,
'ps-5': messageView.isInChatWindowAndIsAlignedRight and !messageView.composerViewInEditing,
'pe-2': messageView.isInChatWindowAndIsAlignedLeft and messageView.composerViewInEditing,
'ps-2': messageView.isInChatWindowAndIsAlignedRight and messageView.composerViewInEditing,
}"
>
<div
class="o_Message_bubble position-relative"
t-att-class="{
'flex-grow-1': messageView.composerViewInEditing,
'w-100': !(messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms') and !messageView.isInChatWindow,
'me-5': messageView.isInChatWindowAndIsAlignedLeft and !messageView.composerViewInEditing,
'ms-5': messageView.isInChatWindowAndIsAlignedRight and !messageView.composerViewInEditing,
'p-3': messageView.message.prettyBody !== '' and !messageView.composerViewInEditing,
'p-2': messageView.composerViewInEditing,
'text-muted': messageView.message.is_note and messageView.message.message_type !== 'sms',
}"
>
<div
t-if="messageView.message.prettyBody !== '' || messageView.composerViewInEditing"
class="o_Message_background position-absolute start-0 top-0 w-100 h-100"
t-att-class="{
'rounded-end-3': !messageView.isInChatWindowAndIsAlignedRight and (messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms'),
'rounded-start-3': messageView.isInChatWindowAndIsAlignedRight and (messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms'),
'rounded-bottom-3 border': messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms',
'rounded-3 bg-100 opacity-50': messageView.message.is_note and messageView.message.message_type !== 'sms',
'o-isAuthorNotCurrentUserOrGuest border-info bg-info-light': !messageView.message.isCurrentUserOrGuestAuthor and !messageView.message.isHighlighted and (messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms'),
'border-success bg-success-light opacity-25': messageView.message.isCurrentUserOrGuestAuthor and !messageView.message.isHighlighted and (messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms'),
'border-warning bg-warning-light opacity-50': messageView.message.isHighlighted and (messageView.message.isDiscussionOrNotification or messageView.message.message_type === 'sms'),
}"
/>
</t>
<t t-if="messageView.message.subtype_description and !messageView.message.isBodyEqualSubtypeDescription">
<p t-esc="messageView.message.subtype_description"/>
</t>
<t t-if="messageView.message.trackingValues.length > 0">
<ul class="o_Message_trackingValues mt-2">
<t t-foreach="messageView.message.trackingValues" t-as="trackingValue" t-key="trackingValue.id">
<li>
<TrackingValue value="trackingValue"/>
</li>
<t t-if="messageView.message.subject and !messageView.message.isSubjectSimilarToOriginThreadName">
<em class="o_Message_subject position-relative mb-1 me-2">Subject: <t t-esc="messageView.message.subject"/></em>
</t>
<div class="o_Message_content position-relative text-break" t-ref="content">
<t t-if="!messageView.composerViewInEditing">
<div class="o_Message_prettyBody" t-ref="prettyBody"/><!-- messageView.message.prettyBody is inserted here from _update() -->
</t>
</ul>
</t>
<t t-if="messageView.composerViewInEditing">
<Composer
className="'o_Message_composer'"
record="messageView.composerViewInEditing"
/>
</t>
<t t-if="messageView.message.subtype_description and !messageView.message.isBodyEqualSubtypeDescription">
<p t-esc="messageView.message.subtype_description" class="mb-0"/>
</t>
<t t-if="messageView.message.trackingValues.length > 0">
<ul class="o_Message_trackingValues mb-0 pl-4">
<t t-foreach="messageView.message.trackingValues" t-as="trackingValue" t-key="trackingValue.id">
<li>
<TrackingValue value="trackingValue"/>
</li>
</t>
</ul>
</t>
</div>
</div>
<div
t-if="messageView.messageActionList and !messageView.composerViewInEditing"
class="o_Message_actionListContainer"
t-att-class="{
'o-squashed': messageView.isSquashed,
'position-absolute top-0': !messageView.isInDiscuss,
'start-0 ms-3': messageView.isInChatWindowAndIsAlignedRight,
'end-0 me-3': messageView.isInChatWindowAndIsAlignedLeft || messageView.isInChatter,
'mt-n4': messageView.isInChatter,
'mt-2': (messageView.isInDiscuss and messageView.message.isDiscussionOrNotification) or messageView.isInChatWindow,
'ms-2': messageView.isInDiscuss,
}"
t-attf-class="{{ messageView.isActive ? 'visible' : 'invisible' }}"
>
<MessageActionList record="messageView.messageActionList"/>
</div>
</div>
<AttachmentList t-if="messageView.attachmentList" record="messageView.attachmentList"/>
<div t-if="messageView.message.messageReactionGroups.length > 0" class="d-flex flex-wrap ms-2">
<AttachmentList t-if="messageView.attachmentList" record="messageView.attachmentList" className="'position-relative'"/>
<div t-if="messageView.message.messageReactionGroups.length > 0" class="position-relative d-flex flex-wrap mt-n2"
t-att-class="{ 'flex-row-reverse me-3': messageView.isInChatWindowAndIsAlignedRight, 'ms-3': !(messageView.isInChatWindowAndIsAlignedRight) }">
<t t-foreach="messageView.message.messageReactionGroups" t-as="messageReactionGroup" t-key="messageReactionGroup.localId">
<MessageReactionGroup className="'me-1 mb-1'" record="messageReactionGroup"/>
<MessageReactionGroup className="'mb-1'" classNameObj="{ 'ms-1': messageView.isInChatWindowAndIsAlignedRight, 'me-1': !(messageView.isInChatWindowAndIsAlignedRight) }" record="messageReactionGroup"/>
</t>
</div>
</div>
@@ -2,10 +2,6 @@
font-size: 0.9rem;
}
.o_MessageActionList:hover {
box-shadow: 0 4px .5rem -.5rem $black;
}
.o_MessageActionList_action:hover {
background-color: mix($border-color, $white);
}
@@ -2,7 +2,7 @@
<templates>
<t t-name="mail.MessageActionList" owl="1">
<t t-if="messageActionList">
<div class="o_MessageActionList d-flex border rounded-1 bg-white" t-att-class="{ 'o-isDeviceSmall': messaging.device.isSmall }" t-attf-class="{{ className }}" t-on-click="messageActionList.onClick" t-ref="root">
<div class="o_MessageActionList d-flex rounded-1 bg-white shadow-sm" t-att-class="{ 'o-isDeviceSmall': messaging.device.isSmall }" t-attf-class="{{ className }}" t-on-click="messageActionList.onClick" t-ref="root">
<i t-if="messageActionList.message.hasReactionIcon" class="o_MessageActionList_action o_MessageActionList_actionReaction fa fa-lg fa-smile-o o_cursor_pointer" t-attf-class="{{ messaging.device.isSmall ? 'ps-3 py-3 pe-2' : 'ps-2 py-2 pe-1' }}" t-att-title="messageActionList.addReactionText" t-on-click="messageActionList.onClickActionReaction" t-ref="actionReaction"/>
<t t-if="!messageActionList.isCompact || !messageActionList.hasCompactIcon">
<span t-if="messageActionList.message.canStarBeToggled" class="o_MessageActionList_action o_MessageActionList_actionStar o_cursor_pointer" t-attf-class="{{ messaging.device.isSmall ? 'px-2 py-3' : 'px-1 py-2' }}" t-att-class="{
@@ -3,5 +3,5 @@
// ------------------------------------------------------------------
.o_MessageReactionGroup.o-hasUserReacted {
background-color: rgba($o-brand-primary, .1);
background: mix($o-brand-primary, white, 10%);
}
@@ -7,7 +7,7 @@
<t t-if="threadView.topbar">
<ThreadViewTopbar className="'border-bottom'" record="threadView.topbar"/>
</t>
<div class="o_ThreadView_bottomPart d-flex flex-grow-1">
<div class="o_ThreadView_bottomPart d-flex flex-grow-1" t-att-class="{ 'border-start border-end': threadView.threadViewer.chatWindow }">
<div class="o_ThreadView_core d-flex flex-column flex-grow-1">
<t t-if="threadView.callView">
<CallView record="threadView.callView"/>
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { many, one } from '@mail/model/model_field';
import { attr, many, one } from '@mail/model/model_field';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
@@ -71,6 +71,66 @@ registerModel({
_computeViewableAttachments() {
return replace(this.attachments.filter(attachment => attachment.isViewable));
},
/**
* @private
* @returns {boolean}
*/
_computeIsInDiscuss() {
return Boolean(
(this.messageViewOwner && this.messageViewOwner.isInDiscuss) ||
(this.composerViewOwner && this.composerViewOwner.isInDiscuss)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindow() {
return Boolean(
(this.messageViewOwner && this.messageViewOwner.isInChatWindow) ||
(this.composerViewOwner && this.composerViewOwner.isInChatWindow)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatter() {
return Boolean(
(this.messageViewOwner && this.messageViewOwner.isInChatter) ||
(this.composerViewOwner && this.composerViewOwner.isInChatter)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsCurrentUserOrGuestAuthor() {
return Boolean(
this.composerViewOwner ||
(this.messageViewOwner && this.messageViewOwner.message.isCurrentUserOrGuestAuthor)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindowAndIsAlignedRight() {
return Boolean(
this.isInChatWindow &&
this.isCurrentUserOrGuestAuthor
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindowAndIsAlignedLeft() {
return Boolean(
this.isInChatWindow &&
!this.isCurrentUserOrGuestAuthor
);
},
},
fields: {
/**
@@ -120,6 +180,42 @@ registerModel({
imageAttachments: many('Attachment', {
compute: '_computeImageAttachments',
}),
/**
* Determines if we are in the Discuss view.
*/
isInDiscuss: attr({
compute: '_computeIsInDiscuss',
}),
/**
* Determines if we are in the ChatWindow view.
*/
isInChatWindow: attr({
compute: '_computeIsInChatWindow',
}),
/**
* Determines if we are in the Chatter view.
*/
isInChatter: attr({
compute: '_computeIsInChatter',
}),
/**
* Determines if it comes from the current user.
*/
isCurrentUserOrGuestAuthor: attr({
compute: '_computeIsCurrentUserOrGuestAuthor',
}),
/**
* Determines if we are in the ChatWindow view AND if the message is right aligned
*/
isInChatWindowAndIsAlignedRight: attr({
compute: '_computeIsInChatWindowAndIsAlignedRight',
}),
/**
* Determines if we are in the ChatWindow view AND if the message is left aligned
*/
isInChatWindowAndIsAlignedLeft: attr({
compute: '_computeIsInChatWindowAndIsAlignedLeft',
}),
/**
* Link with a message view to handle attachments.
*/
@@ -907,6 +907,36 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {boolean}
*/
_computeIsInDiscuss() {
return Boolean(
(this.threadView && (this.threadView.threadViewer.discuss || this.threadView.threadViewer.discussPublicView)) ||
(this.messageViewInEditing && (this.messageViewInEditing.discuss || this.messageViewInEditing.discussPublicView))
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindow() {
return Boolean(
(this.threadView && this.threadView.threadViewer.chatWindow) ||
(this.messageViewInEditing && this.messageViewInEditing.chatWindow)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatter() {
return Boolean(
(this.threadView && this.threadView.threadViewer.chatter) ||
(this.messageViewInEditing && this.messageViewInEditing.isInChatter)
);
},
/**
* @private
* @returns {boolean|FieldCommand}
@@ -1480,6 +1510,24 @@ registerModel({
isFocused: attr({
default: false,
}),
/**
* Determines if we are in the Discuss view.
*/
isInDiscuss: attr({
compute: '_computeIsInDiscuss',
}),
/**
* Determines if we are in the ChatWindow view.
*/
isInChatWindow: attr({
compute: '_computeIsInChatWindow',
}),
/**
* Determines if we are in the Chatter view.
*/
isInChatter: attr({
compute: '_computeIsInChatter',
}),
/**
* Last content of textarea from input event. Useful to determine
* whether the current partner is typing something.
@@ -267,6 +267,82 @@ registerModel({
)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInDiscuss() {
return Boolean(
this.messageListViewMessageViewItemOwner &&
(
this.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.threadViewer.discuss ||
this.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.threadViewer.discussPublicView
)
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindow() {
return Boolean(
this.messageListViewMessageViewItemOwner &&
this.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.threadViewer.chatWindow
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatter() {
return Boolean(
this.messageListViewMessageViewItemOwner &&
this.messageListViewMessageViewItemOwner.messageListViewOwner.threadViewOwner.threadViewer.chatter
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindowAndIsAlignedRight() {
return Boolean(
this.isInChatWindow &&
this.message &&
this.message.isCurrentUserOrGuestAuthor
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsInChatWindowAndIsAlignedLeft() {
return Boolean(
this.isInChatWindow &&
this.message &&
!this.message.isCurrentUserOrGuestAuthor
);
},
/**
* @private
* @returns {boolean}
*/
_computeIsShowingAuthorName() {
return Boolean(
!(
this.isInChatWindow &&
(
(
this.message &&
this.message.isCurrentUserOrGuestAuthor
) ||
(
this.messageListViewMessageViewItemOwner &&
this.messageListViewMessageViewItemOwner.messageListViewOwner.thread.channel_type === 'chat'
)
)
)
);
},
/**
* @private
* @returns {boolean}
@@ -438,6 +514,42 @@ registerModel({
isHovered: attr({
default: false,
}),
/**
* Determines if we are in the Discuss view.
*/
isInDiscuss: attr({
compute: '_computeIsInDiscuss',
}),
/**
* Determines if we are in the ChatWindow view.
*/
isInChatWindow: attr({
compute: '_computeIsInChatWindow',
}),
/**
* Determines if we are in the Chatter view.
*/
isInChatter: attr({
compute: '_computeIsInChatter',
}),
/**
* Determines if we are in the ChatWindow view AND if the message is right aligned
*/
isInChatWindowAndIsAlignedRight: attr({
compute: '_computeIsInChatWindowAndIsAlignedRight',
}),
/**
* Determines if we are in the ChatWindow view AND if the message is left aligned
*/
isInChatWindowAndIsAlignedLeft: attr({
compute: '_computeIsInChatWindowAndIsAlignedLeft',
}),
/**
* Determines if the author name is displayed.
*/
isShowingAuthorName: attr({
compute: '_computeIsShowingAuthorName',
}),
/**
* Tells whether the message is selected in the current thread viewer.
*/
@@ -28,5 +28,5 @@ $o-mail-discuss-sidebar-category-item-avatar-left-margin: $o-mail-discuss-sideba
$o-mail-discuss-sidebar-category-item-avatar-size: 30px !default;
$o-mail-discuss-sidebar-scrollbar-width: 15px !default;
$o-mail-message-sidebar-width: 50px;
$o-mail-message-sidebar-width: 48px !default;
$o-mail-composer-text-input-height: 40px !default;