Vendored
+1
@@ -11,6 +11,7 @@ declare module 'vue' {
|
|||||||
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']
|
ChatComponent: typeof import('./src/components/ChatComponent.vue')['default']
|
||||||
|
ChatForm: typeof import('./src/components/chat/ChatForm.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,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- TODO: figure out how to get rid of v-if. Incorrect works on switch tabs -->
|
<RouterLink :to="route" class="p-4 rounded-lg bg-white shadow-md">
|
||||||
<RouterLink :to="route" class="p-4 rounded-lg bg-white shadow-md" v-if="chatDoc.qr_code_uri">
|
<div class="flex items-center justify-between mb-2" v-if="qrCodeDoc">
|
||||||
<div class="flex items-center justify-between mb-2">
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h2 class="text-lg font-semibold">{{ chatDoc.name }}</h2>
|
<QRCodePlacementIcon :placement="qrCodeDoc.placement" />
|
||||||
|
<h2 class="text-lg font-semibold">{{ qrCodeDoc.name }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-500">
|
<div class="text-gray-500">
|
||||||
@@ -14,11 +14,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { ChatDocument } from '@hereconnect/types'
|
import type { ChatDocument, QRCodeDocument } from '@hereconnect/types'
|
||||||
import { ROUTE_NAMES } from '@/constants/routes'
|
import { ROUTE_NAMES } from '@/constants/routes'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
chatDoc: ChatDocument
|
chatDoc: ChatDocument
|
||||||
|
qrCodeDoc?: QRCodeDocument
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formatDate = (date: string) => {
|
const formatDate = (date: string) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-grow">
|
<div class="flex flex-col flex-grow place-self-stretch">
|
||||||
<!-- Список сообщений -->
|
<!-- Список сообщений -->
|
||||||
<div class="flex-grow overflow-y-auto p-4" ref="messagesContainer">
|
<div class="flex-grow overflow-y-auto p-4" ref="messagesContainer">
|
||||||
<div
|
<div
|
||||||
@@ -28,41 +28,15 @@
|
|||||||
></slot>
|
></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 px-4 pt-0 flex-col-reverse pb-safe-or-4">
|
<ChatForm :qr_code_uri :chat :userRole />
|
||||||
<!-- Форма отправки сообщений -->
|
|
||||||
<form @submit.prevent="sendMessage" class="flex gap-2 items-center" ref="form">
|
|
||||||
<InputText
|
|
||||||
id="newMessage"
|
|
||||||
v-model="newMessage"
|
|
||||||
class="flex-1"
|
|
||||||
placeholder="Напишите сообщение"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
icon="pi pi-send"
|
|
||||||
class="p-button-md"
|
|
||||||
:disabled="!newMessage.trim()"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<ChatSubscribeWebPush :qr_code_uri :chat :user_chat_role="userRole" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, useTemplateRef, watch, nextTick } from 'vue'
|
import { ref, onMounted, onUnmounted, useTemplateRef, watch, nextTick } from 'vue'
|
||||||
import {
|
import { messagesDbLocal as db, messagesDbRemote as remoteDb } from '@/api/dbs'
|
||||||
chatsDbLocal,
|
import { type MessageDocument } from '@hereconnect/types'
|
||||||
chatsDbRemote,
|
|
||||||
messagesDbLocal as db,
|
|
||||||
messagesDbRemote as remoteDb,
|
|
||||||
} from '@/api/dbs'
|
|
||||||
import { type ChatDocument, type MessageDocument } from '@hereconnect/types'
|
|
||||||
import { getBrowserUuid } from '@/utils/getBrowserUuid'
|
|
||||||
import { getUserUuid } from '@/utils/getUserUuid'
|
|
||||||
|
|
||||||
const formRef = useTemplateRef('form')
|
|
||||||
const messagesContainerRef = useTemplateRef('messagesContainer')
|
const messagesContainerRef = useTemplateRef('messagesContainer')
|
||||||
|
|
||||||
const { qr_code_uri, chat, userRole } = defineProps<{
|
const { qr_code_uri, chat, userRole } = defineProps<{
|
||||||
@@ -73,10 +47,9 @@ const { qr_code_uri, chat, userRole } = defineProps<{
|
|||||||
|
|
||||||
// Локальные состояния
|
// Локальные состояния
|
||||||
const messages = ref<MessageDocument[]>([])
|
const messages = ref<MessageDocument[]>([])
|
||||||
const newMessage = ref('')
|
|
||||||
let changesSubscription: PouchDB.Core.Changes<MessageDocument> | null = null
|
let changesSubscription: PouchDB.Core.Changes<MessageDocument> | null = null
|
||||||
let syncMessages: PouchDB.Replication.Sync<MessageDocument> | null = null
|
let syncMessages: PouchDB.Replication.Sync<MessageDocument> | null = null
|
||||||
// let syncChats: PouchDB.Replication.Sync<ChatDocument> | null = null
|
|
||||||
|
|
||||||
// Селектор для фильтрации сообщений
|
// Селектор для фильтрации сообщений
|
||||||
const messageSelector = {
|
const messageSelector = {
|
||||||
@@ -98,39 +71,6 @@ async function loadMessages() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправка нового сообщения
|
|
||||||
async function sendMessage() {
|
|
||||||
const created_at = new Date().toISOString()
|
|
||||||
const user_uuid = getUserUuid()
|
|
||||||
const message: MessageDocument = {
|
|
||||||
_id: `message:${qr_code_uri}:${chat}:${created_at}`,
|
|
||||||
type: 'message',
|
|
||||||
qr_code_uri,
|
|
||||||
chat,
|
|
||||||
from: userRole,
|
|
||||||
body: newMessage.value.trim(),
|
|
||||||
browser_uuid: getBrowserUuid(),
|
|
||||||
user_uuid,
|
|
||||||
created_at,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.put(message)
|
|
||||||
newMessage.value = ''
|
|
||||||
|
|
||||||
// await chatsDbLocal.upsert(`chat:${user_uuid}:${userRole}:${qr_code_uri}:${chat}`, (chatDoc) => {
|
|
||||||
// if (chatDoc.last_message_at && chatDoc.last_message_at < created_at) {
|
|
||||||
// chatDoc.last_message_at = created_at
|
|
||||||
// return chatDoc
|
|
||||||
// } else {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка отправки сообщения:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отслеживание изменений
|
// Отслеживание изменений
|
||||||
function watchChanges() {
|
function watchChanges() {
|
||||||
changesSubscription = db
|
changesSubscription = db
|
||||||
@@ -169,15 +109,6 @@ function setupSync() {
|
|||||||
syncMessages.on('error', (err) => {
|
syncMessages.on('error', (err) => {
|
||||||
console.error('Ошибка синхронизации:', err)
|
console.error('Ошибка синхронизации:', err)
|
||||||
})
|
})
|
||||||
// syncChats = chatsDbLocal.sync(chatsDbRemote, {
|
|
||||||
// live: true,
|
|
||||||
// retry: true,
|
|
||||||
// selector: {
|
|
||||||
// type: 'chat',
|
|
||||||
// qr_code_uri,
|
|
||||||
// chat,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Остановка синхронизации
|
// Остановка синхронизации
|
||||||
@@ -186,10 +117,6 @@ function stopSync() {
|
|||||||
syncMessages.cancel()
|
syncMessages.cancel()
|
||||||
syncMessages = null
|
syncMessages = null
|
||||||
}
|
}
|
||||||
// if (syncChats) {
|
|
||||||
// syncChats.cancel()
|
|
||||||
// syncChats = null
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отписка от изменений
|
// Отписка от изменений
|
||||||
@@ -205,7 +132,6 @@ onMounted(async () => {
|
|||||||
watchChanges()
|
watchChanges()
|
||||||
setupSync()
|
setupSync()
|
||||||
await loadMessages()
|
await loadMessages()
|
||||||
formRef.value?.querySelector('input')?.focus()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex gap-3 px-4 pt-0 flex-col-reverse pb-safe-or-4">
|
||||||
|
<!-- Форма отправки сообщений -->
|
||||||
|
<form @submit.prevent="sendMessage" class="flex gap-2 items-center" ref="form">
|
||||||
|
<InputText
|
||||||
|
id="newMessage"
|
||||||
|
v-model="newMessage"
|
||||||
|
class="flex-1"
|
||||||
|
placeholder="Напишите сообщение"
|
||||||
|
/>
|
||||||
|
<Button type="submit" icon="pi pi-send" class="p-button-md" :disabled="!newMessage.trim()" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ChatSubscribeWebPush :qr_code_uri :chat :user_chat_role="userRole" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, useTemplateRef } from 'vue'
|
||||||
|
const newMessage = ref('')
|
||||||
|
const formRef = useTemplateRef('form')
|
||||||
|
import { getBrowserUuid } from '@/utils/getBrowserUuid'
|
||||||
|
import { getUserUuid } from '@/utils/getUserUuid'
|
||||||
|
import { type MessageDocument } from '@hereconnect/types'
|
||||||
|
import { messagesDbLocal as db } from '@/api/dbs'
|
||||||
|
|
||||||
|
const { qr_code_uri, chat, userRole } = defineProps<{
|
||||||
|
qr_code_uri: MessageDocument['qr_code_uri']
|
||||||
|
chat: MessageDocument['chat']
|
||||||
|
userRole: MessageDocument['from']
|
||||||
|
}>()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
formRef.value?.querySelector('input')?.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Отправка нового сообщения
|
||||||
|
async function sendMessage() {
|
||||||
|
const created_at = new Date().toISOString()
|
||||||
|
const user_uuid = getUserUuid()
|
||||||
|
const message: MessageDocument = {
|
||||||
|
_id: `message:${qr_code_uri}:${chat}:${created_at}`,
|
||||||
|
type: 'message',
|
||||||
|
qr_code_uri,
|
||||||
|
chat,
|
||||||
|
from: userRole,
|
||||||
|
body: newMessage.value.trim(),
|
||||||
|
browser_uuid: getBrowserUuid(),
|
||||||
|
user_uuid,
|
||||||
|
created_at,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.put(message)
|
||||||
|
newMessage.value = ''
|
||||||
|
formRef.value?.querySelector('input')?.focus()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка отправки сообщения:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -69,7 +69,6 @@ const {
|
|||||||
} = useMutation({
|
} = useMutation({
|
||||||
mutationKey: ['createQrCode'],
|
mutationKey: ['createQrCode'],
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
throw new Error('wtf')
|
|
||||||
const qrCodeDocument = await createQrCodeDocument({
|
const qrCodeDocument = await createQrCodeDocument({
|
||||||
name: name.value,
|
name: name.value,
|
||||||
placement: placement.value!,
|
placement: placement.value!,
|
||||||
|
|||||||
@@ -8,37 +8,20 @@
|
|||||||
/>
|
/>
|
||||||
</MetaHead>
|
</MetaHead>
|
||||||
|
|
||||||
<div class="flex flex-col min-h-full">
|
<ChatComponent userRole="guest" :qr_code_uri="qr_code_uri" :chat="chat">
|
||||||
<!-- Основной контент -->
|
<template #endOfMessages="{ count, lastMessage }">
|
||||||
<main class="flex-grow flex flex-col">
|
<!-- Уведомление, если гость отправил только одно сообщение -->
|
||||||
<section class="flex flex-col flex-grow max-w-prose w-full mx-auto">
|
<div
|
||||||
<ChatComponent userRole="guest" :qr_code_uri="qr_code_uri" :chat="chat">
|
v-if="count === 1 && lastMessage && lastMessage.from === 'guest'"
|
||||||
<template #endOfMessages="{ count, lastMessage }">
|
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"
|
||||||
<!-- Уведомление, если гость отправил только одно сообщение -->
|
>
|
||||||
<div
|
<p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p>
|
||||||
v-if="count === 1 && lastMessage && lastMessage.from === 'guest'"
|
<p class="text-gray-600 mt-2">
|
||||||
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"
|
Вы можете написать ещё одно сообщение или подождать ответа.
|
||||||
>
|
</p>
|
||||||
<p class="text-gray-700 font-medium text-lg">Ваше сообщение отправлено.</p>
|
</div>
|
||||||
<p class="text-gray-600 mt-2">
|
</template>
|
||||||
Вы можете написать ещё одно сообщение или подождать ответа.
|
</ChatComponent>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ChatComponent>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Футер -->
|
|
||||||
<!-- <footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">-->
|
|
||||||
<!-- <p><a href="/create" class="underline underline-offset-2">Создайте свой QR‑код</a>.</p>-->
|
|
||||||
<!-- <p class="mt-1">-->
|
|
||||||
<!-- Сделайте НаСвязи удобнее. Будем рады вашей <a href="/donate" class="underline underline-offset-2"-->
|
|
||||||
<!-- >поддержке</a-->
|
|
||||||
<!-- >.-->
|
|
||||||
<!-- </p>-->
|
|
||||||
<!-- </footer>-->
|
|
||||||
</div>
|
|
||||||
</LayoutWithTabs>
|
</LayoutWithTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
<QueryRender :query="query">
|
<QueryRender :query="query">
|
||||||
<template #default="{ data }">
|
<template #default="{ data }">
|
||||||
<div
|
<div
|
||||||
v-if="data.rows.length > 0"
|
v-if="data.length > 0"
|
||||||
class="justify-self-start align-baseline-start flex flex-col gap-4"
|
class="justify-self-start align-baseline-start flex flex-col gap-4"
|
||||||
>
|
>
|
||||||
<ChatCard
|
<ChatCard
|
||||||
v-for="{ doc: chatDoc } in data.rows.filter((row) => !!row.doc)"
|
v-for="row in data"
|
||||||
:key="`${chatDoc!._id}`"
|
:key="row.id"
|
||||||
:chatDoc="chatDoc!"
|
:chatDoc="row.doc!"
|
||||||
|
:qrCodeDoc="row.qrCodeDoc"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,19 +45,32 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useQuery } from '@tanstack/vue-query'
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
import { chatsDbRemote } from '@/api/dbs'
|
import { chatsDbRemote, qrCodeDbLocal } from '@/api/dbs'
|
||||||
import { getUserUuid } from '@/utils/getUserUuid'
|
import { getUserUuid } from '@/utils/getUserUuid'
|
||||||
|
import { type QRCodeDocument } from '@hereconnect/types'
|
||||||
|
|
||||||
|
const user_uuid = getUserUuid()
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['qrCodes'],
|
queryKey: ['chats', user_uuid],
|
||||||
queryFn: () => {
|
queryFn: async () => {
|
||||||
const user_uuid = getUserUuid()
|
const { rows } = await chatsDbRemote.allDocs({
|
||||||
return chatsDbRemote.allDocs({
|
|
||||||
startkey: `chat:${user_uuid}:`,
|
startkey: `chat:${user_uuid}:`,
|
||||||
endkey: `chat:${user_uuid}:\uffff`,
|
endkey: `chat:${user_uuid}:\uffff`,
|
||||||
limit: Number.MAX_SAFE_INTEGER,
|
limit: Number.MAX_SAFE_INTEGER,
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
|
const qrCodes = await qrCodeDbLocal.allDocs({
|
||||||
|
keys: rows.map((row) => `qr_code:${row.doc!.qr_code_uri}`),
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
const result: ((typeof rows)[number] & { qrCodeDoc?: QRCodeDocument })[] = []
|
||||||
|
qrCodes.rows.forEach((row, index) => {
|
||||||
|
result[index] = rows[index]
|
||||||
|
if ('doc' in row) {
|
||||||
|
result[index].qrCodeDoc = row.doc!
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user