feat: implement scroll position restoration for mobile tabs and add double-tap home navigation to refresh feed views.
This commit is contained in:
+73
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user