|
61 | 61 | if (!existingIds.has(msg.chat_message_id)) { |
62 | 62 | messages = [...messages, msg]; |
63 | 63 | scrollToBottom(); |
| 64 | + // If mouse is in the messages area, mark as read |
| 65 | + if (mouseInMessages) markAsReadIfNeeded(); |
64 | 66 | } |
65 | 67 | } |
66 | 68 |
|
|
281 | 283 | onMount(() => { |
282 | 284 | connectSSE(); |
283 | 285 | loadReactions(); |
284 | | - // Mark room as read when entering |
285 | | - fetch(`/api/chat/${data.chatRoom.chat_room_id}/read-marker`, { method: 'PUT' }); |
| 286 | + // Mark as read on initial load (mouse is likely already in the area) |
| 287 | + markAsReadIfNeeded(); |
| 288 | + window.addEventListener('focus', handleWindowFocus); |
286 | 289 | }); |
287 | 290 |
|
288 | 291 | onDestroy(() => { |
289 | 292 | eventSource?.close(); |
290 | 293 | if (pollInterval) clearInterval(pollInterval); |
| 294 | + if (readMarkerTimeout) clearTimeout(readMarkerTimeout); |
| 295 | + window.removeEventListener('focus', handleWindowFocus); |
291 | 296 | }); |
292 | 297 |
|
293 | 298 | async function sendMessage(event: SubmitEvent) { |
|
482 | 487 | return segments.length > 0 ? segments : [{ type: 'text', text: content }]; |
483 | 488 | } |
484 | 489 |
|
| 490 | + // --- Read marker logic --- |
| 491 | + // Track when we last marked the room as read, to avoid redundant PUTs. |
| 492 | + let lastMarkedReadAt: string = $state(''); |
| 493 | + let readMarkerTimeout: ReturnType<typeof setTimeout> | null = null; |
| 494 | + let mouseInMessages = $state(false); |
| 495 | +
|
| 496 | + function markAsReadIfNeeded() { |
| 497 | + if (!latestTimestamp || latestTimestamp === lastMarkedReadAt) return; |
| 498 | + if (!document.hasFocus()) return; |
| 499 | +
|
| 500 | + // Debounce: wait 1s of inactivity before sending |
| 501 | + if (readMarkerTimeout) clearTimeout(readMarkerTimeout); |
| 502 | + readMarkerTimeout = setTimeout(() => { |
| 503 | + lastMarkedReadAt = latestTimestamp; |
| 504 | + fetch(`/api/chat/${data.chatRoom.chat_room_id}/read-marker`, { method: 'PUT' }); |
| 505 | + }, 1000); |
| 506 | + } |
| 507 | +
|
| 508 | + function handleMessagesMouseEnter() { |
| 509 | + mouseInMessages = true; |
| 510 | + markAsReadIfNeeded(); |
| 511 | + } |
| 512 | +
|
| 513 | + function handleMessagesMouseLeave() { |
| 514 | + mouseInMessages = false; |
| 515 | + } |
| 516 | +
|
| 517 | + function handleWindowFocus() { |
| 518 | + if (mouseInMessages) { |
| 519 | + markAsReadIfNeeded(); |
| 520 | + } |
| 521 | + } |
| 522 | +
|
485 | 523 | // Close emoji picker when clicking outside |
486 | 524 | function handleWindowClick(event: MouseEvent) { |
487 | 525 | const target = event.target as HTMLElement; |
|
584 | 622 | class="mb-4 space-y-3 rounded-lg border border-surface-300-600 bg-surface-50-900 p-4" |
585 | 623 | style="min-height: 300px; max-height: 60vh; overflow-y: auto;" |
586 | 624 | data-testid="messages-container" |
| 625 | + onmouseenter={handleMessagesMouseEnter} |
| 626 | + onmouseleave={handleMessagesMouseLeave} |
587 | 627 | > |
588 | 628 | {#if messages.length > 0} |
589 | 629 | {#each messages as message (message.chat_message_id)} |
|
0 commit comments