Added recovery keys
This commit is contained in:
@@ -538,6 +538,7 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
const typingTimeoutRef = useRef(null);
|
||||
const lastTypingEmitRef = useRef(0);
|
||||
const isInitialLoadRef = useRef(true);
|
||||
const initialScrollScheduledRef = useRef(false);
|
||||
const decryptionDoneRef = useRef(false);
|
||||
const channelLoadIdRef = useRef(0);
|
||||
const jumpToMessageIdRef = useRef(null);
|
||||
@@ -559,6 +560,7 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
const prevMessageCountRef = useRef(0);
|
||||
const prevFirstMsgIdRef = useRef(null);
|
||||
const isAtBottomRef = useRef(true);
|
||||
const isLoadingMoreRef = useRef(false);
|
||||
|
||||
const convex = useConvex();
|
||||
|
||||
@@ -576,6 +578,9 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
useEffect(() => {
|
||||
statusRef.current = status;
|
||||
loadMoreRef.current = loadMore;
|
||||
if (status !== 'LoadingMore') {
|
||||
isLoadingMoreRef.current = false;
|
||||
}
|
||||
}, [status, loadMore]);
|
||||
|
||||
const typingData = useQuery(
|
||||
@@ -632,7 +637,21 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
});
|
||||
};
|
||||
|
||||
setDecryptedMessages(buildFromCache());
|
||||
const newMessages = buildFromCache();
|
||||
|
||||
// Adjust firstItemIndex atomically with data to prevent Virtuoso scroll jump
|
||||
const prevCount = prevMessageCountRef.current;
|
||||
const newCount = newMessages.length;
|
||||
if (newCount > prevCount && prevCount > 0) {
|
||||
if (prevFirstMsgIdRef.current && newMessages[0]?.id !== prevFirstMsgIdRef.current) {
|
||||
const prependedCount = newCount - prevCount;
|
||||
setFirstItemIndex(prev => prev - prependedCount);
|
||||
}
|
||||
}
|
||||
prevMessageCountRef.current = newCount;
|
||||
prevFirstMsgIdRef.current = newMessages[0]?.id || null;
|
||||
|
||||
setDecryptedMessages(newMessages);
|
||||
|
||||
// Phase 2: Batch-decrypt only uncached messages in background
|
||||
const processUncached = async () => {
|
||||
@@ -661,7 +680,8 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
decryptionDoneRef.current = true;
|
||||
setDecryptedMessages(buildFromCache());
|
||||
|
||||
if (isInitialLoadRef.current && virtuosoRef.current && !jumpToMessageIdRef.current) {
|
||||
if (isInitialLoadRef.current && !initialScrollScheduledRef.current && virtuosoRef.current && !jumpToMessageIdRef.current) {
|
||||
initialScrollScheduledRef.current = true;
|
||||
const loadId = channelLoadIdRef.current;
|
||||
const scrollEnd = () => { const el = scrollerElRef.current; if (el) el.scrollTop = el.scrollHeight; };
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
@@ -670,6 +690,7 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId && isInitialLoadRef.current) scrollEnd(); }, 300);
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId && isInitialLoadRef.current) scrollEnd(); }, 800);
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId && isInitialLoadRef.current) scrollEnd(); }, 1200);
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId) isInitialLoadRef.current = false; }, 1500);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -786,7 +807,8 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
|
||||
// After decryption, items may be taller — re-scroll to bottom.
|
||||
// Double-rAF waits for paint + ResizeObserver cycle; escalating timeouts are safety nets.
|
||||
if (isInitialLoadRef.current && virtuosoRef.current && !jumpToMessageIdRef.current) {
|
||||
if (isInitialLoadRef.current && !initialScrollScheduledRef.current && virtuosoRef.current && !jumpToMessageIdRef.current) {
|
||||
initialScrollScheduledRef.current = true;
|
||||
const loadId = channelLoadIdRef.current;
|
||||
const scrollEnd = () => { const el = scrollerElRef.current; if (el) el.scrollTop = el.scrollHeight; };
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
@@ -795,6 +817,7 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId && isInitialLoadRef.current) scrollEnd(); }, 300);
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId && isInitialLoadRef.current) scrollEnd(); }, 800);
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId && isInitialLoadRef.current) scrollEnd(); }, 1200);
|
||||
setTimeout(() => { if (channelLoadIdRef.current === loadId) isInitialLoadRef.current = false; }, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -825,6 +848,7 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
channelLoadIdRef.current += 1;
|
||||
setDecryptedMessages([]);
|
||||
isInitialLoadRef.current = true;
|
||||
initialScrollScheduledRef.current = false;
|
||||
decryptionDoneRef.current = false;
|
||||
pingSeededRef.current = false;
|
||||
notifiedMessageIdsRef.current = new Set();
|
||||
@@ -837,6 +861,7 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
setSlashQuery(null);
|
||||
setEphemeralMessages([]);
|
||||
floodAbortRef.current = true;
|
||||
isLoadingMoreRef.current = false;
|
||||
setFirstItemIndex(INITIAL_FIRST_INDEX);
|
||||
prevMessageCountRef.current = 0;
|
||||
prevFirstMsgIdRef.current = null;
|
||||
@@ -1016,24 +1041,13 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
|
||||
// Virtuoso: startReached replaces IntersectionObserver
|
||||
const handleStartReached = useCallback(() => {
|
||||
if (isLoadingMoreRef.current) return;
|
||||
if (statusRef.current === 'CanLoadMore') {
|
||||
isLoadingMoreRef.current = true;
|
||||
loadMoreRef.current(50);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Virtuoso: firstItemIndex management for prepend without jitter
|
||||
useEffect(() => {
|
||||
const prevCount = prevMessageCountRef.current;
|
||||
const newCount = decryptedMessages.length;
|
||||
if (newCount > prevCount && prevCount > 0) {
|
||||
if (prevFirstMsgIdRef.current && decryptedMessages[0]?.id !== prevFirstMsgIdRef.current) {
|
||||
const prependedCount = newCount - prevCount;
|
||||
setFirstItemIndex(prev => prev - prependedCount);
|
||||
}
|
||||
}
|
||||
prevMessageCountRef.current = newCount;
|
||||
prevFirstMsgIdRef.current = decryptedMessages[0]?.id || null;
|
||||
}, [decryptedMessages]);
|
||||
|
||||
// Virtuoso: followOutput auto-scrolls on new messages and handles initial load
|
||||
const followOutput = useCallback((isAtBottom) => {
|
||||
@@ -1068,12 +1082,8 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
if (channelLoadIdRef.current === loadId) {
|
||||
isInitialLoadRef.current = false;
|
||||
}
|
||||
}, 1500);
|
||||
}, 300);
|
||||
}
|
||||
} else if (isInitialLoadRef.current && decryptionDoneRef.current) {
|
||||
// Content resize pushed us off bottom during initial load — snap back
|
||||
const el = scrollerElRef.current;
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
}, [markChannelAsRead]);
|
||||
|
||||
@@ -1668,26 +1678,9 @@ const ChatArea = ({ channelId, channelName, channelType, username, channelKey, u
|
||||
return (
|
||||
<>
|
||||
{status === 'LoadingMore' && (
|
||||
<>
|
||||
{[
|
||||
{ name: 80, lines: [260, 180] },
|
||||
{ name: 60, lines: [310] },
|
||||
{ name: 100, lines: [240, 140] },
|
||||
{ name: 70, lines: [290] },
|
||||
{ name: 90, lines: [200, 260] },
|
||||
{ name: 55, lines: [330] },
|
||||
].map((s, i) => (
|
||||
<div key={i} className="skeleton-message" style={{ animationDelay: `${i * 0.1}s` }}>
|
||||
<div className="skeleton-avatar" />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="skeleton-name" style={{ width: s.name }} />
|
||||
{s.lines.map((w, j) => (
|
||||
<div key={j} className="skeleton-line" style={{ width: w }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '8px 0' }}>
|
||||
<div className="loading-spinner" style={{ width: '20px', height: '20px', borderWidth: '2px' }} />
|
||||
</div>
|
||||
)}
|
||||
{status === 'Exhausted' && (decryptedMessages.length > 0 || rawMessages.length === 0) && (
|
||||
<div className="channel-beginning">
|
||||
|
||||
Reference in New Issue
Block a user