diff options
author | Maksim Sukharev <antreesy.web@gmail.com> | 2024-06-28 11:33:59 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-28 11:33:59 +0200 |
commit | 1da2c9f671b050aebc24bb87132b44ef5e8f5b9b (patch) | |
tree | f080aa1e1376148cb745a00e3d03e96cedcff192 | |
parent | 6cb8843f6d412cccd47dabf5635d1e2523f061a4 (diff) | |
parent | b71a50cf29a27460d6c622a2368be937e972cc0e (diff) |
Merge pull request #12387 from nextcloud/fix/app-sidebar-open
fix(sidebar): use open state to remove always working focus trap on mobile
-rw-r--r-- | package-lock.json | 104 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/components/RightSidebar/RightSidebar.vue | 119 | ||||
-rw-r--r-- | src/components/TopBar/TopBar.vue | 111 |
4 files changed, 156 insertions, 180 deletions
diff --git a/package-lock.json b/package-lock.json index c43304b0a..0408e1518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.1", "@nextcloud/upload": "^1.3.0", - "@nextcloud/vue": "^8.11.3", + "@nextcloud/vue": "^8.13.0", "@vueuse/components": "^10.11.0", "crypto-js": "^4.2.0", "debounce": "^2.1.0", @@ -3807,6 +3807,18 @@ "stylelint-config-recommended-vue": "^1.5.0" } }, + "node_modules/@nextcloud/timezones": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nextcloud/timezones/-/timezones-0.1.1.tgz", + "integrity": "sha512-ldLuLyz605sszetnp6jy6mtlThu4ICKsZThxHIZwn6t4QzjQH3xr+k8mRU7GIvKq9egUFDqBp4gBjxm3/ROZig==", + "dependencies": { + "ical.js": "^2.0.1" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + } + }, "node_modules/@nextcloud/typings": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.8.0.tgz", @@ -3851,27 +3863,27 @@ } }, "node_modules/@nextcloud/vue": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.12.0.tgz", - "integrity": "sha512-MHL12+XGIDvpsSdrJn79pYKYrTVUouEymc4No91lKTNZTWDN6bciDSprmMs553hECXrqj7sfwxu6sepj0zcR3Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.13.0.tgz", + "integrity": "sha512-FV0GWSbxkaDQ8S/bWc4XcCXzJfpHzJn4xj0pK/jEwSwdSleBdBFsFFGo+wLCAGoXH5Xf5mHE+LtATFh6wnX7VA==", "dependencies": { "@floating-ui/dom": "^1.1.0", "@linusborg/vue-simple-portal": "^0.1.5", "@nextcloud/auth": "^2.2.1", "@nextcloud/axios": "^2.4.0", "@nextcloud/browser-storage": "^0.4.0", - "@nextcloud/calendar-js": "^7.0.0", "@nextcloud/capabilities": "^1.1.0", "@nextcloud/event-bus": "^3.1.0", "@nextcloud/initial-state": "^2.1.0", "@nextcloud/l10n": "^3.0.1", "@nextcloud/logger": "^3.0.1", "@nextcloud/router": "^3.0.0", + "@nextcloud/timezones": "^0.1.1", "@nextcloud/vue-select": "^3.25.0", "@vueuse/components": "^10.9.0", "@vueuse/core": "^10.9.0", "clone": "^2.1.2", - "debounce": "2.0.0", + "debounce": "2.1.0", "dompurify": "^3.0.5", "emoji-mart-vue-fast": "^15.0.1", "escape-html": "^1.0.3", @@ -3895,6 +3907,7 @@ "vue": "^2.7.16", "vue-color": "^2.8.1", "vue-frag": "^1.4.3", + "vue-router": "^3.6.5", "vue2-datepicker": "^3.11.0" }, "engines": { @@ -3930,19 +3943,6 @@ "@floating-ui/utils": "^0.1.3" } }, - "node_modules/@nextcloud/vue/node_modules/@nextcloud/calendar-js": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@nextcloud/calendar-js/-/calendar-js-7.0.0.tgz", - "integrity": "sha512-CvCcO4hFPjMfIB2AKW0QLNYukGoHFS7QQVvIC8khJjzNfVGS6qMJd2oaZtD9Q9w1fLpvwp1X7orcYGYmosDkAA==", - "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" - }, - "peerDependencies": { - "ical.js": "^2.0.1", - "uuid": "^9.0.0" - } - }, "node_modules/@nextcloud/vue/node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", @@ -3959,17 +3959,6 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@nextcloud/vue/node_modules/debounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.0.0.tgz", - "integrity": "sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@nextcloud/vue/node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -4040,19 +4029,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/@nextcloud/vue/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@nextcloud/vue/node_modules/vfile": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", @@ -10642,8 +10618,7 @@ "node_modules/ical.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-2.0.1.tgz", - "integrity": "sha512-uYYb1CwTXbd9NP/xTtgQZ5ivv6bpUjQu9VM98s3X78L3XRu00uJW5ZtmnLwyxhztpf5fSiRyDpFW7ZNCePlaPw==", - "peer": true + "integrity": "sha512-uYYb1CwTXbd9NP/xTtgQZ5ivv6bpUjQu9VM98s3X78L3XRu00uJW5ZtmnLwyxhztpf5fSiRyDpFW7ZNCePlaPw==" }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -23099,6 +23074,14 @@ "dev": true, "requires": {} }, + "@nextcloud/timezones": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nextcloud/timezones/-/timezones-0.1.1.tgz", + "integrity": "sha512-ldLuLyz605sszetnp6jy6mtlThu4ICKsZThxHIZwn6t4QzjQH3xr+k8mRU7GIvKq9egUFDqBp4gBjxm3/ROZig==", + "requires": { + "ical.js": "^2.0.1" + } + }, "@nextcloud/typings": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.8.0.tgz", @@ -23131,27 +23114,27 @@ } }, "@nextcloud/vue": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.12.0.tgz", - "integrity": "sha512-MHL12+XGIDvpsSdrJn79pYKYrTVUouEymc4No91lKTNZTWDN6bciDSprmMs553hECXrqj7sfwxu6sepj0zcR3Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.13.0.tgz", + "integrity": "sha512-FV0GWSbxkaDQ8S/bWc4XcCXzJfpHzJn4xj0pK/jEwSwdSleBdBFsFFGo+wLCAGoXH5Xf5mHE+LtATFh6wnX7VA==", "requires": { "@floating-ui/dom": "^1.1.0", "@linusborg/vue-simple-portal": "^0.1.5", "@nextcloud/auth": "^2.2.1", "@nextcloud/axios": "^2.4.0", "@nextcloud/browser-storage": "^0.4.0", - "@nextcloud/calendar-js": "^7.0.0", "@nextcloud/capabilities": "^1.1.0", "@nextcloud/event-bus": "^3.1.0", "@nextcloud/initial-state": "^2.1.0", "@nextcloud/l10n": "^3.0.1", "@nextcloud/logger": "^3.0.1", "@nextcloud/router": "^3.0.0", + "@nextcloud/timezones": "^0.1.1", "@nextcloud/vue-select": "^3.25.0", "@vueuse/components": "^10.9.0", "@vueuse/core": "^10.9.0", "clone": "^2.1.2", - "debounce": "2.0.0", + "debounce": "2.1.0", "dompurify": "^3.0.5", "emoji-mart-vue-fast": "^15.0.1", "escape-html": "^1.0.3", @@ -23175,6 +23158,7 @@ "vue": "^2.7.16", "vue-color": "^2.8.1", "vue-frag": "^1.4.3", + "vue-router": "^3.6.5", "vue2-datepicker": "^3.11.0" }, "dependencies": { @@ -23195,12 +23179,6 @@ "@floating-ui/utils": "^0.1.3" } }, - "@nextcloud/calendar-js": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@nextcloud/calendar-js/-/calendar-js-7.0.0.tgz", - "integrity": "sha512-CvCcO4hFPjMfIB2AKW0QLNYukGoHFS7QQVvIC8khJjzNfVGS6qMJd2oaZtD9Q9w1fLpvwp1X7orcYGYmosDkAA==", - "requires": {} - }, "@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", @@ -23211,11 +23189,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" }, - "debounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.0.0.tgz", - "integrity": "sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==" - }, "is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -23260,12 +23233,6 @@ "@types/unist": "^3.0.0" } }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "peer": true - }, "vfile": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", @@ -28328,8 +28295,7 @@ "ical.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-2.0.1.tgz", - "integrity": "sha512-uYYb1CwTXbd9NP/xTtgQZ5ivv6bpUjQu9VM98s3X78L3XRu00uJW5ZtmnLwyxhztpf5fSiRyDpFW7ZNCePlaPw==", - "peer": true + "integrity": "sha512-uYYb1CwTXbd9NP/xTtgQZ5ivv6bpUjQu9VM98s3X78L3XRu00uJW5ZtmnLwyxhztpf5fSiRyDpFW7ZNCePlaPw==" }, "iconv-lite": { "version": "0.4.24", diff --git a/package.json b/package.json index 8fa9240f0..417afc2a1 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^3.0.1", "@nextcloud/upload": "^1.3.0", - "@nextcloud/vue": "^8.11.3", + "@nextcloud/vue": "^8.13.0", "@vueuse/components": "^10.11.0", "crypto-js": "^4.2.0", "debounce": "^2.1.0", diff --git a/src/components/RightSidebar/RightSidebar.vue b/src/components/RightSidebar/RightSidebar.vue index 435240cd6..8efc1c03a 100644 --- a/src/components/RightSidebar/RightSidebar.vue +++ b/src/components/RightSidebar/RightSidebar.vue @@ -4,13 +4,27 @@ --> <template> - <NcAppSidebar v-show="opened" + <NcAppSidebar v-if="token" + :open="opened" :name="conversation.displayName" :title="conversation.displayName" :active.sync="activeTab" :class="'active-tab-' + activeTab" + :toggle-classes="{ 'chat-button-sidebar-toggle': isInCall }" + :toggle-attrs="isInCall ? inCallToggleAttrs : undefined" + @update:open="handleUpdateOpen" + @update:active="handleUpdateActive" @closed="handleClosed" @close="handleClose"> + <!-- Use a custom icon when sidebar is used for chat messages during the call --> + <template v-if="isInCall" #toggle-icon> + <MessageText :size="20" /> + <NcCounterBubble v-if="unreadMessagesCounter > 0" + class="chat-button__unread-messages-counter" + :type="hasUnreadMentions ? 'highlighted' : 'outlined'"> + {{ unreadMessagesCounter }} + </NcCounterBubble> + </template> <template #description> <LobbyStatus v-if="canFullModerate && hasLobbyEnabled" :token="token" /> </template> @@ -94,13 +108,16 @@ import DotsCircle from 'vue-material-design-icons/DotsCircle.vue' import FolderMultipleImage from 'vue-material-design-icons/FolderMultipleImage.vue' import InformationOutline from 'vue-material-design-icons/InformationOutline.vue' import Message from 'vue-material-design-icons/Message.vue' +import MessageText from 'vue-material-design-icons/MessageText.vue' +import { showMessage } from '@nextcloud/dialogs' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' import { t } from '@nextcloud/l10n' import NcAppSidebar from '@nextcloud/vue/dist/Components/NcAppSidebar.js' import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' import BreakoutRoomsTab from './BreakoutRooms/BreakoutRoomsTab.vue' import LobbyStatus from './LobbyStatus.vue' @@ -123,6 +140,7 @@ export default { NcAppSidebar, NcAppSidebarTab, NcButton, + NcCounterBubble, ParticipantsTab, SetGuestUsername, SharedItemsTab, @@ -134,6 +152,7 @@ export default { FolderMultipleImage, InformationOutline, Message, + MessageText, }, props: { @@ -147,6 +166,7 @@ export default { return { activeTab: 'participants', contactsLoading: false, + unreadNotificationHandle: null, } }, @@ -155,7 +175,7 @@ export default { return this.$store.getters.getSidebarStatus }, opened() { - return !!this.token && !this.isInLobby && this.show + return !this.isInLobby && this.show }, token() { return this.$store.getters.getToken() @@ -257,6 +277,21 @@ export default { breakoutRoomsText() { return t('spreed', 'Breakout rooms') }, + + unreadMessagesCounter() { + return this.conversation.unreadMessages + }, + hasUnreadMentions() { + return this.conversation.unreadMention + }, + + inCallToggleAttrs() { + return { + 'data-theme-dark': true, + 'aria-label': t('spreed', 'Open chat'), + title: t('spreed', 'Open chat') + } + }, }, watch: { @@ -287,6 +322,27 @@ export default { }, }, + unreadMessagesCounter(newValue, oldValue) { + if (!this.isInCall || this.opened) { + return + } + + // new messages arrived + if (newValue > 0 && oldValue === 0 && !this.hasUnreadMentions) { + this.notifyUnreadMessages(t('spreed', 'You have new unread messages in the chat.')) + } + }, + + hasUnreadMentions(newValue) { + if (!this.isInCall || this.opened) { + return + } + + if (newValue) { + this.notifyUnreadMessages(t('spreed', 'You have been mentioned in the chat.')) + } + }, + isInCall(newValue) { if (newValue) { // Set 'chat' tab as active, and switch to it if sidebar is open @@ -294,6 +350,9 @@ export default { return } + // discard notification if the call ends + this.notifyUnreadMessages(null) + // If 'chat' tab wasn't active, leave it as is if (this.activeTab !== 'chat') { return @@ -335,11 +394,30 @@ export default { methods: { t, + + openSidebar() { + // In call by default open on chat + if (this.isInCall) { + this.activeTab = 'chat' + } + + this.$store.dispatch('showSidebar') + BrowserStorage.setItem('sidebarOpen', 'true') + }, + handleClose() { this.$store.dispatch('hideSidebar') BrowserStorage.setItem('sidebarOpen', 'false') }, + handleUpdateOpen(open) { + if (open) { + this.openSidebar() + } else { + this.handleClose() + } + }, + handleUpdateActive(active) { this.activeTab = active }, @@ -351,6 +429,21 @@ export default { handleClosed() { emit('files:sidebar:closed', {}) }, + + notifyUnreadMessages(message) { + if (this.unreadNotificationHandle) { + this.unreadNotificationHandle.hideToast() + this.unreadNotificationHandle = null + } + if (message) { + this.unreadNotificationHandle = showMessage(message, { + onClick: () => { + this.activeTab = 'chat' + this.openSidebar() + }, + }) + } + }, }, } </script> @@ -391,4 +484,26 @@ export default { height: 100%; } +.chat-button__unread-messages-counter { + position: absolute; + bottom: 2px; + right: 2px; + pointer-events: none; + + &.counter-bubble__counter--highlighted { + color: var(--color-primary-text); + } +} +</style> + +<style lang="scss"> +/* + * NcAppSidebar toggle it rendered on the page outside the sidebar element, so we need global styles here. + * It is _quite_ safe, as chat-button-sidebar-toggle class is defined here manually, not an internal class. + */ +.chat-button-sidebar-toggle { + position: relative; + // Allow unread counter to overflow rounded button + overflow: visible !important; +} </style> diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index fee8bc701..14d25316d 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -80,38 +80,6 @@ <CallButton shrink-on-mobile :is-screensharing="!!localMediaModel.attributes.localScreen" /> - <!-- sidebar toggle --> - <template v-if="showOpenSidebarButton"> - <!-- in chat: open last tab --> - <NcButton v-if="!isInCall" - :aria-label="t('spreed', 'Open sidebar')" - :title="t('spreed', 'Open sidebar')" - close-after-click="true" - type="tertiary" - @click="openSidebar"> - <template #icon> - <MenuIcon :size="20" /> - </template> - </NcButton> - - <!-- in call: open chat tab --> - <NcButton v-else - :aria-label="t('spreed', 'Open chat')" - :title="t('spreed', 'Open chat')" - class="chat-button" - type="tertiary" - @click="openSidebar('chat')"> - <template #icon> - <MessageText :size="20" /> - <NcCounterBubble v-if="unreadMessagesCounter > 0" - class="chat-button__unread-messages-counter" - :type="hasUnreadMentions ? 'highlighted' : 'outlined'"> - {{ unreadMessagesCounter }} - </NcCounterBubble> - </template> - </NcButton> - </template> - <!-- Breakout rooms editor --> <BreakoutRoomsEditor v-if="showBreakoutRoomsEditor" :token="token" @@ -121,15 +89,11 @@ <script> import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue' -import MenuIcon from 'vue-material-design-icons/Menu.vue' -import MessageText from 'vue-material-design-icons/MessageText.vue' -import { showMessage } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' import { t, n } from '@nextcloud/l10n' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js' import richEditor from '@nextcloud/vue/dist/Mixins/richEditor.js' @@ -163,13 +127,10 @@ export default { ConversationIcon, TopBarMediaControls, NcButton, - NcCounterBubble, TopBarMenu, ReactionMenu, // Icons AccountMultiple, - MenuIcon, - MessageText, }, mixins: [richEditor], @@ -199,7 +160,6 @@ export default { data: () => { return { - unreadNotificationHandle: null, showBreakoutRoomsEditor: false, } }, @@ -209,10 +169,6 @@ export default { return this.$store.getters.getMainContainerSelector() }, - showOpenSidebarButton() { - return !this.$store.getters.getSidebarStatus - }, - isOneToOneConversation() { return this.conversation.type === CONVERSATION.TYPE.ONE_TO_ONE || this.conversation.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER @@ -238,13 +194,6 @@ export default { return getStatusMessage(this.conversation) }, - unread |