diff --git a/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml b/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml index 685dd43cc0d..42a23b2e171 100644 --- a/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml +++ b/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml @@ -3,66 +3,68 @@
-

Invite people

- -
- -
-
- -
-
- Avatar - - + +

Invite people

+ +
+ +
+
+ +
+
+ Avatar + + + +
+
+ + + +
+ +
+ + +
No user found that is not already a member of this channel.
+
+ +
+ Showing results out of . Narrow your search to see more choices. +
+
+
+ +
+

Selected users:

+
+ +
-
- - - -
-
- -
No user found that is not already a member of this channel.
-
- -
- Showing results out of . Narrow your search to see more choices. -
-
-
- -
-

Selected users:

-
- - - -
+
+ +
+ + +

Invitation Link

+
+
-
- -
- - -

Invitation Link

-
- -
diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml index 926a88ac550..9a1d2ecc0c1 100644 --- a/addons/mail/static/src/components/discuss/discuss.xml +++ b/addons/mail/static/src/components/discuss/discuss.xml @@ -13,7 +13,7 @@ - + diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js index 624f20b8443..bc6e35de673 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { registerMessagingComponent } from '@mail/utils/messaging_component'; +import { useRefToModel } from '@mail/component_hooks/use_ref_to_model/use_ref_to_model'; import { useUpdate } from '@mail/component_hooks/use_update/use_update'; const { Component } = owl; @@ -14,6 +15,7 @@ export class DiscussSidebar extends Component { constructor(...args) { super(...args); useUpdate({ func: () => this._update() }); + useRefToModel({ fieldName: 'startAMeetingButtonRef', modelName: 'mail.discuss', propNameAsRecordLocalId: 'localId', refName: 'startAMeetingButton' }); /** * Reference of the quick search input. Useful to filter channels and * chats based on this input content. @@ -29,7 +31,7 @@ export class DiscussSidebar extends Component { * @returns {mail.discuss} */ get discuss() { - return this.messaging && this.messaging.discuss; + return this.messaging && this.messaging.models['mail.discuss'].get(this.props.localId); } //-------------------------------------------------------------------------- @@ -64,7 +66,9 @@ export class DiscussSidebar extends Component { } Object.assign(DiscussSidebar, { - props: {}, + props: { + localId: String + }, template: 'mail.DiscussSidebar', }); diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml index 5eeb7bdc25e..75508a54568 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml @@ -3,16 +3,11 @@
- -
- -
- - - -
+
+ +

diff --git a/addons/mail/static/src/components/popover_view/popover_view.js b/addons/mail/static/src/components/popover_view/popover_view.js new file mode 100644 index 00000000000..d099c8e8134 --- /dev/null +++ b/addons/mail/static/src/components/popover_view/popover_view.js @@ -0,0 +1,44 @@ +/** @odoo-module **/ + +import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model'; +import { registerMessagingComponent } from '@mail/utils/messaging_component'; + +import { usePosition } from '@web/core/position/position_hook'; + +const { Component } = owl; + +export class PopoverView extends Component { + + setup() { + super.setup(); + useComponentToModel({ fieldName: 'component', modelName: 'mail.popover_view', propNameAsRecordLocalId: 'popoverViewLocalId' }); + usePosition( + () => this.popoverView && this.popoverView.anchorRef && this.popoverView.anchorRef.el, + { + margin: 16, + position: this.popoverView.position, + } + ); + } + + //-------------------------------------------------------------------------- + // Getters / Setters + //-------------------------------------------------------------------------- + + /** + * @returns {mail.popover_view|undefined} + */ + get popoverView() { + return this.messaging && this.messaging.models['mail.popover_view'].get(this.props.popoverViewLocalId); + } + +} + +Object.assign(PopoverView, { + props: { + popoverViewLocalId: String, + }, + template: 'mail.PopoverView', +}); + +registerMessagingComponent(PopoverView); diff --git a/addons/mail/static/src/components/popover_view/popover_view.scss b/addons/mail/static/src/components/popover_view/popover_view.scss new file mode 100644 index 00000000000..e0f99263752 --- /dev/null +++ b/addons/mail/static/src/components/popover_view/popover_view.scss @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_PopoverView { + z-index: $zindex-modal + 1; + border: $border-width solid; +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +.o_PopoverView { + border-color: gray('300'); + background-color: $white; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); +} diff --git a/addons/mail/static/src/components/popover_view/popover_view.xml b/addons/mail/static/src/components/popover_view/popover_view.xml new file mode 100644 index 00000000000..1f6b81b57db --- /dev/null +++ b/addons/mail/static/src/components/popover_view/popover_view.xml @@ -0,0 +1,12 @@ + + + + +
+ + + +
+
+ +
diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.js b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.js index 6c1ca4fb543..fecb2c3a101 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.js +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.js @@ -14,6 +14,7 @@ export class ThreadViewTopbar extends Component { setup() { super.setup(); useRefToModel({ fieldName: 'guestNameInputRef', modelName: 'mail.thread_view_topbar', propNameAsRecordLocalId: 'localId', refName: 'guestNameInput' }); + useRefToModel({ fieldName: 'inviteButtonRef', modelName: 'mail.thread_view_topbar', propNameAsRecordLocalId: 'localId', refName: 'inviteButton' }); useRefToModel({ fieldName: 'threadNameInputRef', modelName: 'mail.thread_view_topbar', propNameAsRecordLocalId: 'localId', refName: 'threadNameInput' }); useRefToModel({ fieldName: 'threadDescriptionInputRef', modelName: 'mail.thread_view_topbar', propNameAsRecordLocalId: 'localId', refName: 'threadDescriptionInput' }); useUpdateToModel({ methodName: 'onComponentUpdate', modelName: 'mail.thread_view_topbar', propNameAsRecordLocalId: 'localId' }); diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss index cca2581b034..0fdc360a8e4 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss @@ -57,7 +57,7 @@ input.o_ThreadViewTopBar_guestNameInput { width: 150px; } -.o_ThreadViewTopbar_invitePopover .popover-body { +.o_ThreadViewTopbar_invitePopoverView .popover-body { padding: 0; // cancel unwanted padding } diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml index c46eda1125c..e089370f745 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml @@ -58,14 +58,12 @@ - - - - - - + + + + diff --git a/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js b/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js index 4bb5f87bc4c..274913df0cb 100644 --- a/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js +++ b/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js @@ -21,13 +21,6 @@ function factory(dependencies) { // Public //---------------------------------------------------------------------- - /** - * Closes this channel invitation form. - */ - close() { - this.component.trigger('o-popover-close'); - } - /** * Handles click on the "invite" button. * @@ -35,20 +28,12 @@ function factory(dependencies) { */ async onClickInvite(ev) { if (this.thread.channel_type === 'chat') { - const channelData = await this.env.services.rpc(({ - model: 'mail.channel', - method: 'create_group', - kwargs: { - partners_to: [...new Set([ - this.messaging.currentPartner.id, - ...this.thread.members.map(member => member.id), - ...this.selectedPartners.map(partner => partner.id), - ])], - }, - })); - const channel = this.messaging.models['mail.thread'].insert( - this.messaging.models['mail.thread'].convertData(channelData) - ); + const partners_to = [...new Set([ + this.messaging.currentPartner.id, + ...this.thread.members.map(member => member.id), + ...this.selectedPartners.map(partner => partner.id), + ])]; + const channel = await this.messaging.models['mail.thread'].createGroupChat({ partners_to }); if (this.thread.rtc) { /** * if we were in a RTC call on the current thread, we move to the new group chat. @@ -76,7 +61,7 @@ function factory(dependencies) { searchTerm: "", selectedPartners: unlinkAll(), }); - this.close(); + this.delete(); } /** @@ -190,20 +175,6 @@ function factory(dependencies) { return this.env._t("Invite to Channel"); } - /** - * @private - * @returns {mail.thread} - */ - _computeThread() { - if (this.threadView && this.threadView.thread) { - return link(this.threadView.thread); - } - if (this.discuss && this.discuss.mostRecentMeetingChannel) { - return link(this.discuss.mostRecentMeetingChannel); - } - return clear(); - } - } ChannelInvitationForm.fields = { @@ -213,9 +184,6 @@ function factory(dependencies) { * it is open to update the button active state. */ component: attr(), - discuss: one2one('mail.discuss', { - inverse: 'channelInvitationForm', - }), /** * Determines whether this search input needs to be focused. */ @@ -238,6 +206,10 @@ function factory(dependencies) { inviteButtonText: attr({ compute: '_computeInviteButtonText', }), + popoverView: one2one('mail.popover_view', { + inverse: 'channelInvitationForm', + isCausal: true, + }), /** * States the OWL ref of the "search" input of this channel invitation * form. Useful to be able to focus it. @@ -268,8 +240,8 @@ function factory(dependencies) { * States the thread on which this list operates (if any). */ thread: many2one('mail.thread', { - compute: '_computeThread', readonly: true, + related: 'threadView.thread', required: true, }), /** diff --git a/addons/mail/static/src/models/chat_window/chat_window.js b/addons/mail/static/src/models/chat_window/chat_window.js index 02b122b479d..2120bb08292 100644 --- a/addons/mail/static/src/models/chat_window/chat_window.js +++ b/addons/mail/static/src/models/chat_window/chat_window.js @@ -110,7 +110,9 @@ function factory(dependencies) { makeActive(options) { this.makeVisible(); this.unfold(options); - this.focus(); + if ((options && options.focus !== undefined) ? options.focus : true) { + this.focus(); + } } /** diff --git a/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js b/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js index 77e66ee1194..eb9d69e025d 100644 --- a/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js +++ b/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js @@ -98,12 +98,15 @@ function factory(dependencies) { /** * @param {mail.thread} thread * @param {Object} [param1={}] + * @param {boolean} [param1.focus] if set, set focus the chat window + * to open. * @param {boolean} [param1.isFolded=false] * @param {boolean} [param1.makeActive=false] * @param {boolean} [param1.notifyServer] * @param {boolean} [param1.replaceNewMessage=false] */ openThread(thread, { + focus, isFolded = false, makeActive = false, notifyServer, @@ -131,7 +134,7 @@ function factory(dependencies) { if (makeActive) { // avoid double notify at this step, it will already be done at // the end of the current method - chatWindow.makeActive({ notifyServer: false }); + chatWindow.makeActive({ focus, notifyServer: false }); } // Flux specific: notify server of chat window being opened. if (notifyServer && !this.messaging.currentGuest) { diff --git a/addons/mail/static/src/models/discuss/discuss.js b/addons/mail/static/src/models/discuss/discuss.js index b598d5e1487..5e88418acd8 100644 --- a/addons/mail/static/src/models/discuss/discuss.js +++ b/addons/mail/static/src/models/discuss/discuss.js @@ -36,23 +36,6 @@ function factory(dependencies) { this.update({ isOpen: false }); } - async createMeetingChannel() { - const channelData = await this.env.services.rpc({ - model: 'mail.channel', - method: 'create_group', - kwargs: { - default_display_mode: 'video_full_screen', - partners_to: [this.messaging.currentPartner.id], - }, - }); - const channel = this.messaging.models['mail.thread'].insert( - this.messaging.models['mail.thread'].convertData(channelData) - ); - this.update({ mostRecentMeetingChannel: link(channel) }); - this.channelInvitationForm.update({ doFocusOnSearchInput: true }); - this.channelInvitationForm.searchPartnersToInvite(); - } - focus() { if (this.threadView && this.threadView.composerView) { this.threadView.composerView.update({ doFocus: true }); @@ -183,8 +166,17 @@ function factory(dependencies) { * * @param {MouseEvent} ev */ - onClickStartAMeetingButton(ev) { - this.createMeetingChannel(); + async onClickStartAMeetingButton(ev) { + const meetingChannel = await this.messaging.models['mail.thread'].createGroupChat({ + default_display_mode: 'video_full_screen', + partners_to: [this.messaging.currentPartner.id], + }); + meetingChannel.toggleCall({ startWithVideo: true }); + await meetingChannel.open({ focus: false }); + if (!meetingChannel.exists() || !this.threadView) { + return; + } + this.threadView.topbar.openInvitePopoverView(); } /** @@ -214,12 +206,16 @@ function factory(dependencies) { * Opens the given thread in Discuss, and opens Discuss if necessary. * * @param {mail.thread} thread + * @param {Object} [param1={}] + * @param {Boolean} [param1.focus=true] */ - async openThread(thread) { + async openThread(thread, { focus = true } = {}) { this.update({ thread: link(thread), }); - this.focus(); + if (focus) { + this.focus(); + } if (!this.isOpen) { this.env.bus.trigger('do-action', { action: 'mail.action_discuss', @@ -278,22 +274,6 @@ function factory(dependencies) { return this.addingChannelValue; } - /** - * @private - * @returns {mail.channel_invitation_form} - */ - _computeChannelInvitationForm() { - if (!this.mostRecentMeetingChannel) { - return clear(); - } - return insertAndReplace({ - searchResultCount: clear(), - searchTerm: clear(), - selectablePartners: clear(), - selectedPartners: clear(), - }); - } - /** * @private * @returns {boolean} @@ -393,16 +373,6 @@ function factory(dependencies) { inverse: 'discussAsChat', isCausal: true, }), - /** - * Determines the channel invitation form to use for the invitation - * to the most recent meeting channel. - */ - channelInvitationForm: one2one('mail.channel_invitation_form', { - compute: '_computeChannelInvitationForm', - inverse: 'discuss', - isCausal: true, - readonly: true, - }), /** * Determines whether `this.thread` should be displayed. */ @@ -450,11 +420,6 @@ function factory(dependencies) { menu_id: attr({ default: null, }), - /** - * The channel created last time the user clicked on the "Start a - * meeting" button. - */ - mostRecentMeetingChannel: one2one('mail.thread'), /** * Quick search input value in the discuss sidebar (desktop). Useful * to filter channels and chats based on this input content. @@ -462,6 +427,11 @@ function factory(dependencies) { sidebarQuickSearchValue: attr({ default: "", }), + /** + * States the OWL ref of the start a meeting button in sidebar. + * Useful to provide anchor for the invite popover positioning. + */ + startAMeetingButtonRef: attr(), /** * Determines the `mail.thread` that should be displayed by `this`. */ diff --git a/addons/mail/static/src/models/popover_view/popover_view.js b/addons/mail/static/src/models/popover_view/popover_view.js new file mode 100644 index 00000000000..f0efe847bc8 --- /dev/null +++ b/addons/mail/static/src/models/popover_view/popover_view.js @@ -0,0 +1,115 @@ +/** @odoo-module **/ + +import { registerNewModel } from '@mail/model/model_core'; +import { attr, one2one } from '@mail/model/model_field'; +import { clear } from '@mail/model/model_field_command'; + +function factory(dependencies) { + + class PopoverView extends dependencies['mail.model'] { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + _created() { + this._onClickCaptureGlobal = this._onClickCaptureGlobal.bind(this); + document.addEventListener('click', this._onClickCaptureGlobal, true); + } + + _willDelete() { + document.removeEventListener('click', this._onClickCaptureGlobal, true); + } + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * @private + * @returns {owl.Ref} + */ + _computeAnchorRef() { + if (this.threadViewTopbarOwner) { + return this.threadViewTopbarOwner.inviteButtonRef; + } + return clear(); + } + + /** + * @private + * @returns {string} + */ + _computePosition() { + if (this.threadViewTopbarOwner) { + return 'bottom'; + } + return clear(); + } + + /** + * Closes the popover when clicking outside, if appropriate. + * + * @private + * @param {MouseEvent} ev + */ + _onClickCaptureGlobal(ev) { + if (!this.component || !this.component.el) { + return; + } + if (this.anchorRef && this.anchorRef.el && this.anchorRef.el.contains(ev.target)) { + return; + } + if (this.component.el.contains(ev.target)) { + return; + } + this.delete(); + } + + } + + PopoverView.fields = { + /** + * HTML element that is used as anchor position for this popover view. + */ + anchorRef: attr({ + compute: '_computeAnchorRef', + required: true, + }), + /** + * The record that represents the content inside the popover view. + */ + channelInvitationForm: one2one('mail.channel_invitation_form', { + inverse: 'popoverView', + isCausal: true, + readonly: true, + required: true, + }), + /** + * States the OWL component of this popover view. + */ + component: attr(), + /** + * Position of the popover view relative to its anchor point. + * Valid values: 'top', 'right', 'bottom', 'left' + */ + position: attr({ + compute: '_computePosition', + default: 'top', + }), + /** + * If set, this popover view is owned by a thread view topbar record. + */ + threadViewTopbarOwner: one2one('mail.thread_view_topbar', { + inverse: 'invitePopoverView', + readonly: true, + }), + }; + + PopoverView.identifyingFields = ['threadViewTopbarOwner', 'channelInvitationForm']; + PopoverView.modelName = 'mail.popover_view'; + + return PopoverView; +} + +registerNewModel('mail.popover_view', factory); diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index 7e339c6ce27..56aeb82833e 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -316,6 +316,29 @@ function factory(dependencies) { return data2; } + /** + * Creates a new group chat with the provided partners. + * + * @param {Object} param0 + * @param {number[]} param0.partners_to Ids of the partners to add as channel + * members. + * @param {boolean|string} param0.default_display_mode + * @returns {mail.thread} The newly created group chat. + */ + static async createGroupChat({ default_display_mode, partners_to }) { + const channelData = await this.env.services.rpc({ + model: 'mail.channel', + method: 'create_group', + kwargs: { + default_display_mode, + partners_to, + }, + }); + return this.messaging.models['mail.thread'].insert( + this.messaging.models['mail.thread'].convertData(channelData) + ); + } + /** * Client-side ending of the call. */ @@ -971,8 +994,9 @@ function factory(dependencies) { * * @param {Object} [param0] * @param {boolean} [param0.expanded=false] + * @param {boolean} [param0.focus=true] */ - async open({ expanded = false } = {}) { + async open({ expanded = false, focus = true } = {}) { const discuss = this.messaging.discuss; // check if thread must be opened in form view if (!['mail.box', 'mail.channel'].includes(this.model)) { @@ -981,10 +1005,11 @@ function factory(dependencies) { // both in chat window and as main document does not look // good. this.messaging.chatWindowManager.closeThread(this); - return this.messaging.openDocument({ + await this.messaging.openDocument({ id: this.id, model: this.model, }); + return; } } // check if thread must be opened in discuss @@ -993,7 +1018,7 @@ function factory(dependencies) { (!device.isMobile && (discuss.isOpen || expanded)) || this.model === 'mail.box' ) { - return discuss.openThread(this); + return discuss.openThread(this, { focus }); } // thread must be opened in chat window return this.messaging.chatWindowManager.openThread(this, { diff --git a/addons/mail/static/src/models/thread_view/thread_view.js b/addons/mail/static/src/models/thread_view/thread_view.js index 47006081112..2b9e284b236 100644 --- a/addons/mail/static/src/models/thread_view/thread_view.js +++ b/addons/mail/static/src/models/thread_view/thread_view.js @@ -97,18 +97,6 @@ function factory(dependencies) { /** * @private - * @returns {mail.channel_invitation_form} - */ - _computeChannelInvitationForm() { - return (this.thread && this.thread.hasInviteFeature) - ? insertAndReplace({ - searchResultCount: clear(), - searchTerm: clear(), - selectablePartners: clear(), - selectedPartners: clear(), - }) - : clear(); - } /** * @private @@ -361,10 +349,8 @@ function factory(dependencies) { * Only applies if this thread is a channel. */ channelInvitationForm: one2one('mail.channel_invitation_form', { - compute: '_computeChannelInvitationForm', inverse: 'threadView', isCausal: true, - readonly: true, }), /** * List of component hints. Hints contain information that help diff --git a/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js b/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js index 1fbbd8560bb..b9b431b9167 100644 --- a/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js +++ b/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js @@ -2,7 +2,7 @@ import { registerNewModel } from '@mail/model/model_core'; import { attr, many2one, one2one } from '@mail/model/model_field'; -import { clear } from '@mail/model/model_field_command'; +import { clear, insertAndReplace, replace } from '@mail/model/model_field_command'; function factory(dependencies) { @@ -75,12 +75,10 @@ function factory(dependencies) { * @param {MouseEvent} ev */ onClickInviteButton(ev) { - if (this.threadView.channelInvitationForm.component) { - return; - } - if (!this.messaging.isCurrentUserGuest) { - this.threadView.channelInvitationForm.update({ doFocusOnSearchInput: true }); - this.threadView.channelInvitationForm.searchPartnersToInvite(); + if (this.invitePopoverView) { + this.update({ invitePopoverView: clear() }); + } else { + this.openInvitePopoverView(); } } @@ -374,6 +372,23 @@ function factory(dependencies) { this.update({ isMouseOverUserInfo: false }); } + /** + * Open the invite popover view in this thread view topbar. + */ + openInvitePopoverView() { + this.threadView.update({ channelInvitationForm: insertAndReplace() }); + this.update({ + invitePopoverView: insertAndReplace({ + channelInvitationForm: replace(this.threadView.channelInvitationForm), + }), + }); + if (this.messaging.isCurrentUserGuest) { + return; + } + this.threadView.channelInvitationForm.update({ doFocusOnSearchInput: true }); + this.threadView.channelInvitationForm.searchPartnersToInvite(); + } + //---------------------------------------------------------------------- // Private //---------------------------------------------------------------------- @@ -594,6 +609,19 @@ function factory(dependencies) { isEditingGuestName: attr({ default: false, }), + /** + * States the OWL ref of the invite button. + * Useful to provide anchor for the invite popover positioning. + */ + inviteButtonRef: attr(), + /** + * If set, this is the record of invite button popover that is currently + * open in the topbar. + */ + invitePopoverView: one2one('mail.popover_view', { + isCausal: true, + inverse: 'threadViewTopbarOwner', + }), /** * Determines whether this thread is currently being renamed. */ diff --git a/addons/web/static/src/core/position/position_hook.js b/addons/web/static/src/core/position/position_hook.js index 26b05956163..e2b82a42f30 100644 --- a/addons/web/static/src/core/position/position_hook.js +++ b/addons/web/static/src/core/position/position_hook.js @@ -229,8 +229,9 @@ export function usePosition(reference, options) { const popperRef = popper ? useRef(popper) : useComponent(); const getReference = reference instanceof HTMLElement ? () => reference : reference; const update = () => { - if (popperRef.el) { - reposition(getReference(), popperRef.el, options); + const ref = getReference(); + if (popperRef.el && ref) { + reposition(ref, popperRef.el, options); } }; useEffect(update);