feat: implement scroll position restoration for mobile tabs and add double-tap home navigation to refresh feed views.

This commit is contained in:
2026-08-29 15:02:56 -07:00
parent cd98c0120e
commit 271a542196
18 changed files with 1446 additions and 240 deletions
+73 -2
View File
@@ -113,6 +113,15 @@ export const App: React.FC = () => {
try {
const user = await api.getMe();
setCurrentUser(user);
if (user.preferences) {
if (user.preferences.auto_read !== undefined) {
localStorage.setItem('rsspipe_auto_read_scroll', user.preferences.auto_read ? 'true' : 'false');
}
if (user.preferences.view_preference) {
setViewPreference(user.preferences.view_preference);
localStorage.setItem('rsspipe_view_preference', user.preferences.view_preference);
}
}
} catch {
localStorage.removeItem('rsspipe_jwt');
} finally {
@@ -125,7 +134,37 @@ export const App: React.FC = () => {
setCurrentUser(null);
};
window.addEventListener('auth:unauthorized', handleUnauthorized);
return () => window.removeEventListener('auth:unauthorized', handleUnauthorized);
const syncPreferences = async () => {
const token = localStorage.getItem('rsspipe_jwt');
if (!token) return;
try {
const prefs = await api.getUserPreferences();
if (prefs) {
if (prefs.auto_read !== undefined) {
localStorage.setItem('rsspipe_auto_read_scroll', prefs.auto_read ? 'true' : 'false');
}
if (prefs.view_preference) {
setViewPreference(prefs.view_preference);
localStorage.setItem('rsspipe_view_preference', prefs.view_preference);
}
}
} catch {
// ignore
}
};
window.addEventListener('focus', syncPreferences);
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') syncPreferences();
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
window.removeEventListener('auth:unauthorized', handleUnauthorized);
window.removeEventListener('focus', syncPreferences);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
// Load sidebar data (Categories, Feeds, Stats)
@@ -145,6 +184,9 @@ export const App: React.FC = () => {
}
}, [currentUser]);
const [totalEntries, setTotalEntries] = useState(0);
const [isLoadingMore, setIsLoadingMore] = useState(false);
// Load entries matching filter
const loadEntries = useCallback(async () => {
if (!currentUser) return;
@@ -154,9 +196,11 @@ export const App: React.FC = () => {
feed_id: filter.feedId,
category_id: filter.categoryId,
search: filter.search || undefined,
limit: 200,
limit: 50,
offset: 0,
});
setEntries(res.entries || []);
setTotalEntries(res.total || 0);
if (res.entries && res.entries.length > 0) {
setSelectedEntryId((prev) => {
@@ -173,6 +217,30 @@ export const App: React.FC = () => {
}
}, [currentUser, filter]);
// Load more entries for infinite scrolling
const handleLoadMore = async () => {
if (isLoadingMore || entries.length >= totalEntries || !currentUser) return;
setIsLoadingMore(true);
try {
const res = await api.listEntries({
view: filter.view,
feed_id: filter.feedId,
category_id: filter.categoryId,
search: filter.search || undefined,
limit: 50,
offset: entries.length,
});
if (res.entries && res.entries.length > 0) {
setEntries((prev) => [...prev, ...res.entries]);
}
setTotalEntries(res.total || 0);
} catch (err) {
console.error('Failed to load more entries:', err);
} finally {
setIsLoadingMore(false);
}
};
useEffect(() => {
if (currentUser) {
loadSidebarData();
@@ -464,6 +532,9 @@ export const App: React.FC = () => {
viewPreference={viewPreference}
onChangeViewPreference={handleChangeViewPreference}
onSwitchToDesktop={() => setViewMode('split')}
hasMore={entries.length < totalEntries}
isLoadingMore={isLoadingMore}
onLoadMore={handleLoadMore}
/>
) : (
<SettingsView