[IMP] mail: merge some models (reduce amount of models)

- [IMP] mail: merge chatter top bar with chatter
- [IMP] mail: merge attachment box into chatter
- [IMP] mail: merge activityBoxView into chatter
- [IMP] mail: merge follow/unfollow button into chatter
- [IMP] mail: merge emoji picker header into emoji picker
- [IMP] mail: merge emoji category bar into emoji picker
- [IMP] mail: merge emoji header list action into emoji picker
- [IMP] mail: merge emoji search bar into emoji picker
- [IMP] mail: merge emoji grid loading screen into emoji grid
- [IMP] mail: merge emoji grid no search into emoji grid
- [IMP] mail: merge emoji grid row section into emoji grid row
- [IMP] mail: merge emoji grid item into emoji view

closes odoo/odoo#105099

Related: odoo/enterprise#33681
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2022-11-06 00:28:16 +01:00
committed by Alexandre Kühn
parent 30087290e9
commit 1ad7685bbf
55 changed files with 459 additions and 770 deletions
@@ -1,15 +0,0 @@
// ------------------------------------------------------------------
// Layout
// ------------------------------------------------------------------
.o_ActivityBoxView_activityList {
max-width: var(--Chatter-max-width, none);
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_ActivityBoxView_titleLine {
border-top: $border-width $border-color dashed;
}
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityBoxView" owl="1">
<div class="o_ActivityBoxView" t-attf-class="{{ className }}" t-ref="root">
<a href="#" role="button" class="o_ActivityBoxView_title btn d-flex align-items-center mt-4 p-0 w-100 fw-bold" t-att-aria-expanded="isActivityListVisible ? 'true' : 'false'" t-on-click="onClickActivityBoxTitle">
<hr class="o_ActivityBoxView_titleLine w-auto flex-grow-1 me-3" />
<span class="o_ActivityBoxView_titleText">
<i class="fa fa-fw" t-att-class="isActivityListVisible ? 'fa-caret-down' : 'fa-caret-right'"/>
Planned activities
</span>
<span t-if="!isActivityListVisible" class="o_ActivityBoxView_titleBadges ms-2">
<span t-if="chatter.thread.overdueActivities.length > 0" class="o_ActivityBoxView_titleBadge me-1 badge text-bg-danger" t-esc="chatter.thread.overdueActivities.length"/>
<span t-if="chatter.thread.todayActivities.length > 0" class="o_ActivityBoxView_titleBadge me-1 badge text-bg-warning" t-esc="chatter.thread.todayActivities.length"/>
<span t-if="chatter.thread.futureActivities.length > 0" class="o_ActivityBoxView_titleBadge me-1 badge text-bg-success" t-esc="chatter.thread.futureActivities.length"/>
</span>
<hr class="o_ActivityBoxView_titleLine w-auto flex-grow-1 ms-3"/>
</a>
<div t-if="isActivityListVisible" class="o_ActivityBoxView_activityList">
<ActivityView t-foreach="activityViews" t-as="activityView" t-key="activityView.localId" className="'o_ActivityBoxView_activity'" record="activityView"/>
</div>
</div>
</t>
</templates>
@@ -1,7 +0,0 @@
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_AttachmentBoxView_dashedLine {
border-top: $border-width dashed $border-color;
}
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.AttachmentBoxView" owl="1">
<div class="o_AttachmentBoxView position-relative" t-attf-class="{{ className }}" t-ref="root">
<div class="o_AttachmentBoxView_title d-flex align-items-center">
<hr class="o_AttachmentBoxView_dashedLine flex-grow-1"/>
<span class="o_AttachmentBoxView_titleText p-3 fw-bold">
Files
</span>
<hr class="o_AttachmentBoxView_dashedLine flex-grow-1"/>
</div>
<div class="o_AttachmentBoxView_content d-flex flex-column">
<AttachmentList t-if="attachmentList"
className="'o_attachmentBox_attachmentList'"
record="attachmentList"
/>
<button class="o_AttachmentBoxView_buttonAdd btn btn-link" type="button" t-on-click="onClickAddAttachment" t-att-disabled="!chatter.hasWriteAccess">
<i class="fa fa-plus-square"/>
Attach files
</button>
</div>
</div>
</t>
</templates>
@@ -31,3 +31,24 @@
.o_ChatterTopbar_buttonClose {
width: $o-statusbar-height;
}
.o_ActivityBoxView_activityList {
max-width: var(--Chatter-max-width, none);
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_ActivityBoxView_titleLine {
border-top: $border-width $border-color dashed;
}
.o_AttachmentBoxView_dashedLine {
border-top: $border-width dashed $border-color;
}
.o_FollowButtonView_unfollow:focus {
background-color: $gray-200;
}
@@ -8,11 +8,91 @@
record="dropZoneView"
/>
<div class="o_Chatter_fixedPanel">
<ChatterTopbar
t-if="topbar"
className="'o_Chatter_topbar'"
record="topbar"
/>
<div t-if="hasTopBar" class="o_ChatterTopbar justify-content-between d-flex">
<div class="o_ChatterTopbar_actions flex-fill d-flex border-transparent">
<div class="o_ChatterTopbar_controllers d-flex pe-2" t-if="threadView">
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage btn text-nowrap me-2"
type="button"
t-att-class="{
'o-active btn-odoo': composerView and !composerView.composer.isLog,
'btn-odoo': !composerView,
'btn-light': composerView and composerView.composer.isLog,
}"
t-att-disabled="!hasWriteAccess"
data-hotkey="m"
t-on-click="onClickSendMessage"
>
Send message
</button>
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonLogNote btn text-nowrap"
type="button"
t-att-class="{
'o-active btn-odoo': composerView and composerView.composer.isLog,
'btn-light': composerView and !composerView.composer.isLog or !composerView,
}"
t-att-disabled="!hasWriteAccess"
t-on-click="onClickLogNote"
data-hotkey="shift+m"
>
Log note
</button>
</div>
<div class="o_ChatterTopbar_tools position-relative d-flex flex-grow-1 border-bottom"
t-att-class="{
'border-start ps-2': hasActivities,
}"
>
<button t-if="hasActivities" class="o_ChatterTopbar_button o_ChatterTopbar_buttonScheduleActivity btn btn-light text-nowrap" type="button" t-att-disabled="!hasWriteAccess" t-on-click="onClickScheduleActivity" data-hotkey="shift+a">
<i class="fa fa-clock-o me-1"/>
<span>Activities</span>
</button>
<div class="flex-grow-1 border-start pe-2"
t-att-class="{
'ms-2': hasActivities,
}"
/>
<div class="o_ChatterTopbar_rightSection flex-grow-1 flex-shrink-0 justify-content-end d-flex">
<button t-if="thread.allAttachments.length === 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonAddAttachments btn btn-light btn-primary" type="button" t-att-disabled="!hasWriteAccess" t-on-click="onClickButtonAddAttachments" >
<i class="fa fa-paperclip fa-lg me-1" role="img" aria-label="Attachments"/>
<i t-if="isShowingAttachmentsLoading" class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin" aria-label="Attachment counter loading..."/>
</button>
<button t-if="thread.allAttachments.length > 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonToggleAttachments btn btn-light btn-primary" type="button" t-att-disabled="!hasReadAccess" t-att-aria-expanded="attachmentBoxView ? 'true' : 'false'" t-on-click="onClickButtonToggleAttachments">
<i class="fa fa-paperclip fa-lg me-1" role="img" aria-label="Attachments"/>
<span t-if="!isShowingAttachmentsLoading" class="o_ChatterTopbar_buttonCount o_ChatterTopbar_buttonAttachmentsCount" t-esc="attachmentButtonText"/>
<i t-if="isShowingAttachmentsLoading" class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin" aria-label="Attachment counter loading..."/>
</button>
<t t-if="hasFollowers and thread">
<FollowerListMenuView
className="'o_ChatterTopbar_followerListMenu'"
record="followerListMenuView"
/>
<div t-if="hasFollowButton" class="o_ChatterTopbar_followButton o_FollowButtonView d-flex">
<button t-if="thread and thread.isCurrentPartnerFollowing" class="o_FollowButtonView_unfollow btn px-0" t-att-class="{ 'o_ChatterTopbar_button': chatter, 'o-following text-success': !isUnfollowButtonHighlighted, 'o-unfollow text-warning': isUnfollowButtonHighlighted }" t-att-disabled="isFollowButtonDisabled" t-on-click="onClickUnfollow" t-on-mouseenter="onMouseEnterUnfollow" t-on-mouseleave="onMouseleaveUnfollow">
<div class="position-relative">
<!-- Hidden element used to set the button maximum size -->
<span class="invisible">
<i class="fa fa-fw fa fa-eye-slash"/> <t t-out="followingText.length > unfollowingText.length ? followingText : unfollowingText"></t>
</span>
<span t-if="isUnfollowButtonHighlighted" class="o_FollowButtonView_text position-absolute end-0 top-0 bottom-0 text-warning">
<i class="fa fa-fw fa-times"/> <span t-out="unfollowingText"/>
</span>
<span t-else="" class="o_FollowButtonView_text d-block position-absolute end-0 top-0 bottom-0">
<i class="fa fa-fw fa-check"/> <span t-out="followingText"/>
</span>
</div>
</button>
<button t-else="" class="o_FollowButtonView_follow btn btn-link text-600 pe-0" t-att-disabled="isFollowButtonDisabled" t-on-click="onClickFollow" t-att-class="{ 'o_ChatterTopbar_button': chatter }">
Follow
</button>
</div>
</t>
</div>
</div>
</div>
<button t-if="hasTopbarCloseButton" class="o_ChatterTopbar_buttonClose btn btn-dark flex-shrink-0 rounded-bottom-3" aria-label="Close" t-on-click="onClickChatterTopbarClose">
<i class="oi oi-large oi-close"/>
</button>
</div>
<ComposerView t-if="composerView"
className="'o_Chatter_composer border-bottom'"
classNameObj="{ 'o-bordered': hasExternalBorder }"
@@ -20,14 +100,43 @@
/>
</div>
<div class="o_Chatter_scrollPanel overflow-auto" t-on-scroll="onScrollScrollPanel" t-ref="scrollPanel">
<AttachmentBoxView t-if="attachmentBoxView"
className="'o_Chatter_attachmentBox'"
record="attachmentBoxView"
/>
<ActivityBoxView t-if="activityBoxView"
className="'o_Chatter_activityBox'"
record="activityBoxView"
/>
<div t-if="hasAttachmentBox" class="o_Chatter_attachmentBox o_AttachmentBoxView position-relative">
<div class="o_AttachmentBoxView_title d-flex align-items-center">
<hr class="o_AttachmentBoxView_dashedLine flex-grow-1"/>
<span class="o_AttachmentBoxView_titleText p-3 fw-bold">
Files
</span>
<hr class="o_AttachmentBoxView_dashedLine flex-grow-1"/>
</div>
<div class="o_AttachmentBoxView_content d-flex flex-column">
<AttachmentList t-if="attachmentList"
className="'o_attachmentBox_attachmentList'"
record="attachmentList"
/>
<button class="o_AttachmentBoxView_buttonAdd btn btn-link" type="button" t-on-click="onClickButtonAddAttachments" t-att-disabled="!hasWriteAccess">
<i class="fa fa-plus-square"/>
Attach files
</button>
</div>
</div>
<div t-if="hasActivityBox" class="o_Chatter_activityBox o_ActivityBoxView">
<a href="#" role="button" class="o_ActivityBoxView_title btn d-flex align-items-center mt-4 p-0 w-100 fw-bold" t-att-aria-expanded="isActivityListVisible ? 'true' : 'false'" t-on-click="onClickActivityBoxTitle">
<hr class="o_ActivityBoxView_titleLine w-auto flex-grow-1 me-3" />
<span class="o_ActivityBoxView_titleText">
<i class="fa fa-fw" t-att-class="isActivityListVisible ? 'fa-caret-down' : 'fa-caret-right'"/>
Planned activities
</span>
<span t-if="!isActivityListVisible" class="o_ActivityBoxView_titleBadges ms-2">
<span t-if="thread.overdueActivities.length > 0" class="o_ActivityBoxView_titleBadge me-1 badge text-bg-danger" t-esc="thread.overdueActivities.length"/>
<span t-if="thread.todayActivities.length > 0" class="o_ActivityBoxView_titleBadge me-1 badge text-bg-warning" t-esc="thread.todayActivities.length"/>
<span t-if="thread.futureActivities.length > 0" class="o_ActivityBoxView_titleBadge me-1 badge text-bg-success" t-esc="thread.futureActivities.length"/>
</span>
<hr class="o_ActivityBoxView_titleLine w-auto flex-grow-1 ms-3"/>
</a>
<div t-if="isActivityListVisible" class="o_ActivityBoxView_activityList">
<ActivityView t-foreach="activityViews" t-as="activityView" t-key="activityView.localId" className="'o_ActivityBoxView_activity'" record="activityView"/>
</div>
</div>
<ThreadView t-if="threadView"
className="'o_Chatter_thread'"
record="threadView"
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ChatterTopbar" owl="1">
<div class="o_ChatterTopbar justify-content-between d-flex" t-attf-class="{{ className }}" t-ref="root">
<div class="o_ChatterTopbar_actions flex-fill d-flex border-transparent">
<div class="o_ChatterTopbar_controllers d-flex pe-2" t-if="chatter.threadView">
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage btn text-nowrap me-2"
type="button"
t-att-class="{
'o-active btn-odoo': chatter.composerView and !chatter.composerView.composer.isLog,
'btn-odoo': !chatter.composerView,
'btn-light': chatter.composerView and chatter.composerView.composer.isLog,
}"
t-att-disabled="!chatter.hasWriteAccess"
data-hotkey="m"
t-on-click="chatter.onClickSendMessage"
>
Send message
</button>
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonLogNote btn text-nowrap"
type="button"
t-att-class="{
'o-active btn-odoo': chatter.composerView and chatter.composerView.composer.isLog,
'btn-light': chatter.composerView and !chatter.composerView.composer.isLog or !chatter.composerView,
}"
t-att-disabled="!chatter.hasWriteAccess"
t-on-click="chatter.onClickLogNote"
data-hotkey="shift+m"
>
Log note
</button>
</div>
<div class="o_ChatterTopbar_tools position-relative d-flex flex-grow-1 border-bottom"
t-att-class="{
'border-start ps-2': chatter.hasActivities,
}"
>
<button t-if="chatter.hasActivities" class="o_ChatterTopbar_button o_ChatterTopbar_buttonScheduleActivity btn btn-light text-nowrap" type="button" t-att-disabled="!chatter.hasWriteAccess" t-on-click="chatter.onClickScheduleActivity" data-hotkey="shift+a">
<i class="fa fa-clock-o me-1"/>
<span>Activities</span>
</button>
<div class="flex-grow-1 border-start pe-2"
t-att-class="{
'ms-2': chatter.hasActivities,
}"
/>
<div class="o_ChatterTopbar_rightSection flex-grow-1 flex-shrink-0 justify-content-end d-flex">
<button t-if="chatter.thread.allAttachments.length === 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonAddAttachments btn btn-light btn-primary" type="button" t-att-disabled="!chatter.hasWriteAccess" t-on-click="chatter.onClickButtonAddAttachments" >
<i class="fa fa-paperclip fa-lg me-1" role="img" aria-label="Attachments"/>
<i t-if="chatter.isShowingAttachmentsLoading" class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin" aria-label="Attachment counter loading..."/>
</button>
<button t-if="chatter.thread.allAttachments.length > 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonToggleAttachments btn btn-light btn-primary" type="button" t-att-disabled="!chatter.hasReadAccess" t-att-aria-expanded="chatter.attachmentBoxView ? 'true' : 'false'" t-on-click="chatter.onClickButtonToggleAttachments">
<i class="fa fa-paperclip fa-lg me-1" role="img" aria-label="Attachments"/>
<span t-if="!chatter.isShowingAttachmentsLoading" class="o_ChatterTopbar_buttonCount o_ChatterTopbar_buttonAttachmentsCount" t-esc="attachmentButtonText"/>
<i t-if="chatter.isShowingAttachmentsLoading" class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin" aria-label="Attachment counter loading..."/>
</button>
<t t-if="chatter.hasFollowers and chatter.thread">
<FollowerListMenuView
className="'o_ChatterTopbar_followerListMenu'"
record="chatter.followerListMenuView"
/>
<FollowButtonView t-if="chatter.followButtonView"
className="'o_ChatterTopbar_followButton'"
record="chatter.followButtonView"
/>
</t>
</div>
</div>
</div>
<button t-if="chatter.hasTopbarCloseButton" class="o_ChatterTopbar_buttonClose btn btn-dark flex-shrink-0 rounded-bottom-3" aria-label="Close" t-on-click="chatter.onClickChatterTopbarClose">
<i class="oi oi-large oi-close"/>
</button>
</div>
</t>
</templates>
@@ -1,3 +0,0 @@
.o_EmojiCategoryBarView {
background-color: var(--EmojiCategoryBarView-background-color, #{$o-gray-200});
}
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiCategoryBarView" owl="1">
<div class="o_EmojiCategoryBarView d-flex flex-shrink-0 overflow-auto border-end" t-attf-class="{{ className }}" t-ref="root">
<EmojiCategoryView t-foreach="emojiCategoryViews" t-as="emojiCategoryView" t-key="emojiCategoryView.localId" record="emojiCategoryView"/>
</div>
</t>
</templates>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiGridItemView" owl="1">
<div class="o_EmojiGridItemView d-flex justify-content-center" t-attf-style="width: {{width}}px;">
<EmojiView t-if="emojiView" className="'o_EmojiGrid_emoji w-100 h-100'" record="emojiView"/>
</div>
</t>
</templates>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiGridLoadingScreen" owl="1">
<div class="o_EmojiGridLoadingScreen d-flex flex-column justify-content-center align-items-center w-100 h-100" t-attf-class="{{ className }}" t-ref="root">
<div class="o_EmojiGridLoadingScreen_spinner fa fa-circle-o-notch fa-spin fs-1"/>
<span class="o_EmojiGridLoadingScreen_text text-muted mt-4" t-out="text"/>
</div>
</t>
</templates>
@@ -3,9 +3,11 @@
<t t-name="mail.EmojiGridRowView" owl="1">
<div class="o_EmojiGridRowView">
<EmojiGridSectionView t-if="sectionView" record="sectionView"/>
<div t-if="hasSection" class="o_EmojiGridSectionView d-flex justify-content-center align-items-end mx-2 text-truncate">
<span t-out="category.name"/>
</div>
<t t-else="">
<EmojiGridItemView t-foreach="items" t-as="item" t-key="item" className="'o_EmojiGridRowView_item'" record="item"/>
<EmojiView t-foreach="items" t-as="item" t-key="item" className="'o_EmojiGridRowView_item'" record="item"/>
</t>
</div>
</t>
@@ -1,5 +0,0 @@
.o_EmojiGridSearchNoContentView_image {
filter: grayscale(1);
font-size: 5rem;
}
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiGridSearchNoContentView" owl="1">
<div class="o_EmojiGridSearchNoContentView d-flex flex-column justify-content-center align-items-center w-100 h-100" t-attf-class="{{ className }}" t-ref="root">
<div class="o_EmojiGridSearchNoContentView_image">😢</div>
<span class="o_EmojiGridSearchNoContentView_text text-muted" t-out="text"/>
</div>
</t>
</templates>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiGridSectionView" owl="1">
<div class="o_EmojiGridSectionView d-flex justify-content-center align-items-end mx-2 text-truncate">
<span t-out="category.name"/>
</div>
</t>
</templates>
@@ -3,9 +3,15 @@
<t t-name="mail.EmojiGridView" owl="1">
<div class="o_EmojiGridView o_EmojiGridView_container" t-attf-style="--rowHeight: {{rowHeight}}px; --itemWidth: {{itemWidth}}px; height: {{height}}px; width: {{width}}px;" t-on-scroll="onScroll" t-ref="containerRef">
<EmojiGridLoadingScreen t-if="!this.messaging.emojiRegistry.isLoaded" record="loadingScreenView"/>
<EmojiGridSearchNoContentView t-if="searchNoContentView" record="searchNoContentView"/>
<div t-if="!searchNoContentView" class="o_EmojiGridView_list" t-attf-style="height: {{listHeight}}px; min-height: {{listHeight}}px;" t-ref="listRef">
<div t-if="!this.messaging.emojiRegistry.isLoaded" class="o_EmojiGridLoadingScreen d-flex flex-column justify-content-center align-items-center w-100 h-100">
<div class="o_EmojiGridLoadingScreen_spinner fa fa-circle-o-notch fa-spin fs-1"/>
<span class="o_EmojiGridLoadingScreen_text text-muted mt-4" t-out="loadingText"/>
</div>
<div t-if="hasNoSearchResult" class="o_EmojiGridSearchNoContentView d-flex flex-column justify-content-center align-items-center w-100 h-100">
<div class="o_EmojiGridSearchNoContentView_image">😢</div>
<span class="o_EmojiGridSearchNoContentView_text text-muted" t-out="noSearchResultText"/>
</div>
<div t-else="" class="o_EmojiGridView_list" t-attf-style="height: {{listHeight}}px; min-height: {{listHeight}}px;" t-ref="listRef">
<div t-if="lastRenderedRowIndex >= firstRenderedRowIndex" class="o_EmojiGridView_viewBlock" t-attf-style="top: {{ distanceFromTop - distanceInRowOffset }}px; width: {{ width }}px;" t-ref="viewBlockRef">
<EmojiGridRowView t-foreach="renderedRows" t-as="row" t-key="row" record="row"/>
</div>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiPickerHeaderActionListView" owl="1">
<div class="o_EmojiPickerHeaderActionListView d-flex" t-attf-class="{{ className }}" t-ref="root">
<EmojiPickerHeaderActionView t-foreach="actionViews" t-as="actionView" t-key="actionView" record="actionView"/>
</div>
</t>
</templates>
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.EmojiPickerHeaderView" owl="1">
<div class="o_EmojiPickerHeaderView d-flex bg-100 border-bottom" t-attf-class="{{ className }}" t-ref="root">
<EmojiCategoryBarView record="emojiCategoryBarView"/>
<span class="flex-grow-1"/>
<EmojiPickerHeaderActionListView t-if="actionListView" record="actionListView"/>
</div>
</t>
</templates>
@@ -13,3 +13,12 @@
.o_EmojiSearchBarView_searchInput:focus {
outline: none;
}
.o_EmojiCategoryBarView {
background-color: var(--EmojiCategoryBarView-background-color, #{$o-gray-200});
}
.o_EmojiGridSearchNoContentView_image {
filter: grayscale(1);
font-size: 5rem;
}
@@ -3,8 +3,21 @@
<t t-name="mail.EmojiPickerView" owl="1">
<div class="o_EmojiPickerView d-flex flex-column" t-attf-class="{{ className }}" t-ref="root">
<EmojiPickerHeaderView record="headerView"/>
<EmojiSearchBarView record="emojiSearchBarView"/>
<div class="o_EmojiPickerHeaderView d-flex bg-100 border-bottom">
<div class="o_EmojiCategoryBarView d-flex flex-shrink-0 overflow-auto border-end">
<EmojiCategoryView t-foreach="emojiCategoryViews" t-as="emojiCategoryView" t-key="emojiCategoryView.localId" record="emojiCategoryView"/>
</div>
<span class="flex-grow-1"/>
<div class="o_EmojiPickerHeaderActionListView d-flex">
<EmojiPickerHeaderActionView t-foreach="actionViews" t-as="actionView" t-key="actionView" record="actionView"/>
</div>
</div>
<div class="o_EmojiSearchBarView overflow-auto d-flex flex-shrink-0 p-2 flex-column border-bottom">
<div class="o_EmojiSearchBarView_inputContainer d-flex flex-grow-1 align-items-center">
<input class="o_EmojiSearchBarView_searchInput o_input form-control overflow-auto flex-fill" t-att-placeholder="placeholder" t-on-focusin="onFocusinInput" t-on-focusout="onFocusoutInput" t-on-input="onInput" t-ref="input"/>
<i class="o_EmojiSearchBarView_icon oi oi-search ps-2" title="Search..." role="img" aria-label="Search..."/>
</div>
</div>
<EmojiGridView record="emojiGridView"/>
</div>
</t>
@@ -1,13 +0,0 @@
<templates xml:space="preserve">
<t t-name="mail.EmojiSearchBarView" owl="1">
<div class="o_EmojiSearchBarView overflow-auto d-flex flex-shrink-0 p-2 flex-column border-bottom" t-attf-class="{{ className }}" t-ref="root">
<div class="o_EmojiSearchBarView_inputContainer d-flex flex-grow-1 align-items-center">
<input class="o_EmojiSearchBarView_searchInput o_input form-control overflow-auto flex-fill" t-att-placeholder="placeholder" t-on-focusin="onFocusinInput" t-on-focusout="onFocusoutInput" t-on-input="onInput" t-ref="input"/>
<i class="o_EmojiSearchBarView_icon oi oi-search ps-2" title="Search..." role="img" aria-label="Search..."/>
</div>
</div>
</t>
</templates>
@@ -2,7 +2,9 @@
<templates xml:space="preserve">
<t t-name="mail.EmojiView" owl="1">
<span class="o_EmojiView p-1 fs-3 cursor-pointer" t-att-class="emojiGridViewAsHovered ? 'bg-200' : ''" t-esc="emoji.codepoints" t-on-click="onClick" t-on-mouseenter="onMouseenter" t-on-mouseleave="onMouseleave" t-att-title="emoji.name" t-att-data-source="emoji.sources[0]" t-att-data-codepoints="emoji.codepoints" t-attf-class="{{ className }}" t-ref="root"/>
<div class="o_EmojiViewItem d-flex justify-content-center" t-attf-style="width: {{width}}px;">
<span class="o_EmojiView p-1 fs-3 cursor-pointer" t-att-class="emojiGridViewAsHovered ? 'bg-200' : ''" t-esc="emoji.codepoints" t-on-click="onClick" t-on-mouseenter="onMouseenter" t-on-mouseleave="onMouseleave" t-att-title="emoji.name" t-att-data-source="emoji.sources[0]" t-att-data-codepoints="emoji.codepoints" t-attf-class="{{ className }}" t-ref="root"/>
</div>
</t>
</templates>
@@ -1,3 +0,0 @@
.o_FollowButtonView_unfollow:focus {
background-color: $gray-200;
}
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.FollowButtonView" owl="1">
<div class="o_FollowButtonView d-flex" t-attf-class="{{ className }}" t-ref="root">
<button t-if="chatterOwner.thread and chatterOwner.thread.isCurrentPartnerFollowing" class="o_FollowButtonView_unfollow btn px-0" t-att-class="{ 'o_ChatterTopbar_button': chatterOwner, 'o-following text-success': !isUnfollowButtonHighlighted, 'o-unfollow text-warning': isUnfollowButtonHighlighted }" t-att-disabled="isDisabled" t-on-click="onClickUnfollow" t-on-mouseenter="onMouseEnterUnfollow" t-on-mouseleave="onMouseleaveUnfollow">
<div class="position-relative">
<!-- Hidden element used to set the button maximum size -->
<span class="invisible">
<i class="fa fa-fw fa fa-eye-slash"/> <t t-out="followingText.length > unfollowingText.length ? followingText : unfollowingText"></t>
</span>
<span t-if="isUnfollowButtonHighlighted" class="o_FollowButtonView_text position-absolute end-0 top-0 bottom-0 text-warning">
<i class="fa fa-fw fa-times"/> <span t-out="unfollowingText"/>
</span>
<span t-else="" class="o_FollowButtonView_text d-block position-absolute end-0 top-0 bottom-0">
<i class="fa fa-fw fa-check"/> <span t-out="followingText"/>
</span>
</div>
</button>
<button t-else="" class="o_FollowButtonView_follow btn btn-link text-600 pe-0" t-att-disabled="isDisabled" t-on-click="onClickFollow" t-att-class="{ 'o_ChatterTopbar_button': chatterOwner }">
Follow
</button>
</div>
</t>
</templates>
@@ -1,29 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
registerModel({
name: 'ActivityBoxView',
template: 'mail.ActivityBoxView',
recordMethods: {
/**
* Handles click on activity box title.
*/
onClickActivityBoxTitle(ev) {
ev.preventDefault();
this.update({ isActivityListVisible: !this.isActivityListVisible });
},
},
fields: {
activityViews: many('ActivityView', { inverse: 'activityBoxView',
compute() {
return this.chatter.thread.activities.map(activity => {
return { activity };
});
},
}),
chatter: one('Chatter', { identifying: true, inverse: 'activityBoxView' }),
isActivityListVisible: attr({ default: true }),
},
});
@@ -39,7 +39,7 @@ registerModel({
* Handles click on this "Done" button.
*/
async onClickDone() {
const chatter = this.activityViewOwner && this.activityViewOwner.activityBoxView.chatter;
const chatter = this.activityViewOwner && this.activityViewOwner.chatterOwner;
const reloadFunc = this.reloadFunc;
const webRecord = this.webRecord;
const thread = this.activity.thread;
@@ -60,7 +60,7 @@ registerModel({
* Handles click on this "Done & Schedule Next" button.
*/
async onClickDoneAndScheduleNext() {
const chatter = this.activityViewOwner && this.activityViewOwner.activityBoxView.chatter;
const chatter = this.activityViewOwner && this.activityViewOwner.chatterOwner;
const reloadFunc = this.reloadFunc;
const webRecord = this.webRecord;
const thread = this.activity.thread;
@@ -34,7 +34,7 @@ registerModel({
* Handles the click on the cancel button
*/
async onClickCancel() {
const { chatter } = this.activityBoxView; // save value before deleting activity
const chatter = this.chatterOwner; // save value before deleting activity
await this.activity.deleteServerRecord();
if (chatter.exists() && chatter.component) {
chatter.reloadParentView();
@@ -51,7 +51,7 @@ registerModel({
* Handles the click on the edit button
*/
async onClickEdit() {
const { chatter } = this.activityBoxView;
const chatter = this.chatterOwner;
await this.activity.edit();
if (chatter.exists() && chatter.component) {
chatter.reloadParentView();
@@ -70,7 +70,6 @@ registerModel({
},
fields: {
activity: one('Activity', { identifying: true, inverse: 'activityViews' }),
activityBoxView: one('ActivityBoxView', { identifying: true, inverse: 'activityViews' }),
/**
* Determines whether the details are visible.
*/
@@ -86,6 +85,7 @@ registerModel({
return sprintf(this.env._t("for %s"), this.activity.assignee.nameOrDisplayName);
},
}),
chatterOwner: one('Chatter', { identifying: true, inverse: 'activityViews' }),
clockWatcher: one('ClockWatcher', { default: { clock: { frequency: 60 * 1000 } }, inverse: 'activityViewOwner' }),
/**
* Compute the label for "when" the activity is due.
@@ -1,40 +0,0 @@
/** @odoo-module **/
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'AttachmentBoxView',
template: 'mail.AttachmentBoxView',
componentSetup() {
useComponentToModel({ fieldName: 'component' });
},
recordMethods: {
/**
* Handles click on the "add attachment" button.
*/
onClickAddAttachment() {
this.fileUploader.openBrowserFileUploader();
},
},
fields: {
/**
* Determines the attachment list that will be used to display the attachments.
*/
attachmentList: one('AttachmentList', { inverse: 'attachmentBoxViewOwner',
compute() {
return (this.chatter.thread && this.chatter.thread.allAttachments.length > 0)
? {}
: clear();
},
}),
chatter: one('Chatter', { identifying: true, inverse: 'attachmentBoxView' }),
/**
* States the OWL component displaying this attachment box.
*/
component: attr(),
fileUploader: one('FileUploader', { default: {}, inverse: 'attachmentBoxView', readonly: true, required: true }),
},
});
@@ -54,17 +54,15 @@ registerModel({
compute() {
if (
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm &&
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner &&
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.chatterOwner
) {
return this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter;
return this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.chatterOwner;
}
if (
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm &&
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner &&
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.chatterOwner
) {
return this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter;
return this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.chatterOwner;
}
return clear();
},
@@ -90,7 +90,7 @@ registerModel({
if (this.attachmentList.composerViewOwner) {
return 50;
}
if (this.attachmentList.attachmentBoxViewOwner) {
if (this.attachmentList.chatterOwner) {
return 160;
}
if (this.attachmentList.messageViewOwner) {
@@ -28,9 +28,9 @@ registerModel({
},
fields: {
/**
* Link with a AttachmentBoxView to handle attachments.
* Link with a chatter to handle attachments.
*/
attachmentBoxViewOwner: one('AttachmentBoxView', { identifying: true, inverse: 'attachmentList' }),
chatterOwner: one('Chatter', { identifying: true, inverse: 'attachmentList' }),
/**
* States the attachment cards that are displaying this nonImageAttachments.
*/
@@ -56,8 +56,8 @@ registerModel({
if (this.messageViewOwner) {
return this.messageViewOwner.message.attachments;
}
if (this.attachmentBoxViewOwner) {
return this.attachmentBoxViewOwner.chatter.thread.allAttachments;
if (this.chatterOwner) {
return this.chatterOwner.thread.allAttachments;
}
if (this.composerViewOwner && this.composerViewOwner.composer) {
return this.composerViewOwner.composer.attachments;
+123 -21
View File
@@ -2,7 +2,7 @@
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { attr, many, one } from '@mail/model/model_field';
import { clear, insert, link } from '@mail/model/model_field_command';
const getThreadNextTemporaryId = (function () {
@@ -40,6 +40,13 @@ registerModel({
isShowingAttachmentsLoading: true,
});
},
/**
* Handles click on activity box title.
*/
onClickActivityBoxTitle(ev) {
ev.preventDefault();
this.update({ isActivityListVisible: !this.isActivityListVisible });
},
/**
* Handles click on the attach button.
*/
@@ -50,7 +57,7 @@ registerModel({
* Handles click on the attachments button.
*/
onClickButtonToggleAttachments() {
this.update({ attachmentBoxView: this.attachmentBoxView ? clear() : {} });
this.update({ hasAttachmentBox: this.hasAttachmentBox ? clear() : true });
},
/**
* Handles click on top bar close button.
@@ -60,6 +67,15 @@ registerModel({
onClickChatterTopbarClose(ev) {
this.component.trigger('o-close-chatter');
},
/**
* @param {MouseEvent} ev
*/
onClickFollow(ev) {
if (!this.exists() || !this.thread) {
return;
}
this.thread.follow();
},
/**
* Handles click on "log note" button.
*
@@ -95,6 +111,34 @@ registerModel({
this.showSendMessage();
}
},
/**
* @param {MouseEvent} ev
*/
onClickUnfollow(ev) {
if (!this.exists() || !this.thread) {
return;
}
this.thread.unfollow();
this.reloadParentView({ fieldNames: ['message_follower_ids'] });
},
/**
* @param {MouseEvent} ev
*/
onMouseEnterUnfollow(ev) {
if (!this.exists()) {
return;
}
this.update({ isUnfollowButtonHighlighted: true });
},
/**
* @param {MouseEvent} ev
*/
onMouseleaveUnfollow(ev) {
if (!this.exists()) {
return;
}
this.update({ isUnfollowButtonHighlighted: false });
},
/**
* Handles scroll on this scroll panel.
*
@@ -106,8 +150,8 @@ registerModel({
}
this.threadView.messageListView.onScroll();
},
openAttachmentBoxView() {
this.update({ attachmentBoxView: {} });
openAttachmentBox() {
this.update({ hasAttachmentBox: true });
},
/**
* Open a dialog to add partners as followers.
@@ -186,7 +230,7 @@ registerModel({
this.thread.delete();
}
this.update({
attachmentBoxView: this.isAttachmentBoxVisibleInitially ? {} : clear(),
hasAttachmentBox: this.isAttachmentBoxVisibleInitially ? true : clear(),
thread: insert({
// If the thread was considered to have the activity
// mixin once, it will have it forever.
@@ -205,7 +249,7 @@ registerModel({
});
const nextId = getThreadNextTemporaryId();
this.update({
attachmentBoxView: clear(),
hasAttachmentBox: clear(),
thread: insert({
areAttachmentsLoaded: true,
id: nextId,
@@ -240,15 +284,41 @@ registerModel({
},
},
fields: {
activityBoxView: one('ActivityBoxView', { inverse: 'chatter',
activityViews: many('ActivityView', { inverse: 'chatterOwner',
compute() {
if (this.thread && this.thread.hasActivities && this.thread.activities.length > 0) {
return {};
if (!this.thread) {
return clear();
}
return clear();
return this.thread.activities.map(activity => {
return { activity };
});
},
}),
/**
* Determines the attachment list that will be used to display the attachments.
*/
attachmentList: one('AttachmentList', { inverse: 'chatterOwner',
compute() {
return (this.thread && this.thread.allAttachments.length > 0)
? {}
: clear();
},
}),
/**
* Determines the label on the attachment button of the topbar.
*/
attachmentButtonText: attr({ default: "",
compute() {
if (!this.thread) {
return clear();
}
const attachments = this.thread.allAttachments;
if (attachments.length === 0) {
return clear();
}
return attachments.length;
},
}),
attachmentBoxView: one('AttachmentBoxView', { inverse: 'chatter' }),
attachmentsLoaderTimer: one('Timer', { inverse: 'chatterOwnerAsAttachmentsLoader' }),
/**
* States the OWL Chatter component of this chatter.
@@ -275,14 +345,6 @@ registerModel({
return this.thread ? {} : clear();
},
}),
followButtonView: one('FollowButtonView', { inverse: 'chatterOwner',
compute() {
if (this.hasFollowers && this.thread && (!this.thread.channel || this.thread.channel.channel_type !== 'chat')) {
return {};
}
return clear();
},
}),
followerListMenuView: one('FollowerListMenuView', { inverse: 'chatterOwner',
compute() {
if (this.hasFollowers && this.thread) {
@@ -291,11 +353,36 @@ registerModel({
return clear();
},
}),
/**
* Text displayed by the follow button when not already followed.
*/
followingText: attr({
compute() {
return this.env._t("Following");
},
}),
/**
* Determines whether `this` should display an activity box.
*/
hasActivities: attr({ default: true }),
hasActivityBox: attr({ default: false,
compute() {
if (this.thread && this.thread.hasActivities && this.thread.activities.length > 0) {
return true;
}
return clear();
},
}),
hasAttachmentBox: attr({ default: false }),
hasExternalBorder: attr({ default: true }),
hasFollowButton: attr({ default: false,
compute() {
if (this.hasFollowers && this.thread && (!this.thread.channel || this.thread.channel.channel_type !== 'chat')) {
return true;
}
return clear();
},
}),
/**
* Determines whether `this` should display followers menu.
*/
@@ -328,6 +415,11 @@ registerModel({
return Boolean(this.thread && this.hasMessageList);
},
}),
hasTopBar: attr({ default: false,
compute() {
return this.thread ? true : clear();
},
}),
hasWriteAccess: attr({
compute() {
return Boolean(this.thread && !this.thread.isTemporary && this.thread.hasWriteAccess);
@@ -340,10 +432,16 @@ registerModel({
* of this record.
*/
id: attr({ identifying: true }),
isActivityListVisible: attr({ default: true }),
/**
* Determiners whether the attachment box is visible initially.
*/
isAttachmentBoxVisibleInitially: attr({ default: false }),
isFollowButtonDisabled: attr({
compute() {
return !this.hasReadAccess;
},
}),
isInFormSheetBg: attr({ default: false }),
isPreparingAttachmentsLoading: attr({ default: false,
compute() {
@@ -351,6 +449,7 @@ registerModel({
},
}),
isShowingAttachmentsLoading: attr({ default: false }),
isUnfollowButtonHighlighted: attr({ default: false }),
scrollPanelRef: attr({ ref: 'scrollPanel' }),
/**
* Determines whether the view should reload after file changed in this chatter,
@@ -392,9 +491,12 @@ registerModel({
};
},
}),
topbar: one('ChatterTopbar', { inverse: 'chatter',
/**
* Text displayed by the follow button when already followed.
*/
unfollowingText: attr({
compute() {
return this.thread ? {} : clear();
return this.env._t("Unfollow");
},
}),
useDragVisibleDropZone: one('UseDragVisibleDropZone', { default: {}, inverse: 'chatterOwner', readonly: true, required: true }),
@@ -1,28 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'ChatterTopbar',
template: 'mail.ChatterTopbar',
fields: {
/**
* Determines the label on the attachment button of the topbar.
*/
attachmentButtonText: attr({ default: "",
compute() {
if (!this.chatter || !this.chatter.thread) {
return clear();
}
const attachments = this.chatter.thread.allAttachments;
if (attachments.length === 0) {
return clear();
}
return attachments.length;
},
}),
chatter: one('Chatter', { identifying: true, inverse: 'topbar' }),
},
});
@@ -1,22 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { many, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiCategoryBarView',
template: 'mail.EmojiCategoryBarView',
fields: {
emojiCategoryViews: many('EmojiCategoryView', { inverse: 'emojiCategoryBarViewOwner',
compute() {
if (!this.emojiPickerView) {
return clear();
}
return this.emojiPickerView.categories.map(category => ({ viewCategory: category }));
},
}),
emojiPickerHeaderViewOwner: one('EmojiPickerHeaderView', { identifying: true, inverse: 'emojiCategoryBarView' }),
emojiPickerView: one('EmojiPickerView', { related: 'emojiPickerHeaderViewOwner.emojiPickerViewOwner' }),
},
});
@@ -11,8 +11,8 @@ registerModel({
* @param {MouseEvent} ev
*/
onClick() {
this.emojiPickerView.emojiSearchBarView.reset();
this.emojiPickerView.emojiGridView.update({ categorySelectedByUser: this.viewCategory });
this.emojiPickerViewOwner.reset();
this.emojiPickerViewOwner.emojiGridView.update({ categorySelectedByUser: this.viewCategory });
},
/**
* @param {MouseEvent} ev
@@ -35,8 +35,7 @@ registerModel({
},
fields: {
category: one('EmojiCategory', { related: 'viewCategory.category' }),
emojiCategoryBarViewOwner: one('EmojiCategoryBarView', { identifying: true, inverse: 'emojiCategoryViews' }),
emojiPickerView: one('EmojiPickerView', { related: 'emojiCategoryBarViewOwner.emojiPickerView' }),
emojiPickerViewOwner: one('EmojiPickerView', { identifying: true, inverse: 'emojiCategoryViews' }),
isActive: attr({
compute() {
return Boolean(this.viewCategory.emojiPickerViewAsActive);
@@ -1,33 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiGridItemView',
template: 'mail.EmojiGridItemView',
fields: {
emojiOrEmojiInCategory: one('EmojiOrEmojiInCategory', { identifying: true, inverse: 'emojiGridItemViews' }),
emojiGridRowViewOwner: one('EmojiGridRowView', { identifying: true, inverse: 'items' }),
emojiView: one('EmojiView', { inverse: 'emojiGridItemViewOwner',
compute() {
if (this.emojiOrEmojiInCategory.emoji) {
return { emoji: this.emojiOrEmojiInCategory.emoji };
}
if (this.emojiOrEmojiInCategory.emojiInCategory) {
return { emoji: this.emojiOrEmojiInCategory.emojiInCategory.emoji };
}
return clear();
},
}),
width: attr({ default: 0,
compute() {
if (!this.emojiGridRowViewOwner.emojiGridViewOwner) {
return clear();
}
return this.emojiGridRowViewOwner.emojiGridViewOwner.itemWidth;
},
}),
},
});
@@ -1,17 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'EmojiGridLoadingScreen',
template: 'mail.EmojiGridLoadingScreen',
fields: {
emojiGridViewOwner: one('EmojiGridView', { identifying: true, inverse: 'loadingScreenView' }),
text: attr({
compute() {
return this.env._t("Loading...");
},
}),
},
});
@@ -8,17 +8,18 @@ registerModel({
name: 'EmojiGridRowView',
template: 'mail.EmojiGridRowView',
fields: {
category: one('EmojiCategory', { related: 'viewCategory.category' }),
emojiGridViewOwner: one('EmojiGridView', { related: 'emojiGridViewRowRegistryOwner.emojiGridViewOwner' }),
index: attr({ identifying: true }),
items: many('EmojiGridItemView', { inverse: 'emojiGridRowViewOwner' }),
sectionView: one('EmojiGridSectionView', { inverse: 'emojiGridRowViewOwner',
hasSection: attr({ default: false,
compute() {
if (this.viewCategory) {
return {};
return true;
}
return clear();
},
}),
index: attr({ identifying: true }),
items: many('EmojiView', { inverse: 'emojiGridRowViewOwner' }),
emojiGridViewRowRegistryOwner: one('EmojiGridViewRowRegistry', { identifying: true, inverse: 'rows' }),
viewCategory: one('EmojiPickerView.Category', { inverse: 'emojiGridRowView' }),
},
@@ -1,17 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'EmojiGridSearchNoContentView',
template: 'mail.EmojiGridSearchNoContentView',
fields: {
emojiGridViewOwner: one('EmojiGridView', { identifying: true, inverse: 'searchNoContentView' }),
text: attr({
compute() {
return this.env._t("No emoji match your search");
},
}),
},
});
@@ -1,14 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
registerModel({
name: 'EmojiGridSectionView',
template: 'mail.EmojiGridSectionView',
fields: {
category: one('EmojiCategory', { related: 'viewCategory.category' }),
emojiGridRowViewOwner: one('EmojiGridRowView', { identifying: true, inverse: 'sectionView' }),
viewCategory: one('EmojiPickerView.Category', { related: 'emojiGridRowViewOwner.viewCategory' }),
},
});
@@ -24,7 +24,7 @@ registerModel({
onComponentUpdate() {
if (
this.categorySelectedByUser &&
this.emojiPickerViewOwner.emojiSearchBarView.currentSearch === ""
this.emojiPickerViewOwner.currentSearch === ""
) {
this.doJumpToCategorySelectedByUser();
}
@@ -53,7 +53,7 @@ registerModel({
* Filters emoji according to the current search terms.
*/
_filterEmoji(emoji) {
return (emoji._isStringInEmojiKeywords(this.emojiPickerViewOwner.emojiSearchBarView.currentSearch));
return (emoji._isStringInEmojiKeywords(this.emojiPickerViewOwner.currentSearch));
},
},
fields: {
@@ -96,6 +96,15 @@ registerModel({
);
},
}),
hasNoSearchResult: attr({
compute() {
if (this.emojiPickerViewOwner.currentSearch !== "" && this.rows.length === 0) {
return true;
}
return clear();
},
default: false,
}),
height: attr({
compute() {
return this.rowHeight * 9.5;
@@ -121,15 +130,17 @@ registerModel({
},
}),
listRef: attr({ ref: 'listRef' }),
loadingScreenView: one('EmojiGridLoadingScreen', { inverse: 'emojiGridViewOwner',
loadingText: attr({
compute() {
if (!this.messaging.emojiRegistry.isLoaded) {
return {};
}
return clear();
return this.env._t("Loading...");
},
}),
nonSearchRowRegistry: one('EmojiGridViewRowRegistry', { default: {}, inverse: 'emojiGridViewOwnerAsNonSearch' }),
noSearchResultText: attr({
compute() {
return this.env._t("No emoji match your search");
},
}),
onScrollThrottle: one('Throttle', { inverse: 'emojiGridViewAsOnScroll',
compute() {
return { func: () => this.update({ scrollRecomputeCount: increment() }) };
@@ -159,7 +170,7 @@ registerModel({
rowHeight: attr({ default: 30 }),
rows: many('EmojiGridRowView', {
compute() {
if (this.emojiPickerViewOwner.emojiSearchBarView.currentSearch !== "") {
if (this.emojiPickerViewOwner.currentSearch !== "") {
return this.searchRowRegistry.rows;
}
return this.nonSearchRowRegistry.rows;
@@ -188,14 +199,6 @@ registerModel({
}),
scrollbarThresholdWidth: attr({ default: 15 }),
scrollRecomputeCount: attr({ default: 0 }),
searchNoContentView: one('EmojiGridSearchNoContentView', { inverse: 'emojiGridViewOwner',
compute() {
if (this.emojiPickerViewOwner.emojiSearchBarView.currentSearch !== "" && this.rows.length === 0) {
return {};
}
return clear();
},
}),
searchRowRegistry: one('EmojiGridViewRowRegistry', { default: {}, inverse: 'emojiGridViewOwnerAsSearch' }),
viewBlockRef: attr({ ref: 'viewBlockRef' }),
/**
@@ -32,7 +32,7 @@ registerModel({
return value;
},
computeSearchRows() {
if (this.emojiGridViewOwner.emojiPickerViewOwner.emojiSearchBarView.currentSearch === "") {
if (this.emojiGridViewOwner.emojiPickerViewOwner.currentSearch === "") {
return clear();
}
const emojis = this.messaging.emojiRegistry.allEmojis.filter(this.emojiGridViewOwner._filterEmoji);
@@ -9,6 +9,6 @@ registerModel({
fields: {
emoji: one('Emoji', { identifying: true, inverse: 'emojiOrEmojiInCategory' }),
emojiInCategory: one('EmojiInCategory', { identifying: true, inverse: 'emojiOrEmojiInCategory' }),
emojiGridItemViews: many('EmojiGridItemView', { inverse: 'emojiOrEmojiInCategory' }),
emojiViews: many('EmojiView', { inverse: 'emojiOrEmojiInCategory' }),
},
});
@@ -1,17 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { many, one } from '@mail/model/model_field';
registerModel({
name: 'EmojiPickerHeaderActionListView',
template: 'mail.EmojiPickerHeaderActionListView',
fields: {
__dummyActionView: one('EmojiPickerHeaderActionView', { inverse: '__ownerAsDummy' }),
actionViews: many('EmojiPickerHeaderActionView', { inverse: 'owner',
sort: [['smaller-first', 'sequence']],
}),
emojiPickerView: one('EmojiPickerView', { related: 'owner.emojiPickerViewOwner' }),
owner: one('EmojiPickerHeaderView', { identifying: true, inverse: 'actionListView' }),
},
});
@@ -10,7 +10,7 @@ registerModel({
identifyingMode: 'xor',
fields: {
// dummy identifying field, so that it works without defining one initially in mail
__ownerAsDummy: one('EmojiPickerHeaderActionListView', { identifying: true, inverse: '__dummyActionView' }),
__ownerAsDummy: one('EmojiPickerView', { identifying: true, inverse: '__dummyActionView', }),
content: one('Record', { required: true,
compute() {
return clear();
@@ -21,7 +21,7 @@ registerModel({
return clear();
},
}),
owner: one('EmojiPickerHeaderActionListView', { inverse: 'actionViews', required: true,
owner: one('EmojiPickerView', { inverse: 'actionViews', required: true,
compute() {
return clear();
},
@@ -1,14 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
registerModel({
name: 'EmojiPickerHeaderView',
template: 'mail.EmojiPickerHeaderView',
fields: {
actionListView: one('EmojiPickerHeaderActionListView', { default: {}, isCausal: true, inverse: 'owner' }),
emojiCategoryBarView: one('EmojiCategoryBarView', { default: {}, inverse: 'emojiPickerHeaderViewOwner', readonly: true, required: true }),
emojiPickerViewOwner: one('EmojiPickerView', { identifying: true, inverse: 'headerView' }),
},
});
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
import { useUpdateToModel } from '@mail/component_hooks/use_update_to_model';
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
@@ -10,6 +11,7 @@ registerModel({
template: 'mail.EmojiPickerView',
componentSetup() {
useComponentToModel({ fieldName: 'component' });
useUpdateToModel({ methodName: 'onComponentUpdate', modelName: 'EmojiPickerView' });
},
lifecycleHooks: {
_created() {
@@ -17,13 +19,69 @@ registerModel({
return;
}
this.messaging.emojiRegistry.loadEmojiData();
if (!this.messaging.device.isSmall) {
this.update({ isDoFocus: true });
}
},
},
recordMethods: {
/**
* Handles OWL update on the search bar.
*/
onComponentUpdate() {
this._handleFocus();
},
onFocusinInput() {
if (!this.exists()) {
return;
}
this.update({ isFocused: true });
},
onFocusoutInput() {
if (!this.exists()) {
return;
}
this.update({ isFocused: false });
},
/**
* @public
*/
onInput() {
if (!this.exists()) {
return;
}
this.update({ currentSearch: this.inputRef.el.value });
},
/**
* @public
*/
reset() {
this.update({ currentSearch: "" });
this.inputRef.el.value = "";
},
/**
* @private
*/
_handleFocus() {
if (this.isDoFocus) {
if (!this.inputRef.el) {
return;
}
this.update({ isDoFocus: false });
this.inputRef.el.focus();
}
},
},
fields: {
__dummyActionView: one('EmojiPickerHeaderActionView', { inverse: '__ownerAsDummy' }),
actionViews: many('EmojiPickerHeaderActionView', {
inverse: 'owner',
sort: [['smaller-first', 'sequence']],
}),
activeCategoryByGridViewScroll: one('EmojiPickerView.Category'),
activeCategory: one('EmojiPickerView.Category', {
compute() {
if (this.emojiSearchBarView.currentSearch !== "") {
if (this.currentSearch !== "") {
return clear();
}
if (this.activeCategoryByGridViewScroll) {
@@ -41,6 +99,8 @@ registerModel({
return this.messaging.emojiRegistry.allCategories.map(category => ({ category }));
},
}),
component: attr(),
currentSearch: attr({ default: "" }),
defaultActiveCategory: one('EmojiPickerView.Category', {
compute() {
if (this.categories.length === 0) {
@@ -49,10 +109,20 @@ registerModel({
return this.categories[0];
},
}),
emojiCategoryViews: many('EmojiCategoryView', { inverse: 'emojiPickerViewOwner',
compute() {
return this.categories.map(category => ({ viewCategory: category }));
},
}),
emojiGridView: one('EmojiGridView', { default: {}, inverse: 'emojiPickerViewOwner', readonly: true, required: true }),
emojiSearchBarView: one('EmojiSearchBarView', { default: {}, inverse: 'emojiPickerView', readonly: true }),
headerView: one('EmojiPickerHeaderView', { default: {}, inverse: 'emojiPickerViewOwner', readonly: true, required: true }),
inputRef: attr({ ref: 'input' }),
isDoFocus: attr({ default: false }),
isFocused: attr({ default: false }),
placeholder: attr({ required: true,
compute() {
return this.env._t("Search an emoji");
},
}),
popoverViewOwner: one('PopoverView', { identifying: true, inverse: 'emojiPickerView' }),
component: attr(),
},
});
@@ -1,82 +0,0 @@
/** @odoo-module **/
import { useUpdateToModel } from '@mail/component_hooks/use_update_to_model';
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'EmojiSearchBarView',
template: 'mail.EmojiSearchBarView',
componentSetup() {
useUpdateToModel({ methodName: 'onComponentUpdate', modelName: 'EmojiSearchBarView' });
},
lifecycleHooks: {
_created() {
if (!this.messaging.device.isSmall) {
this.update({ isDoFocus: true });
}
}
},
recordMethods: {
/**
* Handles OWL update on this EmojiSearchBarView component.
*/
onComponentUpdate() {
this._handleFocus();
},
onFocusinInput() {
if (!this.exists()) {
return;
}
this.update({ isFocused: true });
},
onFocusoutInput() {
if (!this.exists()) {
return;
}
this.update({ isFocused: false });
},
/**
* @public
*/
onInput() {
if (!this.exists()) {
return;
}
this.update({
currentSearch: this.inputRef.el.value,
});
},
/**
* @public
*/
reset() {
this.update({ currentSearch: "" });
this.inputRef.el.value = "";
},
/**
* @private
*/
_handleFocus() {
if (this.isDoFocus) {
if (!this.inputRef.el) {
return;
}
this.update({ isDoFocus: false });
this.inputRef.el.focus();
}
},
},
fields: {
currentSearch: attr({ default: "" }),
emojiPickerView: one("EmojiPickerView", { identifying: true, inverse: "emojiSearchBarView" }),
inputRef: attr({ ref: 'input' }),
isDoFocus: attr({ default: false }),
isFocused: attr({ default: false }),
placeholder: attr({ required: true,
compute() {
return this.env._t("Search an emoji");
},
}),
},
});
+26 -7
View File
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
@@ -12,7 +12,7 @@ registerModel({
* @param {MouseEvent} ev
*/
onClick(ev) {
if (!this.emojiGridItemViewOwner.emojiGridRowViewOwner) {
if (!this.emojiGridRowViewOwner) {
return;
}
if (this.emojiPickerViewOwner.popoverViewOwner.messageActionViewOwnerAsReaction) {
@@ -31,7 +31,7 @@ registerModel({
if (!this.exists()) {
return;
}
this.update({ emojiGridViewAsHovered: this.emojiGridItemViewOwner.emojiGridRowViewOwner.emojiGridViewOwner });
this.update({ emojiGridViewAsHovered: this.emojiGridRowViewOwner.emojiGridViewOwner });
},
/**
* @param {MouseEvent} ev
@@ -44,13 +44,32 @@ registerModel({
},
},
fields: {
emoji: one('Emoji', { identifying: true, inverse: 'emojiViews' }),
emojiGridItemViewOwner: one('EmojiGridItemView', { identifying: true, inverse: 'emojiView' }),
emoji: one('Emoji', { inverse: 'emojiViews',
compute() {
if (this.emojiOrEmojiInCategory.emoji) {
return this.emojiOrEmojiInCategory.emoji;
}
if (this.emojiOrEmojiInCategory.emojiInCategory) {
return this.emojiOrEmojiInCategory.emojiInCategory.emoji;
}
return clear();
},
}),
emojiGridViewAsHovered: one('EmojiGridView', { inverse: 'hoveredEmojiView' }),
emojiOrEmojiInCategory: one('EmojiOrEmojiInCategory', { identifying: true, inverse: 'emojiViews' }),
emojiGridRowViewOwner: one('EmojiGridRowView', { identifying: true, inverse: 'items' }),
emojiPickerViewOwner: one('EmojiPickerView', {
compute() {
return this.emojiGridItemViewOwner.emojiGridRowViewOwner.emojiGridViewOwner.emojiPickerViewOwner;
return this.emojiGridRowViewOwner.emojiGridViewOwner.emojiPickerViewOwner;
}
})
}),
width: attr({ default: 0,
compute() {
if (!this.emojiGridRowViewOwner.emojiGridViewOwner) {
return clear();
}
return this.emojiGridRowViewOwner.emojiGridViewOwner.itemWidth;
},
}),
},
});
@@ -43,8 +43,8 @@ registerModel({
if (this.fileInput && this.fileInput.el) {
this.fileInput.el.value = '';
}
if (this.chatterOwner && !this.chatterOwner.attachmentBoxView) {
this.chatterOwner.openAttachmentBoxView();
if (this.chatterOwner && !this.chatterOwner.hasAttachmentBox) {
this.chatterOwner.openAttachmentBox();
}
this.messaging.messagingBus.trigger('o-file-uploader-upload', { files });
},
@@ -100,8 +100,7 @@ registerModel({
const thread = this.thread; // save before async
const chatter = (
(this.chatterOwner) ||
(this.attachmentBoxView && this.attachmentBoxView.chatter) ||
(this.activityView && this.activityView.activityBoxView.chatter)
(this.activityView && this.activityView.chatterOwner)
); // save before async
const activity = (
this.activityView && this.activityView.activity ||
@@ -167,7 +166,6 @@ registerModel({
fields: {
activityListViewItemOwner: one('ActivityListViewItem', { identifying: true, inverse: 'fileUploader' }),
activityView: one('ActivityView', { identifying: true, inverse: 'fileUploader' }),
attachmentBoxView: one('AttachmentBoxView', { identifying: true, inverse: 'fileUploader' }),
chatterOwner: one('Chatter', { identifying: true, inverse: 'fileUploader' }),
composerView: one('ComposerView', { identifying: true, inverse: 'fileUploader' }),
fileInput: attr({
@@ -192,9 +190,6 @@ registerModel({
if (this.activityListViewItemOwner) {
return this.activityListViewItemOwner.activity.thread;
}
if (this.attachmentBoxView) {
return this.attachmentBoxView.chatter.thread;
}
if (this.chatterOwner) {
return this.chatterOwner.thread;
}
@@ -1,77 +0,0 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'FollowButtonView',
template: 'mail.FollowButtonView',
recordMethods: {
/**
* @param {MouseEvent} ev
*/
onClickFollow(ev) {
if (!this.exists()) {
return;
}
if (!this.chatterOwner || !this.chatterOwner.thread) {
return;
}
this.chatterOwner.thread.follow();
},
/**
* @param {MouseEvent} ev
*/
onClickUnfollow(ev) {
if (!this.exists()) {
return;
}
if (!this.chatterOwner || !this.chatterOwner.thread) {
return;
}
this.chatterOwner.thread.unfollow();
this.chatterOwner.reloadParentView({ fieldNames: ['message_follower_ids'] });
},
/**
* @param {MouseEvent} ev
*/
onMouseEnterUnfollow(ev) {
if (!this.exists()) {
return;
}
this.update({ isUnfollowButtonHighlighted: true });
},
/**
* @param {MouseEvent} ev
*/
onMouseleaveUnfollow(ev) {
if (!this.exists()) {
return;
}
this.update({ isUnfollowButtonHighlighted: false });
},
},
fields: {
chatterOwner: one('Chatter', { identifying: true, inverse: 'followButtonView' }),
followingText: attr({
compute() {
return this.env._t("Following");
},
}),
isDisabled: attr({
compute() {
if (!this.chatterOwner) {
return clear();
}
return !this.chatterOwner.hasReadAccess;
},
}),
isUnfollowButtonHighlighted: attr({ default: false }),
unfollowingText: attr({
compute() {
return this.env._t("Unfollow");
},
}),
},
});
@@ -750,7 +750,7 @@ QUnit.test('activity upload document is available', async function (assert) {
"should have activity upload button"
);
assert.ok(
messaging.models['Chatter'].all()[0].activityBoxView.activityViews[0].fileUploader,
messaging.models['Chatter'].all()[0].activityViews[0].fileUploader,
"should have a file uploader"
);
});
@@ -36,7 +36,7 @@ QUnit.test('base empty rendering', async function (assert) {
"should have a button add"
);
assert.ok(
messaging.models['Chatter'].all()[0].attachmentBoxView.fileUploader,
messaging.models['Chatter'].all()[0].fileUploader,
"should have a file uploader"
);
assert.strictEqual(
@@ -90,7 +90,7 @@ QUnit.test('base non-empty rendering', async function (assert) {
"should have a button add"
);
assert.ok(
messaging.models['Chatter'].all()[0].attachmentBoxView.fileUploader,
messaging.models['Chatter'].all()[0].fileUploader,
"should have a file uploader"
);
assert.strictEqual(
@@ -9,7 +9,7 @@ registerPatch({
* Handles click on the "grant access" button.
*/
async onGrantAccess(ev) {
const { chatter } = this.activityBoxView; // save value before deleting activity
const chatter = this.chatterOwner; // save value before deleting activity
await this.messaging.rpc({
model: 'slide.channel',
method: 'action_grant_access',
@@ -25,7 +25,7 @@ registerPatch({
* Handles click on the "refuse access" button.
*/
async onRefuseAccess(ev) {
const { chatter } = this.activityBoxView; // save value before deleting activity
const chatter = this.chatterOwner; // save value before deleting activity
await this.messaging.rpc({
model: 'slide.channel',
method: 'action_refuse_access',