fix(chat): scroll to bottom on load and auto-save text style
- Use dedicated flag (hasInitialScrolled) instead of scrollTop===0 check for reliable initial scroll behavior - Add debounced effect to save text style changes to database (1.5s debounce) when user modifies font family, size, style, or color
- commit
- 5a6bd116f0928976d7b10f74c40235748b25c1c3
- author
- Kit · · 2026-01-24 05:40 UTC
- parent
- 27eb0fa
- signature
- SSH Unverified
SHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6TkThe key is unknown
1 file · +48 -9
M src/lib/components/chat-room.svelte +48 -9
| @@ -203,6 +203,7 @@ | |||
| 203 | 203 | let hasMoreMessages = $state(true); | |
| 204 | 204 | let oldestMessageTimestamp = $state<number | null>(null); | |
| 205 | 205 | let lastMessageCount = $state(0); | |
| 206 | + | let hasInitialScrolled = $state(false); | |
| 206 | 207 | ||
| 207 | 208 | // ============ DERIVED STATE ============ | |
| 208 | 209 | ||
| @@ -291,6 +292,27 @@ | |||
| 291 | 292 | } | |
| 292 | 293 | }); | |
| 293 | 294 | ||
| 295 | + | // Save text style to database when it changes (after initial load) | |
| 296 | + | let lastSavedStyleJson = $state<string | null>(null); | |
| 297 | + | $effect(() => { | |
| 298 | + | // Only save if user is logged in and style has been initialized | |
| 299 | + | if (!currentUser || !hasAppliedUserStyle || !browser) return; | |
| 300 | + | ||
| 301 | + | const styleJson = JSON.stringify(currentTextStyle); | |
| 302 | + | ||
| 303 | + | // Skip if this is the initial load (set lastSavedStyleJson to current) | |
| 304 | + | if (lastSavedStyleJson === null) { | |
| 305 | + | lastSavedStyleJson = styleJson; | |
| 306 | + | return; | |
| 307 | + | } | |
| 308 | + | ||
| 309 | + | // Only save if style actually changed | |
| 310 | + | if (styleJson !== lastSavedStyleJson) { | |
| 311 | + | lastSavedStyleJson = styleJson; | |
| 312 | + | debouncedSaveTextStyle(currentTextStyle); | |
| 313 | + | } | |
| 314 | + | }); | |
| 315 | + | ||
| 294 | 316 | // Auto-scroll to bottom when new messages arrive | |
| 295 | 317 | $effect(() => { | |
| 296 | 318 | if (messages.length > lastMessageCount && messages.length > 0) { | |
| @@ -307,15 +329,13 @@ | |||
| 307 | 329 | ||
| 308 | 330 | // Scroll to bottom on initial load (when chatArea becomes available and messages exist) | |
| 309 | 331 | $effect(() => { | |
| 310 | - | if (browser && chatArea && messages.length > 0 && !isInitialLoading) { | |
| 311 | - | // Only run once on initial load by checking if we haven't scrolled yet | |
| 312 | - | if (chatArea.scrollTop === 0 && chatArea.scrollHeight > chatArea.clientHeight) { | |
| 313 | - | tick().then(() => { | |
| 314 | - | if (chatArea) { | |
| 315 | - | chatArea.scrollTop = chatArea.scrollHeight; | |
| 316 | - | } | |
| 317 | - | }); | |
| 318 | - | } | |
| 332 | + | if (browser && chatArea && messages.length > 0 && !isInitialLoading && !hasInitialScrolled) { | |
| 333 | + | hasInitialScrolled = true; | |
| 334 | + | tick().then(() => { | |
| 335 | + | if (chatArea) { | |
| 336 | + | chatArea.scrollTop = chatArea.scrollHeight; | |
| 337 | + | } | |
| 338 | + | }); | |
| 319 | 339 | } | |
| 320 | 340 | }); | |
| 321 | 341 | ||
| @@ -571,6 +591,25 @@ | |||
| 571 | 591 | } | |
| 572 | 592 | }, 1000); | |
| 573 | 593 | ||
| 594 | + | // Debounced function to save text style preferences to the database | |
| 595 | + | const debouncedSaveTextStyle = debounce(async (style: TextStyle) => { | |
| 596 | + | if (!currentUser || !browser) return; | |
| 597 | + | try { | |
| 598 | + | await fetch('/api/user/text-preferences', { | |
| 599 | + | method: 'POST', | |
| 600 | + | headers: { 'Content-Type': 'application/json' }, | |
| 601 | + | credentials: 'include', | |
| 602 | + | body: JSON.stringify({ | |
| 603 | + | defaultStyle: style, | |
| 604 | + | allowFormatting: true, | |
| 605 | + | maxMessageLength: MAX_MESSAGE_LENGTH | |
| 606 | + | }) | |
| 607 | + | }); | |
| 608 | + | } catch (error) { | |
| 609 | + | console.error('Failed to save text style preferences:', error); | |
| 610 | + | } | |
| 611 | + | }, 1500); | |
| 612 | + | ||
| 574 | 613 | function setsEqual(a: Set<string>, b: Set<string>) { | |
| 575 | 614 | if (a.size !== b.size) return false; | |
| 576 | 615 | for (const value of a) { | |
| @@ -203,6 +203,7 @@ | |||||
| 203 | let hasMoreMessages = $state(true); | 203 | let hasMoreMessages = $state(true); | ||
| 204 | let oldestMessageTimestamp = $state<number | null>(null); | 204 | let oldestMessageTimestamp = $state<number | null>(null); | ||
| 205 | let lastMessageCount = $state(0); | 205 | let lastMessageCount = $state(0); | ||
| 206 | + | let hasInitialScrolled = $state(false); | |||
| 206 | 207 | ||||
| 207 | // ============ DERIVED STATE ============ | 208 | // ============ DERIVED STATE ============ | ||
| 208 | 209 | ||||
| @@ -291,6 +292,27 @@ | |||||
| 291 | } | 292 | } | ||
| 292 | }); | 293 | }); | ||
| 293 | 294 | ||||
| 295 | + | // Save text style to database when it changes (after initial load) | |||
| 296 | + | let lastSavedStyleJson = $state<string | null>(null); | |||
| 297 | + | $effect(() => { | |||
| 298 | + | // Only save if user is logged in and style has been initialized | |||
| 299 | + | if (!currentUser || !hasAppliedUserStyle || !browser) return; | |||
| 300 | + | ||||
| 301 | + | const styleJson = JSON.stringify(currentTextStyle); | |||
| 302 | + | ||||
| 303 | + | // Skip if this is the initial load (set lastSavedStyleJson to current) | |||
| 304 | + | if (lastSavedStyleJson === null) { | |||
| 305 | + | lastSavedStyleJson = styleJson; | |||
| 306 | + | return; | |||
| 307 | + | } | |||
| 308 | + | ||||
| 309 | + | // Only save if style actually changed | |||
| 310 | + | if (styleJson !== lastSavedStyleJson) { | |||
| 311 | + | lastSavedStyleJson = styleJson; | |||
| 312 | + | debouncedSaveTextStyle(currentTextStyle); | |||
| 313 | + | } | |||
| 314 | + | }); | |||
| 315 | + | ||||
| 294 | // Auto-scroll to bottom when new messages arrive | 316 | // Auto-scroll to bottom when new messages arrive | ||
| 295 | $effect(() => { | 317 | $effect(() => { | ||
| 296 | if (messages.length > lastMessageCount && messages.length > 0) { | 318 | if (messages.length > lastMessageCount && messages.length > 0) { | ||
| @@ -307,15 +329,13 @@ | |||||
| 307 | 329 | ||||
| 308 | // Scroll to bottom on initial load (when chatArea becomes available and messages exist) | 330 | // Scroll to bottom on initial load (when chatArea becomes available and messages exist) | ||
| 309 | $effect(() => { | 331 | $effect(() => { | ||
| 310 | - | if (browser && chatArea && messages.length > 0 && !isInitialLoading) { | 332 | + | if (browser && chatArea && messages.length > 0 && !isInitialLoading && !hasInitialScrolled) { |
| 311 | - | // Only run once on initial load by checking if we haven't scrolled yet | 333 | + | hasInitialScrolled = true; |
| 312 | - | if (chatArea.scrollTop === 0 && chatArea.scrollHeight > chatArea.clientHeight) { | 334 | + | tick().then(() => { |
| 313 | - | tick().then(() => { | 335 | + | if (chatArea) { |
| 314 | - | if (chatArea) { | 336 | + | chatArea.scrollTop = chatArea.scrollHeight; |
| 315 | - | chatArea.scrollTop = chatArea.scrollHeight; | 337 | + | } |
| 316 | - | } | 338 | + | }); |
| 317 | - | }); | |||
| 318 | - | } | |||
| 319 | } | 339 | } | ||
| 320 | }); | 340 | }); | ||
| 321 | 341 | ||||
| @@ -571,6 +591,25 @@ | |||||
| 571 | } | 591 | } | ||
| 572 | }, 1000); | 592 | }, 1000); | ||
| 573 | 593 | ||||
| 594 | + | // Debounced function to save text style preferences to the database | |||
| 595 | + | const debouncedSaveTextStyle = debounce(async (style: TextStyle) => { | |||
| 596 | + | if (!currentUser || !browser) return; | |||
| 597 | + | try { | |||
| 598 | + | await fetch('/api/user/text-preferences', { | |||
| 599 | + | method: 'POST', | |||
| 600 | + | headers: { 'Content-Type': 'application/json' }, | |||
| 601 | + | credentials: 'include', | |||
| 602 | + | body: JSON.stringify({ | |||
| 603 | + | defaultStyle: style, | |||
| 604 | + | allowFormatting: true, | |||
| 605 | + | maxMessageLength: MAX_MESSAGE_LENGTH | |||
| 606 | + | }) | |||
| 607 | + | }); | |||
| 608 | + | } catch (error) { | |||
| 609 | + | console.error('Failed to save text style preferences:', error); | |||
| 610 | + | } | |||
| 611 | + | }, 1500); | |||
| 612 | + | ||||
| 574 | function setsEqual(a: Set<string>, b: Set<string>) { | 613 | function setsEqual(a: Set<string>, b: Set<string>) { | ||
| 575 | if (a.size !== b.size) return false; | 614 | if (a.size !== b.size) return false; | ||
| 576 | for (const value of a) { | 615 | for (const value of a) { | ||