|
22 | 22 | <div v-if="detailLoading" class="chat-loading"> |
23 | 23 | <v-circular-progress indeterminate class="sm" /> |
24 | 24 | </div> |
25 | | - <template v-else> |
| 25 | + <div v-if="loadingMore" class="chat-loading-more"> |
| 26 | + <v-circular-progress indeterminate class="sm" /> |
| 27 | + </div> |
| 28 | + <template v-if="!detailLoading"> |
26 | 29 | <div v-for="(item, index) in sortedItems" :key="item.id" class="chat-message-wrapper"> |
27 | 30 | <div v-if="showDateSeparator(index)" class="chat-date-separator"> |
28 | 31 | <span>{{ formatDateLabel(item.date) }}</span> |
@@ -191,6 +194,11 @@ let lastMmsSendBody = '' |
191 | 194 | let lastMmsSendAddress = '' |
192 | 195 | const { tags, fetch: fetchTags } = useTags(dataType) |
193 | 196 |
|
| 197 | +const PAGE_SIZE = 100 |
| 198 | +const offset = ref(0) |
| 199 | +const noMoreOlder = ref(false) |
| 200 | +const loadingMore = ref(false) |
| 201 | +
|
194 | 202 | // MMS size constants — images are auto-compressed on the server, but |
195 | 203 | // video/audio cannot be compressed and will fail if too large. |
196 | 204 | const MMS_WARN_SIZE = 300 * 1024 // 300 KB — conservative carrier minimum |
@@ -286,31 +294,65 @@ function scrollToBottom() { |
286 | 294 | } |
287 | 295 |
|
288 | 296 | function onScroll() { |
289 | | - // placeholder for future infinite scroll |
| 297 | + if (!chatScrollRef.value || loadingMore.value || noMoreOlder.value || loading.value) return |
| 298 | + if (chatScrollRef.value.scrollTop < 200) { |
| 299 | + fetchMore() |
| 300 | + } |
290 | 301 | } |
291 | 302 |
|
292 | | -const { loading, fetch } = initLazyQuery({ |
| 303 | +const { loading, fetch: rawFetch } = initLazyQuery({ |
293 | 304 | handle: (data: { sms: IMessage[]; smsCount: number }, error: string) => { |
294 | | - detailLoading.value = false |
295 | 305 | if (error) { |
| 306 | + detailLoading.value = false |
| 307 | + loadingMore.value = false |
296 | 308 | toast(t(error), 'error') |
297 | 309 | } else if (data) { |
298 | | - items.value = data.sms |
299 | | - // Clear the in-memory SMS pending item once the backend confirms the new message |
300 | | - if (pendingSmsItem.value && data.sms.length > pendingSmsPreCount) { |
301 | | - pendingSmsItem.value = null |
| 310 | + if (loadingMore.value) { |
| 311 | + const el = chatScrollRef.value |
| 312 | + const prevScrollHeight = el?.scrollHeight ?? 0 |
| 313 | + const existingIds = new Set(items.value.map((i) => i.id)) |
| 314 | + const newItems = data.sms.filter((i) => !existingIds.has(i.id)) |
| 315 | + items.value = [...newItems, ...items.value] |
| 316 | + if (data.sms.length < PAGE_SIZE) noMoreOlder.value = true |
| 317 | + loadingMore.value = false |
| 318 | + nextTick(() => { |
| 319 | + if (el) { |
| 320 | + el.scrollTop = el.scrollHeight - prevScrollHeight |
| 321 | + } |
| 322 | + }) |
| 323 | + } else { |
| 324 | + detailLoading.value = false |
| 325 | + items.value = data.sms |
| 326 | + if (data.sms.length < PAGE_SIZE) noMoreOlder.value = true |
| 327 | + if (pendingSmsItem.value && data.sms.length > pendingSmsPreCount) { |
| 328 | + pendingSmsItem.value = null |
| 329 | + } |
| 330 | + scrollToBottom() |
302 | 331 | } |
303 | | - scrollToBottom() |
304 | 332 | } |
305 | 333 | }, |
306 | 334 | document: smsGQL, |
307 | 335 | variables: () => ({ |
308 | | - offset: 0, |
309 | | - limit: 5000, |
| 336 | + offset: offset.value, |
| 337 | + limit: PAGE_SIZE, |
310 | 338 | query: buildQuery([{ name: 'thread_id', op: '', value: threadId.value }]), |
311 | 339 | }), |
312 | 340 | }) |
313 | 341 |
|
| 342 | +function fetch() { |
| 343 | + offset.value = 0 |
| 344 | + noMoreOlder.value = false |
| 345 | + loadingMore.value = false |
| 346 | + rawFetch() |
| 347 | +} |
| 348 | +
|
| 349 | +function fetchMore() { |
| 350 | + if (loadingMore.value || noMoreOlder.value || loading.value) return |
| 351 | + loadingMore.value = true |
| 352 | + offset.value = items.value.length |
| 353 | + rawFetch() |
| 354 | +} |
| 355 | +
|
314 | 356 | const { mutate: mutateCall } = initMutation({ |
315 | 357 | document: callGQL, |
316 | 358 | }) |
@@ -646,6 +688,12 @@ onUnmounted(() => { |
646 | 688 | flex: 1; |
647 | 689 | } |
648 | 690 |
|
| 691 | +.chat-loading-more { |
| 692 | + display: flex; |
| 693 | + justify-content: center; |
| 694 | + padding: 12px 0; |
| 695 | +} |
| 696 | +
|
649 | 697 | .chat-date-separator { |
650 | 698 | display: flex; |
651 | 699 | justify-content: center; |
|
0 commit comments