mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-10-26 06:19:36 +00:00
load older messages when the thread cannot scroll
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<div class="flex flex-col relative h-full">
|
||||
<div ref="threadEl" class="flex-1 overflow-y-auto overscroll-contain [overflow-anchor:none]" @scroll="handleScroll">
|
||||
<div ref="contentEl" class="min-h-full px-4 pb-10 relative">
|
||||
<div v-if="loadingOlder" class="flex justify-center py-3" role="status">
|
||||
<div v-if="loadingOlder && conversationStore.messages.fetching" class="flex justify-center py-3" role="status">
|
||||
<Loader2 class="size-4 animate-spin" aria-hidden="true" />
|
||||
<span class="sr-only">{{ $t('globals.terms.loading') }}</span>
|
||||
</div>
|
||||
@@ -75,6 +75,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { useConversationRead } from '@main/composables/useConversationRead'
|
||||
import MessageBubble from './MessageBubble.vue'
|
||||
import ActivityMessageBubble from './ActivityMessageBubble.vue'
|
||||
@@ -103,6 +104,7 @@ const route = useRoute()
|
||||
const conversationStore = useConversationStore()
|
||||
const userStore = useUserStore()
|
||||
const threadEl = ref(null)
|
||||
const { height: threadHeight } = useElementSize(threadEl)
|
||||
const contentEl = ref(null)
|
||||
const readMarkerEl = ref(null)
|
||||
const readReady = ref(false)
|
||||
@@ -277,6 +279,7 @@ const loadMore = async () => {
|
||||
const thread = threadEl.value
|
||||
if (!thread) return
|
||||
const conversationUUID = conversationStore.current?.uuid
|
||||
const previousPage = conversationStore.messages.data.getLastFetchedPage(conversationUUID)
|
||||
const threadTop = thread.getBoundingClientRect().top
|
||||
const anchor = [...thread.querySelectorAll('[data-message-uuid]')].find(
|
||||
element => element.getBoundingClientRect().bottom > threadTop
|
||||
@@ -288,7 +291,6 @@ const loadMore = async () => {
|
||||
loadingOlder.value = true
|
||||
try {
|
||||
await conversationStore.fetchNextMessages()
|
||||
loadingOlder.value = false
|
||||
await nextTick()
|
||||
if (conversationUUID !== conversationStore.current?.uuid || thread !== threadEl.value) return
|
||||
if (anchor?.isConnected) {
|
||||
@@ -296,11 +298,27 @@ const loadMore = async () => {
|
||||
} else {
|
||||
scrollToOffset(thread.scrollHeight - prevHeight + prevTop)
|
||||
}
|
||||
if (thread.scrollHeight <= thread.clientHeight) hasUserScrolled.value = false
|
||||
} finally {
|
||||
loadingOlder.value = false
|
||||
if (
|
||||
conversationUUID !== conversationStore.current?.uuid ||
|
||||
conversationStore.messages.data.getLastFetchedPage(conversationUUID) > previousPage
|
||||
) fillShortThread()
|
||||
}
|
||||
}
|
||||
|
||||
const fillShortThread = () => {
|
||||
const thread = threadEl.value
|
||||
if (
|
||||
readReady.value &&
|
||||
thread?.clientHeight > 0 &&
|
||||
thread.scrollHeight <= thread.clientHeight
|
||||
) loadMore()
|
||||
}
|
||||
|
||||
watch([threadHeight, readReady], fillShortThread, { flush: 'post' })
|
||||
|
||||
const messageRows = computed(() => {
|
||||
const messages = conversationStore.conversationMessages
|
||||
return messages.map((message, index) => {
|
||||
|
||||
@@ -541,6 +541,63 @@ describe('Conversation lifecycle', () => {
|
||||
})
|
||||
})
|
||||
|
||||
for (const scenario of [
|
||||
{ name: 'loads until the thread can scroll', height: 2400, total: 90 },
|
||||
{ name: 'stops at the final page while the thread still fits', height: 4000, total: 60 },
|
||||
{ name: 'stops after an older page fails', height: 4000, total: 90, fail: true }
|
||||
]) {
|
||||
it(`fills a short first page and ${scenario.name}`, () => {
|
||||
const requestedPages = []
|
||||
cy.viewport(1440, scenario.height)
|
||||
cy.api('GET', `/api/v1/conversations/${conversationUuid}/messages?page=1`).then(({ body }) => {
|
||||
const original = body.data.results.find((message) => message.type === 'incoming')
|
||||
const messages = Array.from({ length: scenario.total }, (_, index) => ({
|
||||
...original,
|
||||
id: original.id + index,
|
||||
uuid: `${conversationUuid.slice(0, -12)}${String(index + 1).padStart(12, '0')}`,
|
||||
created_at: new Date(Date.now() - (scenario.total - index) * 1000).toISOString(),
|
||||
content_type: 'text',
|
||||
content: `Short history ${index + 1}`,
|
||||
attachments: []
|
||||
}))
|
||||
cy.intercept('GET', `**/conversations/${conversationUuid}/messages?page=*`, (request) => {
|
||||
const page = Number(request.query.page)
|
||||
requestedPages.push(page)
|
||||
if (scenario.fail && page > 1) {
|
||||
request.reply({ statusCode: 503, body: { error: { type: 'GeneralException', message: 'History request failed' } } })
|
||||
return
|
||||
}
|
||||
request.reply({ body: { data: {
|
||||
results: messages.slice(Math.max(0, scenario.total - page * 30), scenario.total - (page - 1) * 30).reverse(),
|
||||
page, per_page: 30, total: scenario.total, total_pages: Math.ceil(scenario.total / 30)
|
||||
} } })
|
||||
}).as('shortHistoryPage')
|
||||
cy.intercept('PUT', `**/conversations/${conversationUuid}/last-seen`, {
|
||||
body: { data: { conversation_uuid: conversationUuid, read_version: 1, is_unread: false } }
|
||||
})
|
||||
})
|
||||
cy.visit(`/inboxes/all/conversation/${conversationUuid}`)
|
||||
cy.wait('@shortHistoryPage').its('request.query.page').should('eq', '1')
|
||||
cy.wait('@shortHistoryPage').then(({ request, response }) => {
|
||||
expect(request.query.page).to.eq('2')
|
||||
expect(response.statusCode).to.eq(scenario.fail ? 503 : 200)
|
||||
})
|
||||
if (scenario.fail) {
|
||||
cy.get('[data-message-uuid]').should('have.length', 30)
|
||||
} else {
|
||||
cy.get('[data-message-uuid]').should('have.length', 60)
|
||||
}
|
||||
cy.get('[class*="overflow-anchor"]').find('[role="status"]').should('not.exist')
|
||||
cy.get('[class*="overflow-anchor"]').should(($thread) => {
|
||||
const thread = $thread[0]
|
||||
if (scenario.height === 2400) expect(thread.scrollHeight).to.be.greaterThan(thread.clientHeight)
|
||||
else expect(thread.scrollHeight).to.eq(thread.clientHeight)
|
||||
expect(requestedPages).to.deep.eq([1, 2])
|
||||
})
|
||||
if (scenario.height === 4000) cy.get('.absolute.bottom-5.right-6').should('not.be.visible')
|
||||
})
|
||||
}
|
||||
|
||||
it('keeps file and image tiles equal and reveals controls only for the active attachment', () => {
|
||||
const finishTransitions = () => cy.get('.group\\/attachment').then(($items) => {
|
||||
for (const item of $items) {
|
||||
|
||||
Reference in New Issue
Block a user