|
35 | 35 | export let onPreviewImage; |
36 | 36 | export let startLoadingImage; // Fix: Add missing prop |
37 | 37 | export let isLoading = false; |
| 38 | + export let previewImage; // Fix: Add missing prop |
| 39 | + export let onJumpToMessage = null; // Fix: Add missing prop |
38 | 40 |
|
39 | 41 | let textarea; |
40 | 42 | let touchStartX = 0; |
|
91 | 93 | let containerRef; |
92 | 94 |
|
93 | 95 | let isLoadingMore = false; |
94 | | - async function handleScroll(e) { |
95 | | - const container = e.target; |
96 | | - const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight; |
97 | | - showScrollButton = distanceToBottom > 50; |
98 | 96 |
|
99 | | - // Load more when reaching top |
100 | | - if (container.scrollTop < 100 && canLoadMore && !isLoadingMore && onLoadMore) { |
101 | | - isLoadingMore = true; |
102 | | - const oldHeight = container.scrollHeight; |
103 | | - await onLoadMore(); |
104 | | - await tick(); |
105 | | - // Maintain scroll position relative to bottom |
106 | | - const newHeight = container.scrollHeight; |
107 | | - container.scrollTop += (newHeight - oldHeight); |
108 | | - isLoadingMore = false; |
109 | | - } |
110 | | - } |
111 | 97 |
|
112 | 98 | function scrollToBottom(force = false) { |
113 | 99 | // Guard against unnecessary scrolls |
|
152 | 138 | }); |
153 | 139 |
|
154 | 140 | // Handle Contact Change & Initialize Loading |
155 | | - $: if (isLoading || (selectedContact && selectedContact.ID !== currentContactId)) { |
156 | | - if (selectedContact) currentContactId = selectedContact.ID; |
157 | | - // Reset state for new chat or loading start |
| 141 | + let lastMessageId = null; |
| 142 | +
|
| 143 | + $: if (selectedContact && selectedContact.ID !== currentContactId) { |
| 144 | + currentContactId = selectedContact.ID; |
| 145 | + // Reset state ONLY for new chat |
158 | 146 | chatReady = false; |
159 | 147 | initialScrollDone = false; |
160 | 148 | imagesLoading = true; |
161 | 149 | pendingImages = 0; |
162 | 150 | loadedImages = 0; |
163 | | - |
164 | | - // Safety timeout |
165 | | - if (isLoading) { |
166 | | - // If legitimate loading, we rely on isLoading becoming false |
167 | | - } else { |
168 | | - // Fallback if not loading but contact changed (shouldn't happen with new App logic) |
169 | | - setTimeout(() => { |
170 | | - if (!chatReady && !isLoading) { |
171 | | - chatReady = true; |
172 | | - imagesLoading = false; |
173 | | - scrollToBottom(true); |
174 | | - } |
175 | | - }, 3000); |
176 | | - } |
| 151 | + lastMessageId = null; |
177 | 152 | } |
178 | 153 |
|
179 | 154 | // Handle Messages Update & Auto-scroll |
180 | | - $: if (!isLoading && messages && currentContactId && containerRef) { |
181 | | - // Logic when data is ready |
182 | | - |
| 155 | + $: if (messages && currentContactId && containerRef) { |
183 | 156 | if (!chatReady) { |
184 | | - // Check for images in new messages |
185 | | - const images = messages.flatMap(m => m.Attachments || []).filter(a => a.MimeType && a.MimeType.startsWith('image/')); |
| 157 | + // Initial Load Logic |
| 158 | + const images = (messages || []).flatMap(m => m.Attachments || []).filter(a => a.MimeType && a.MimeType.startsWith('image/')); |
186 | 159 | |
187 | 160 | if (images.length > 0) { |
188 | | - // Check if we already counted them (to avoid reset loop) |
189 | 161 | if (pendingImages === 0) { |
190 | 162 | pendingImages = images.length; |
191 | 163 | loadedImages = 0; |
192 | | - // Wait for onImageLoad |
193 | 164 | } |
194 | | - // If all images loaded (rare case of cache) |
| 165 | + // If all images loaded (rare case of cache) or no pending |
195 | 166 | if (loadedImages >= pendingImages) { |
196 | 167 | finishLoading(); |
197 | 168 | } |
|
201 | 172 | } |
202 | 173 | } else { |
203 | 174 | // Already ready, handle normal new message scroll |
204 | | - const distanceToBottom = containerRef.scrollHeight - containerRef.scrollTop - containerRef.clientHeight; |
205 | | - const wasNearBottom = distanceToBottom < 100; |
206 | | - const isUserSender = messages.length > 0 && messages[messages.length-1].IsOutgoing; |
207 | | -
|
208 | | - if (wasNearBottom || isUserSender) { |
209 | | - tick().then(() => { |
210 | | - scrollToBottom(true); |
211 | | - }); |
| 175 | + const currentLastMsg = messages.length > 0 ? messages[messages.length-1] : null; |
| 176 | + const isNewMessage = currentLastMsg && currentLastMsg.ID !== lastMessageId; |
| 177 | + |
| 178 | + if (isNewMessage) { |
| 179 | + lastMessageId = currentLastMsg.ID; |
| 180 | + |
| 181 | + const distanceToBottom = containerRef.scrollHeight - containerRef.scrollTop - containerRef.clientHeight; |
| 182 | + const wasNearBottom = distanceToBottom < 100; |
| 183 | + const isUserSender = currentLastMsg.IsOutgoing; |
| 184 | +
|
| 185 | + if (wasNearBottom || isUserSender) { |
| 186 | + tick().then(() => { |
| 187 | + scrollToBottom(true); |
| 188 | + }); |
| 189 | + } |
212 | 190 | } |
213 | 191 | } |
214 | 192 | } |
215 | 193 |
|
216 | | - function onImageLoad() { |
| 194 | + function onImageLoad(e) { |
| 195 | + // If image is already complete (cached), handle immediately |
| 196 | + if (e && e.target && e.target.complete) { |
| 197 | + // Logic handled below |
| 198 | + } |
| 199 | +
|
217 | 200 | if (chatReady) { |
218 | 201 | // Smart scroll after image load if near bottom |
219 | 202 | if (containerRef) { |
220 | 203 | const dist = containerRef.scrollHeight - containerRef.scrollTop - containerRef.clientHeight; |
221 | | - if (dist < 100) scrollToBottom(true); |
| 204 | + if (dist < 150) scrollToBottom(true); |
222 | 205 | } |
223 | 206 | return; |
224 | 207 | } |
225 | 208 | loadedImages++; |
226 | 209 | if (loadedImages >= pendingImages) { |
227 | | - chatReady = true; |
228 | | - imagesLoading = false; |
229 | | - scrollToBottom(true); |
| 210 | + finishLoading(); |
230 | 211 | } |
231 | 212 | } |
232 | 213 |
|
233 | | - // Auto-scroll on new messages |
234 | | - $: if (messages && messages.length > 0 && chatReady) { |
235 | | - scrollToBottom(); |
236 | | - } |
237 | | -
|
238 | 214 | function finishLoading() { |
239 | 215 | if (chatReady) return; |
240 | | - chatReady = true; |
241 | | - imagesLoading = false; |
| 216 | + |
| 217 | + // 1. Force scroll to bottom WHILE INVISIBLE |
242 | 218 | scrollToBottom(true); |
| 219 | + |
| 220 | + // 2. Make visible after brief delay to allow layout to settle |
| 221 | + requestAnimationFrame(() => { |
| 222 | + chatReady = true; |
| 223 | + imagesLoading = false; |
| 224 | + |
| 225 | + if (messages && messages.length > 0) { |
| 226 | + lastMessageId = messages[messages.length-1].ID; |
| 227 | + } |
| 228 | + |
| 229 | + // 3. Force scroll again just in case opacity change affected anything |
| 230 | + setTimeout(() => scrollToBottom(true), 10); |
| 231 | + }); |
| 232 | + } |
| 233 | + |
| 234 | + // ... |
| 235 | + |
| 236 | + async function handleScroll(e) { |
| 237 | + if (!chatReady) return; // CRITICAL: Don't load history if initial scroll isn't done |
| 238 | + |
| 239 | + const container = e.target; |
| 240 | + const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight; |
| 241 | + showScrollButton = distanceToBottom > 50; |
| 242 | + |
| 243 | + // ... rest of logic |
| 244 | + if (container.scrollTop < 100 && canLoadMore && !isLoadingMore && onLoadMore) { |
| 245 | + // ... |
| 246 | + isLoadingMore = true; |
| 247 | + const oldHeight = container.scrollHeight; |
| 248 | + await onLoadMore(); |
| 249 | + await tick(); |
| 250 | + const newHeight = container.scrollHeight; |
| 251 | + container.scrollTop += (newHeight - oldHeight); |
| 252 | + isLoadingMore = false; |
| 253 | + } |
243 | 254 | } |
244 | 255 |
|
245 | 256 | </script> |
|
0 commit comments