diff options
author | Joas Schilling <213943+nickvergessen@users.noreply.github.com> | 2021-08-27 14:33:32 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-27 14:33:32 +0300 |
commit | b58af3f6ad3985b54e4475364ff3f5ce5228e796 (patch) | |
tree | 42407bb30f89fe4c828f11b40edc9148d9fd9b6b /src | |
parent | 44ad976b56198a3456a01fe261639ec6bb10cc7d (diff) | |
parent | 3da5277deefdaa29aa1032c7d8b9a9b6c5ee139a (diff) |
Merge pull request #6119 from nextcloud/fix-container-of-modal-and-popup-components-when-talk-is-embedded
Fix container of modal and popup components when Talk is embedded
Diffstat (limited to 'src')
22 files changed, 187 insertions, 22 deletions
diff --git a/src/collections.js b/src/collections.js index a07561a48..1d9561fa4 100644 --- a/src/collections.js +++ b/src/collections.js @@ -43,7 +43,14 @@ import Vue from 'vue' body.appendChild(container) const RoomSelector = () => import('./views/RoomSelector') const ComponentVM = new Vue({ - render: h => h(RoomSelector), + render: h => h(RoomSelector, { + props: { + // Even if it is used from Talk the Collections menu is + // independently loaded, so the properties that depend + // on the store need to be explicitly injected. + container: window.store ? window.store.getters.getMainContainerSelector() : undefined, + }, + }), }) ComponentVM.$mount(container) ComponentVM.$root.$on('close', () => { diff --git a/src/components/AvatarWrapper/AvatarWrapper.spec.js b/src/components/AvatarWrapper/AvatarWrapper.spec.js index c072795ff..d428db753 100644 --- a/src/components/AvatarWrapper/AvatarWrapper.spec.js +++ b/src/components/AvatarWrapper/AvatarWrapper.spec.js @@ -1,9 +1,21 @@ +import Vuex from 'vuex' import { shallowMount } from '@vue/test-utils' +import { cloneDeep } from 'lodash' +import storeConfig from '../../store/storeConfig' import AvatarWrapper from './AvatarWrapper' describe('AvatarWrapper.vue', () => { + let testStoreConfig + let store + + beforeEach(() => { + testStoreConfig = cloneDeep(storeConfig) + store = new Vuex.Store(testStoreConfig) + }) + it('Renders user avatars properly', () => { const wrapper = shallowMount(AvatarWrapper, { + store, propsData: { id: 'test-id', source: 'users', @@ -17,6 +29,7 @@ describe('AvatarWrapper.vue', () => { }) it('Renders group icons properly', () => { const wrapper = shallowMount(AvatarWrapper, { + store, propsData: { id: '', source: 'groups', @@ -29,6 +42,7 @@ describe('AvatarWrapper.vue', () => { }) it('Renders email icons properly', () => { const wrapper = shallowMount(AvatarWrapper, { + store, propsData: { id: '', source: 'emails', @@ -43,6 +57,7 @@ describe('AvatarWrapper.vue', () => { }) it('Renders guests icons properly', () => { const wrapper = shallowMount(AvatarWrapper, { + store, propsData: { id: 'random-sha1', source: 'guests', diff --git a/src/components/AvatarWrapper/AvatarWrapper.vue b/src/components/AvatarWrapper/AvatarWrapper.vue index a44853767..0bfa6f34b 100644 --- a/src/components/AvatarWrapper/AvatarWrapper.vue +++ b/src/components/AvatarWrapper/AvatarWrapper.vue @@ -29,7 +29,7 @@ <Avatar v-else-if="!isGuest" :user="id" :display-name="name" - menu-container="#content-vue" + :menu-container="menuContainer" menu-position="left" :disable-tooltip="disableTooltip" :disable-menu="disableMenu" @@ -118,6 +118,9 @@ export default { const customName = this.name !== t('spreed', 'Guest') ? this.name : '?' return customName.charAt(0) }, + menuContainer() { + return this.$store.getters.getMainContainerSelector() + }, // Takes the the size prop and makes it a string for the classes sizeToString() { return this.size.toString() diff --git a/src/components/AvatarWrapper/AvatarWrapperSmall.vue b/src/components/AvatarWrapper/AvatarWrapperSmall.vue index 52bb8029f..8db74fdf1 100644 --- a/src/components/AvatarWrapper/AvatarWrapperSmall.vue +++ b/src/components/AvatarWrapper/AvatarWrapperSmall.vue @@ -29,7 +29,7 @@ <Avatar v-else-if="!isGuest" :user="id" :display-name="name" - menu-container="#content-vue" + :menu-container="menuContainer" menu-position="left" :disable-tooltip="disableTooltip" :disable-menu="disableMenu" @@ -107,6 +107,9 @@ export default { const customName = this.name !== t('spreed', 'Guest') ? this.name : '?' return customName.charAt(0) }, + menuContainer() { + return this.$store.getters.getMainContainerSelector() + }, // Takes the the size prop and makes it a string for the classes sizeToString() { return this.size.toString() diff --git a/src/components/CallView/shared/LocalMediaControls.vue b/src/components/CallView/shared/LocalMediaControls.vue index 0233d8775..c73f5105d 100644 --- a/src/components/CallView/shared/LocalMediaControls.vue +++ b/src/components/CallView/shared/LocalMediaControls.vue @@ -76,7 +76,7 @@ :class="screenSharingButtonClass" class="app-navigation-entry-utils-menu-button" :boundaries-element="boundaryElement" - container="#content-vue" + :container="container" :open="screenSharingMenuOpen" @update:open="screenSharingMenuOpen = true" @update:close="screenSharingMenuOpen = false"> @@ -147,7 +147,7 @@ <Actions v-if="showActions" v-tooltip="t('spreed', 'More actions')" - container="#content-vue" + :container="container" :aria-label="t('spreed', 'More actions')"> <ActionButton :close-after-click="true" @@ -451,6 +451,10 @@ export default { return this.model.attributes.localScreen ? t('spreed', 'Screensharing options') : t('spreed', 'Enable screensharing') }, + container() { + return this.$store.getters.getMainContainerSelector() + }, + isQualityWarningTooltipDismissed() { return this.$store.getters.isQualityWarningTooltipDismissed }, diff --git a/src/components/ConversationIcon.vue b/src/components/ConversationIcon.vue index 524b6aabd..d61687565 100644 --- a/src/components/ConversationIcon.vue +++ b/src/components/ConversationIcon.vue @@ -30,7 +30,7 @@ :disable-menu="disableMenu" :display-name="item.displayName" :preloaded-user-status="preloadedUserStatus" - menu-container="#content-vue" + :menu-container="menuContainer" menu-position="left" class="conversation-icon__avatar" /> <div v-if="showCall" @@ -118,6 +118,15 @@ export default { } return undefined }, + menuContainer() { + // The store may not be defined in the RoomSelector if used from + // the Collaboration menu outside Talk. + if (!this.$store) { + return undefined + } + + return this.$store.getters.getMainContainerSelector() + }, }, } </script> diff --git a/src/components/ConversationSettings/ConversationSettingsDialog.vue b/src/components/ConversationSettings/ConversationSettingsDialog.vue index c9829ae7c..e27d2e91d 100644 --- a/src/components/ConversationSettings/ConversationSettingsDialog.vue +++ b/src/components/ConversationSettings/ConversationSettingsDialog.vue @@ -25,7 +25,7 @@ :aria-label="t('spreed', 'Conversation settings')" :open.sync="showSettings" :show-navigation="true" - container="#content-vue"> + :container="container"> <!-- description --> <AppSettingsSection v-if="showDescription" @@ -134,6 +134,10 @@ export default { }, computed: { + container() { + return this.$store.getters.getMainContainerSelector() + }, + canUserEnableSIP() { return this.conversation.canEnableSIP }, diff --git a/src/components/ConversationSettings/Matterbridge/MatterbridgeSettings.vue b/src/components/ConversationSettings/Matterbridge/MatterbridgeSettings.vue index 5c6d5fa94..c87e10c32 100644 --- a/src/components/ConversationSettings/Matterbridge/MatterbridgeSettings.vue +++ b/src/components/ConversationSettings/Matterbridge/MatterbridgeSettings.vue @@ -72,7 +72,7 @@ class="icon icon-edit" @click="showLogContent" /> <Modal v-if="logModal" - container="#content-vue" + :container="container" @close="closeLogModal"> <div class="modal__content"> <textarea v-model="processLog" class="log-content" /> @@ -530,6 +530,9 @@ export default { ? t('spreed', 'not running, check Matterbridge log') : t('spreed', 'not running') }, + container() { + return this.$store.getters.getMainContainerSelector() + }, }, beforeMount() { diff --git a/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue b/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue index dea7bd78d..5bcdc688a 100644 --- a/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue +++ b/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue @@ -35,7 +35,7 @@ <!-- New group form --> <Modal v-if="modal" - container="#content-vue" + :container="container" @close="closeModal"> <!-- Wrapper for content & navigation --> <div @@ -185,6 +185,9 @@ export default { }, computed: { + container() { + return this.$store.getters.getMainContainerSelector() + }, // Trims whitespaces from the input string conversationName() { return this.conversationNameInput.trim() diff --git a/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue b/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue index 1dde6510c..e1d6425c0 100644 --- a/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue +++ b/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue @@ -26,7 +26,7 @@ :user="authorId" :show-user-status="false" :disable-menu="disableMenu" - menu-container="#content-vue" + :menu-container="menuContainer" menu-position="left" :display-name="displayName" /> <div v-else-if="isDeletedUser" @@ -88,6 +88,10 @@ export default { return customName.charAt(0) }, + menuContainer() { + return this.$store.getters.getMainContainerSelector() + }, + disableMenu() { // disable the menu if accessing the conversation as guest // or the message sender is a bridged user diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 09820d64c..5e00ecd51 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -132,7 +132,8 @@ the main body of the message as well as a quote. </Actions> <Actions :force-menu="true" - container="#content-vue"> + :container="container" + :boundaries-element="containerElement"> <ActionButton v-if="isPrivateReplyable" icon="icon-user" @@ -573,6 +574,14 @@ export default { return !this.isSystemMessage && !this.isTemporary }, + container() { + return this.$store.getters.getMainContainerSelector() + }, + + containerElement() { + return document.querySelector(this.container) + }, + isTemporaryUpload() { return this.isTemporary && this.messageParameters.file }, diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Forwarder.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Forwarder.vue index e62a1539c..52c7c46fe 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Forwarder.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Forwarder.vue @@ -25,7 +25,7 @@ message to --> <RoomSelector v-if="!showForwardedConfirmation" - container="#content-vue" + :container="container" :show-postable-only="true" :dialog-title="dialogTitle" :dialog-subtitle="dialogSubtitle" @@ -89,6 +89,10 @@ export default { }, computed: { + container() { + return this.$store.getters.getMainContainerSelector() + }, + dialogTitle() { return t('spreed', 'Forward message') }, diff --git a/src/components/NewMessageForm/NewMessageForm.vue b/src/components/NewMessageForm/NewMessageForm.vue index 2a1933d93..5e89d01e4 100644 --- a/src/components/NewMessageForm/NewMessageForm.vue +++ b/src/components/NewMessageForm/NewMessageForm.vue @@ -42,7 +42,8 @@ class="new-message-form__upload-menu"> <Actions ref="uploadMenu" - container="#content-vue" + :container="container" + :boundaries-element="containerElement" :disabled="disabled" :aria-label="t('spreed', 'Share files to the conversation')" :aria-haspopup="true"> @@ -70,7 +71,7 @@ <div class="new-message-form__input"> <div class="new-message-form__emoji-picker"> <EmojiPicker - container="#content-vue" + :container="container" @select="addEmoji"> <button type="button" @@ -245,6 +246,14 @@ export default { hasText() { return this.parsedText !== '' }, + + container() { + return this.$store.getters.getMainContainerSelector() + }, + + containerElement() { + return document.querySelector(this.container) + }, }, watch: { diff --git a/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue b/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue index a11d0a9b7..7ec84f126 100644 --- a/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue +++ b/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue @@ -44,7 +44,7 @@ :name="computedName" :source="participant.source || participant.actorType" :offline="isOffline" - menu-container="#content-vue" /> + :menu-container="container" /> <div class="participant-row__user-wrapper" :class="{ @@ -97,7 +97,7 @@ </div> <Actions v-if="canBeModerated && !isSearched" - container="#content-vue" + :container="container" :aria-label="participantSettingsAriaLabel" :force-menu="true" class="participant-row__actions"> @@ -213,6 +213,10 @@ export default { }, computed: { + container() { + return this.$store.getters.getMainContainerSelector() + }, + participantSettingsAriaLabel() { return t('spreed', 'Settings for participant "{user}"', { user: this.computedName }) }, diff --git a/src/components/SettingsDialog/SettingsDialog.vue b/src/components/SettingsDialog/SettingsDialog.vue index 38ef02535..d4577062a 100644 --- a/src/components/SettingsDialog/SettingsDialog.vue +++ b/src/components/SettingsDialog/SettingsDialog.vue @@ -24,7 +24,7 @@ :open.sync="showSettings" :show-navigation="true" first-selected-section="keyboard shortcuts" - container="#content-vue"> + :container="container"> <AppSettingsSection :title="t('spreed', 'Choose devices')" class="app-settings-section"> <MediaDevicesPreview /> @@ -154,6 +154,10 @@ export default { }, computed: { + container() { + return this.$store.getters.getMainContainerSelector() + }, + playSounds() { return this.$store.getters.playSounds }, diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index f6253b884..6523f250a 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -53,7 +53,7 @@ <Actions slot="trigger" class="forced-background" - container="#content-vue"> + :container="container"> <ActionButton v-if="isInCall" :icon="changeViewIconClass" @click="changeView"> @@ -66,7 +66,7 @@ class="top-bar__button forced-background" menu-align="right" :aria-label="t('spreed', 'Conversation actions')" - container="#content-vue" + :container="container" @shortkey.native="toggleFullscreen"> <ActionButton :icon="iconFullscreen" @@ -125,7 +125,7 @@ <Actions v-if="showOpenSidebarButton" class="top-bar__button forced-background" close-after-click="true" - container="#content-vue"> + :container="container"> <ActionButton v-if="isInCall" key="openSideBarButtonMessageText" @@ -207,6 +207,10 @@ export default { }, computed: { + container() { + return this.$store.getters.getMainContainerSelector() + }, + isFullscreen() { return this.$store.getters.isFullscreen() }, diff --git a/src/components/UploadEditor.vue b/src/components/UploadEditor.vue index acb58e7e0..5c9adc0c9 100644 --- a/src/components/UploadEditor.vue +++ b/src/components/UploadEditor.vue @@ -22,7 +22,7 @@ <template> <Modal v-if="showModal" class="upload-editor" - container="#content-vue" + :container="container" @close="handleDismiss"> <template v-if="!isVoiceMessage"> <!--native file picker, hidden --> @@ -111,6 +111,10 @@ export default { return !!this.currentUploadId }, + container() { + return this.$store.getters.getMainContainerSelector() + }, + addMoreAriaLabel() { return t('spreed', 'Add more files') }, diff --git a/src/main.js b/src/main.js index 46d51a9e3..70da0be04 100644 --- a/src/main.js +++ b/src/main.js @@ -82,6 +82,7 @@ Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) Tooltip.options.defaultContainer = '#content-vue' +store.dispatch('setMainContainerSelector', '#content-vue') const instance = new Vue({ el: '#content', diff --git a/src/mainFilesSidebar.js b/src/mainFilesSidebar.js index 1551ff23f..21c9b7966 100644 --- a/src/mainFilesSidebar.js +++ b/src/mainFilesSidebar.js @@ -73,6 +73,8 @@ Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) Vue.use(VueObserveVisibility) +store.dispatch('setMainContainerSelector', '.talkChatTab') + const newCallView = () => new Vue({ store, render: h => h(FilesSidebarCallViewApp), diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js index 49967ba71..16b8b6db4 100644 --- a/src/store/storeConfig.js +++ b/src/store/storeConfig.js @@ -35,6 +35,7 @@ import sidebarStore from './sidebarStore' import soundsStore from './soundsStore' import talkHashStore from './talkHashStore' import tokenStore from './tokenStore' +import uiModeStore from './uiModeStore' import windowVisibilityStore from './windowVisibilityStore' import messageActionsStore from './messageActionsStore' @@ -55,6 +56,7 @@ export default { soundsStore, talkHashStore, tokenStore, + uiModeStore, windowVisibilityStore, messageActionsStore, }, diff --git a/src/store/uiModeStore.js b/src/store/uiModeStore.js new file mode 100644 index 000000000..f780fbd61 --- /dev/null +++ b/src/store/uiModeStore.js @@ -0,0 +1,60 @@ +/** + * @copyright Copyright (c) 2021 Daniel Calviño Sánchez <danxuliu@gmail.com> + * + * @author Daniel Calviño Sánchez <danxuliu@gmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This store handles the values that need to be customized depending on the + * current UI mode of Talk (main UI, embedded in Files sidebar, video + * verification page...). + */ + +const state = { + mainContainerSelector: undefined, +} + +const getters = { + getMainContainerSelector: (state) => () => { + return state.mainContainerSelector + }, +} + +const mutations = { + setMainContainerSelector(state, mainContainerSelector) { + state.mainContainerSelector = mainContainerSelector + }, +} + +const actions = { + /** + * Set the main container selector. + * + * By default the container selector is undefined, which in practice will + * cause the components to use "body" as the selector. + * + * @param {Object} context default store context + * @param {string} mainContainerSelector the selector for the container + */ + setMainContainerSelector(context, mainContainerSelector) { + context.commit('setMainContainerSelector', mainContainerSelector) + }, +} + +export default { state, mutations, getters, actions } diff --git a/src/views/RoomSelector.vue b/src/views/RoomSelector.vue index 8a7daa696..1c47c322f 100644 --- a/src/views/RoomSelector.vue +++ b/src/views/RoomSelector.vue @@ -21,7 +21,9 @@ --> <template> - <Modal @close="close"> + <Modal + :container="container" + @close="close"> <div id="modal-inner" class="talk-modal" :class="{ 'icon-loading': loading }"> <div id="modal-content"> <h2> @@ -76,6 +78,11 @@ export default { Modal, }, props: { + container: { + type: String, + default: undefined, + }, + dialogTitle: { type: String, default: t('spreed', 'Link to a conversation'), |