diff --git a/nginx/vue/src/components/util/Chat.vue b/nginx/vue/src/components/util/Chat.vue index f859684..28c9e1b 100644 --- a/nginx/vue/src/components/util/Chat.vue +++ b/nginx/vue/src/components/util/Chat.vue @@ -11,21 +11,47 @@ const authStore = useAuthStore(); const messages = computed(() => messagesStore.messages); const messageInput = ref(""); const messagesContainer = ref(null); +const messagesInner = ref(null); const fileInput = ref(null); +const isNearBottom = ref(true); +const SCROLL_THRESHOLD = 100; +let resizeObserver = null; + function scrollToBottom() { - nextTick(() => { - if (messagesContainer.value) { - messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; - } - }); + if (messagesContainer.value) { + messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; + } } -watch(messages, scrollToBottom, { deep: true }); +function scrollToBottomIfNear() { + if (isNearBottom.value) { + scrollToBottom(); + } +} + +function onScroll() { + if (!messagesContainer.value) return; + const { scrollHeight, scrollTop, clientHeight } = messagesContainer.value; + isNearBottom.value = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD; +} + +function goToBottom() { + isNearBottom.value = true; + scrollToBottom(); +} + +watch( + () => messages.value.length, + () => { + nextTick(scrollToBottomIfNear); + }, +); function sendMessage() { const text = messageInput.value.trim(); if (!text) return; + isNearBottom.value = true; messagesStore.sendMessage(text); messageInput.value = ""; } @@ -33,6 +59,7 @@ function sendMessage() { async function onFileSelected(e) { const file = e.target.files[0]; if (!file) return; + isNearBottom.value = true; await messagesStore.uploadAndSendFile(file); fileInput.value.value = ""; } @@ -73,9 +100,30 @@ function parseMessageParts(text) { onMounted(() => { messagesStore.connect(); + + if (messagesContainer.value) { + messagesContainer.value.addEventListener("scroll", onScroll, { passive: true }); + } + + if (messagesInner.value) { + resizeObserver = new ResizeObserver(scrollToBottomIfNear); + resizeObserver.observe(messagesInner.value); + } + + scrollToBottom(); }); + onUnmounted(() => { messagesStore.disconnect(); + + if (messagesContainer.value) { + messagesContainer.value.removeEventListener("scroll", onScroll); + } + + if (resizeObserver) { + resizeObserver.disconnect(); + resizeObserver = null; + } }); @@ -83,41 +131,41 @@ onUnmounted(() => {
- {{ message.authorId }}:
-
- {{ part.value }}
+
+ {{ message.authorId }}:
+
- {{ part.value }}
-
-
-
-
- {{
- message.fileUrl.split("/").pop()
- }}
-
-
+
+ {{
+ message.fileUrl.split("/").pop()
+ }}
+
+