fix chat scroll inside scroll
Deploy app frontend / app frontend (push) Successful in 1m43s

This commit is contained in:
2024-12-07 01:33:10 +02:00
parent 14e7dec5b4
commit 7d611b6916
7 changed files with 120 additions and 106 deletions
+1 -1
View File
@@ -10,8 +10,8 @@ declare module 'vue' {
Button: typeof import('primevue/button')['default'] Button: typeof import('primevue/button')['default']
Card: typeof import('primevue/card')['default'] Card: typeof import('primevue/card')['default']
ChatCard: typeof import('./src/components/ChatCard.vue')['default'] ChatCard: typeof import('./src/components/ChatCard.vue')['default']
ChatComponent: typeof import('./src/components/ChatComponent.vue')['default']
ChatForm: typeof import('./src/components/chat/ChatForm.vue')['default'] ChatForm: typeof import('./src/components/chat/ChatForm.vue')['default']
ChatMessages: typeof import('./src/components/chat/ChatMessages.vue')['default']
ChatSubscribeWebPush: typeof import('./src/components/ChatSubscribeWebPush.vue')['default'] ChatSubscribeWebPush: typeof import('./src/components/ChatSubscribeWebPush.vue')['default']
Checkbox: typeof import('primevue/checkbox')['default'] Checkbox: typeof import('primevue/checkbox')['default']
CopyLinkButton: typeof import('@/components/link/LinkCopyButton.vue')['default'] CopyLinkButton: typeof import('@/components/link/LinkCopyButton.vue')['default']
@@ -1,5 +1,5 @@
<template> <template>
<div class="flex gap-3 px-4 pt-0 flex-col-reverse pb-safe-or-4"> <div class="sm:max-w-prose mx-auto flex gap-3 px-4 pt-0 flex-col-reverse sm:pb-safe-or-4 w-full">
<!-- Форма отправки сообщений --> <!-- Форма отправки сообщений -->
<form @submit.prevent="sendMessage" class="flex gap-2 items-center" ref="form"> <form @submit.prevent="sendMessage" class="flex gap-2 items-center" ref="form">
<InputText <InputText
@@ -1,34 +1,32 @@
<template> <template>
<div class="flex flex-col flex-grow place-self-stretch"> <div class="place-self-stretch flex flex-col">
<!-- Список сообщений --> <div
<div class="flex-grow overflow-y-auto p-4" ref="messagesContainer"> v-for="message in messages"
<div :key="message._id"
v-for="message in messages" class="mb-4 max-w-md"
:key="message._id" :class="{
class="mb-4 max-w-md" 'ml-auto text-right': message.from === userRole,
:class="{ 'ml-auto': message.from === userRole, 'mr-auto': message.from !== userRole }" 'mr-auto text-left': message.from !== userRole,
}"
>
<Card
class="p-card-sm"
:class="{
'bg-blue-50 border-blue-300 text-blue-500': message.from === userRole,
'bg-gray-50 border-gray-300 text-gray-700': message.from !== userRole,
}"
> >
<Card <template #content>
class="p-card-sm" {{ message.body }}
:class="{ </template>
'bg-blue-50 border-blue-300 text-blue-500': message.from === userRole, </Card>
'bg-gray-50 border-gray-300 text-gray-700': message.from !== userRole,
}"
>
<template #content>
{{ message.body }}
</template>
</Card>
</div>
<slot
name="endOfMessages"
:count="messages.length"
:lastMessage="messages.length > 0 ? messages[messages.length - 1] : null"
></slot>
</div> </div>
<ChatForm :qr_code_uri :chat :userRole /> <slot
name="endOfMessages"
:count="messages.length"
:lastMessage="messages.length > 0 ? messages[messages.length - 1] : null"
/>
</div> </div>
</template> </template>
@@ -37,8 +35,6 @@ import { ref, onMounted, onUnmounted, useTemplateRef, watch, nextTick } from 'vu
import { messagesDbLocal as db, messagesDbRemote as remoteDb } from '@/api/dbs' import { messagesDbLocal as db, messagesDbRemote as remoteDb } from '@/api/dbs'
import { type MessageDocument } from '@hereconnect/types' import { type MessageDocument } from '@hereconnect/types'
const messagesContainerRef = useTemplateRef('messagesContainer')
const { qr_code_uri, chat, userRole } = defineProps<{ const { qr_code_uri, chat, userRole } = defineProps<{
qr_code_uri: MessageDocument['qr_code_uri'] qr_code_uri: MessageDocument['qr_code_uri']
chat: MessageDocument['chat'] chat: MessageDocument['chat']
@@ -134,13 +130,15 @@ onMounted(async () => {
await loadMessages() await loadMessages()
}) })
const emit = defineEmits<{
(event: 'scrollToBottom'): void
}>()
watch( watch(
() => messages.value?.length, () => messages.value?.length,
async () => { async () => {
if (messagesContainerRef.value) { await nextTick()
await nextTick() emit('scrollToBottom')
messagesContainerRef.value.scrollTop = messagesContainerRef.value.scrollHeight
}
}, },
) )
+17 -4
View File
@@ -1,8 +1,11 @@
<template> <template>
<div class="grid h-full sm:grid-rows-[auto_1fr_auto] grid-rows-[1fr_auto_auto] overflow-hidden"> <div class="grid h-full sm:grid-rows-[auto_1fr_auto] grid-rows-[1fr_auto_auto] overflow-hidden">
<div class="flex overflow-auto pb-safe-or-4 sm:pt-safe w-full px-4 touch-pan-y"> <div
<div class="sm:max-w-prose grid grid-cols-1 mx-auto my-5"> class="flex overflow-auto pb-safe-or-4 sm:pt-safe px-4 touch-pan-y"
<slot /> ref="contentScrollContainer"
>
<div class="sm:max-w-prose grid grid-cols-1 mx-auto my-5 w-full">
<slot name="default" v-bind:scrollToBottom />
</div> </div>
</div> </div>
@@ -25,11 +28,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useTemplateRef } from 'vue'
const slots = defineSlots<{ const slots = defineSlots<{
default?: () => void default?: (slotBindings: { scrollToBottom: typeof scrollToBottom }) => void
footer?: () => void footer?: () => void
}>() }>()
const contentScrollContainerRef = useTemplateRef('contentScrollContainer')
const scrollToBottom = () => {
if (contentScrollContainerRef.value) {
contentScrollContainerRef.value.scrollTop = contentScrollContainerRef.value.scrollHeight
}
}
const layoutPageHead = useHead({ const layoutPageHead = useHead({
htmlAttrs: { class: 'overflow-hidden' }, htmlAttrs: { class: 'overflow-hidden' },
bodyAttrs: { class: 'overflow-hidden' }, bodyAttrs: { class: 'overflow-hidden' },
+32 -22
View File
@@ -1,32 +1,42 @@
<template> <template>
<LayoutWithTabs> <LayoutWithTabs>
<MetaHead> <template #default="{ scrollToBottom }">
<title>Чат НаСвязи</title> <MetaHead>
<meta <title>Чат НаСвязи</title>
name="description" <meta
content="Чат QR-кода. Безопасная и конфиденциальная связь для решения всех вопросов" name="description"
/> content="Чат QR-кода. Безопасная и конфиденциальная связь для решения всех вопросов"
</MetaHead> />
</MetaHead>
<ChatComponent userRole="guest" :qr_code_uri="qr_code_uri" :chat="chat"> <ChatMessages
<template #endOfMessages="{ count, lastMessage }"> userRole="guest"
<!-- Уведомление, если гость отправил только одно сообщение --> :qr_code_uri="qr_code_uri"
<div :chat="chat"
v-if="count === 1 && lastMessage && lastMessage.from === 'guest'" @scrollToBottom="scrollToBottom()"
class="flex flex-col items-center justify-center text-center bg-gray-50 border border-gray-200 rounded-lg shadow-md p-6 mt-6" >
> <template #endOfMessages="{ count, lastMessage }">
<p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p> <!-- Уведомление, если гость отправил только одно сообщение -->
<p class="text-gray-600 mt-2"> <div
Вы можете написать ещё одно сообщение или подождать ответа. v-if="count === 1 && lastMessage && lastMessage.from === 'guest'"
</p> class="flex flex-col items-center justify-center text-center bg-gray-50 border border-gray-200 rounded-lg shadow-md p-6 my-6"
</div> >
</template> <p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p>
</ChatComponent> <p class="text-gray-600 mt-2">
Вы можете написать ещё одно сообщение или подождать ответа.
</p>
</div>
</template>
</ChatMessages>
</template>
<template #footer>
<ChatForm :qr_code_uri :chat userRole="guest" />
</template>
</LayoutWithTabs> </LayoutWithTabs>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { MessageDocument } from '@hereconnect/types' import type { MessageDocument } from '@hereconnect/types'
import ChatComponent from '@/components/ChatComponent.vue'
defineProps<{ qr_code_uri: MessageDocument['qr_code_uri']; chat: MessageDocument['chat'] }>() defineProps<{ qr_code_uri: MessageDocument['qr_code_uri']; chat: MessageDocument['chat'] }>()
</script> </script>
@@ -12,7 +12,7 @@
<template #default="{ data }"> <template #default="{ data }">
<div <div
v-if="data.length > 0" v-if="data.length > 0"
class="justify-self-start align-baseline-start flex flex-col gap-4" class="grid grid-cols-1 gap-5 place-self-center sm:place-self-start w-full"
> >
<ChatCard <ChatCard
v-for="row in data" v-for="row in data"
@@ -1,56 +1,49 @@
<template> <template>
<LayoutWithTabs> <LayoutWithTabs>
<MetaHead> <template #default="{ scrollToBottom }">
<title>Чат {{ chatDoc?.name ? `«${chatDoc.name}»` : '' }} НаСвязи</title> <MetaHead>
<meta <title>Чат {{ chatDoc?.name ? `«${chatDoc.name}»` : '' }} НаСвязи</title>
name="description" <meta
content="Ваш QR-код успешно создан! Узнайте, как его использовать и настройте все доступные функции для удобного взаимодействия с окружающими." name="description"
/> content="Ваш QR-код успешно создан! Узнайте, как его использовать и настройте все доступные функции для удобного взаимодействия с окружающими."
</MetaHead> />
</MetaHead>
<div class="flex flex-col min-h-full"> <ChatMessages
<!-- Основной контент --> userRole="owner"
<main class="flex-grow flex flex-col"> :qr_code_uri="qr_code_uri"
<section class="flex flex-col flex-grow max-w-prose w-full mx-auto"> :chat="chat"
<ChatComponent userRole="owner" :qr_code_uri="qr_code_uri" :chat="chat"> @scrollToBottom="scrollToBottom()"
<template #endOfMessages="{ count, lastMessage }"> >
<div <template #endOfMessages="{ count, lastMessage }">
v-if="!count" <div
class="flex flex-col items-center justify-center flex-grow text-center text-gray-500" v-if="!count"
> class="flex flex-col items-center justify-center flex-grow text-center text-gray-500"
<h1 class="text-2xl font-bold mb-4">Пока нет сообщений</h1> >
<p class="text-gray-600">Вы можете начать общение, отправив первое сообщение.</p> <h1 class="text-2xl font-bold mb-4">Пока нет сообщений</h1>
</div> <p class="text-gray-600">Вы можете начать общение, отправив первое сообщение.</p>
<div </div>
v-else-if="lastMessage && lastMessage.from === 'owner'" <div
class="flex flex-col items-center justify-center text-center bg-gray-50 border border-gray-200 rounded-lg shadow-md p-6 mt-6" v-else-if="lastMessage && lastMessage.from === 'owner'"
> class="flex flex-col items-center justify-center text-center bg-gray-50 border border-gray-200 rounded-lg shadow-md p-6 my-6"
<p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p> >
<p class="text-gray-600 mt-2"> <p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p>
Вы можете написать ещё одно сообщение или подождать ответа. <p class="text-gray-600 mt-2">
</p> Вы можете написать ещё одно сообщение или подождать ответа.
</div> </p>
</template> </div>
</ChatComponent> </template>
</section> </ChatMessages>
</main> </template>
<!-- Футер --> <template #footer>
<!-- <footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">--> <ChatForm :qr_code_uri :chat userRole="owner" />
<!-- <p><a href="/create" class="underline underline-offset-2">Создайте свой QRкод</a>.</p>--> </template>
<!-- <p class="mt-1">-->
<!-- Сделайте НаСвязи удобнее. Будем рады вашей&nbsp;<a href="/donate" class="underline underline-offset-2"-->
<!-- >поддержке</a-->
<!-- >.-->
<!-- </p>-->
<!-- </footer>-->
</div>
</LayoutWithTabs> </LayoutWithTabs>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { MessageDocument } from '@hereconnect/types' import type { MessageDocument } from '@hereconnect/types'
import ChatComponent from '@/components/ChatComponent.vue'
import { getQrCodeDocument } from '@/api/qrCode' import { getQrCodeDocument } from '@/api/qrCode'
import { useQuery } from '@tanstack/vue-query' import { useQuery } from '@tanstack/vue-query'