Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions frontend/src/lib/components/feed/FeedListView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@
}

export function openSelectedReader() {
const index = feedViewStore.selectedIndex;
if (index < 0) return;
const item = feedViewStore.currentItems[index];
const key = feedViewStore.selectedKey;
if (key === null) return;
const item = feedViewStore.currentItems.find((i) => i.key === key);
if (item) {
openReader(item);
}
Expand All @@ -86,7 +86,8 @@
}

async function handleExpand(index: number) {
if (feedViewStore.expandedIndex === index) {
const key = feedViewStore.currentItems[index]?.key ?? null;
if (key !== null && feedViewStore.expandedKey === key) {
feedViewStore.collapse();
} else {
feedViewStore.select(index);
Expand All @@ -97,7 +98,8 @@
}

function handleSelect(index: number) {
if (feedViewStore.selectedIndex === index) {
const key = feedViewStore.currentItems[index]?.key ?? null;
if (key !== null && feedViewStore.selectedKey === key) {
feedViewStore.deselect();
} else {
feedViewStore.select(index);
Expand Down Expand Up @@ -225,9 +227,9 @@
isSaved={itemLabelsStore.isSaved(article.guid)}
isShared={sharesStore.isShared(article.guid)}
shareNote={sharesStore.getShareNote(article.guid)}
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
expanded={feedViewStore.expandedIndex === index}
highlighted={feedViewStore.selectedIndex === index}
selected={preferences.expandAllItems || feedViewStore.selectedKey === displayItem.key}
expanded={feedViewStore.expandedKey === displayItem.key}
highlighted={feedViewStore.selectedKey === displayItem.key}
onToggleSave={() => onToggleSave(article)}
onToggleRead={() => handleToggleRead(article)}
onShare={() => sub && onShare(article, sub)}
Expand All @@ -250,9 +252,9 @@
{localArticle}
isRead={itemLabelsStore.isSocialRead(share.recordUri)}
isSaved={itemLabelsStore.isSaved(share.recordUri)}
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
expanded={feedViewStore.expandedIndex === index}
highlighted={feedViewStore.selectedIndex === index}
selected={preferences.expandAllItems || feedViewStore.selectedKey === displayItem.key}
expanded={feedViewStore.expandedKey === displayItem.key}
highlighted={feedViewStore.selectedKey === displayItem.key}
onToggleSave={() =>
itemLabelsStore.toggleSave(share.recordUri, 'share', share.itemUrl, share.itemTitle, {
type: 'share',
Expand Down Expand Up @@ -306,9 +308,9 @@
isShared={true}
shareNote={share.note}
reshareCount={share.reshareCount || 0}
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
expanded={feedViewStore.expandedIndex === index}
highlighted={feedViewStore.selectedIndex === index}
selected={preferences.expandAllItems || feedViewStore.selectedKey === displayItem.key}
expanded={feedViewStore.expandedKey === displayItem.key}
highlighted={feedViewStore.selectedKey === displayItem.key}
onToggleSave={() => onToggleSave(article)}
onToggleRead={() => handleToggleRead(article)}
onUnshare={() => onUnshare(share.articleGuid)}
Expand All @@ -328,9 +330,9 @@
document={doc}
isRead={itemLabelsStore.isSocialRead(doc.recordUri)}
isSaved={itemLabelsStore.isSaved(doc.recordUri)}
selected={preferences.expandAllItems || feedViewStore.selectedIndex === index}
expanded={feedViewStore.expandedIndex === index}
highlighted={feedViewStore.selectedIndex === index}
selected={preferences.expandAllItems || feedViewStore.selectedKey === displayItem.key}
expanded={feedViewStore.expandedKey === displayItem.key}
highlighted={feedViewStore.selectedKey === displayItem.key}
onToggleSave={() =>
itemLabelsStore.toggleSave(
doc.recordUri,
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/lib/components/feed/SavedListView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,9 @@
}

export function openSelectedReader() {
const index = feedViewStore.selectedIndex;
if (index < 0) return;
const item = feedViewStore.currentItems[index];
const key = feedViewStore.selectedKey;
if (key === null) return;
const item = feedViewStore.currentItems.find((i) => i.key === key);
if (item) {
openReader(item);
}
Expand Down Expand Up @@ -314,7 +314,7 @@
<div bind:this={articleElements[index]}>
<SavedCard
{displayItem}
selected={feedViewStore.selectedIndex === index}
selected={feedViewStore.selectedKey === displayItem.key}
onOpen={() => openReader(displayItem)}
onHover={() => handleSelect(index)}
onArchive={() => handleArchive(displayItem)}
Expand Down
66 changes: 29 additions & 37 deletions frontend/src/lib/hooks/useFeedKeyboardShortcuts.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,20 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {
return subscriptionsStore.subscriptions.find((s) => s.id === article.subscriptionId);
}

// Helper to resolve the currently-selected item by key. Returns null when
// nothing is selected or the previously-selected item is no longer present.
function getSelectedItem(): FeedDisplayItem | null {
const key = feedViewStore.selectedKey;
if (key === null) return null;
return feedViewStore.currentItems.find((i) => i.key === key) ?? null;
}

// Helper to get selected article info
function getSelectedArticle(): {
article: Article;
sub: Subscription;
} | null {
const selectedIndex = feedViewStore.selectedIndex;
if (selectedIndex < 0) return null;

const items = feedViewStore.currentItems;
const item = items[selectedIndex];
const item = getSelectedItem();
if (!item) return null;

const article = getArticleFromItem(item);
Expand Down Expand Up @@ -77,11 +81,7 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {

// Open selected item in new tab
function openSelectedItem() {
const selectedIndex = feedViewStore.selectedIndex;
if (selectedIndex < 0) return;

const items = feedViewStore.currentItems;
const item = items[selectedIndex];
const item = getSelectedItem();
if (!item) return;

let url: string;
Expand All @@ -103,11 +103,7 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {

// Toggle save on selected item (works for all item types)
function toggleSelectedSave() {
const selectedIndex = feedViewStore.selectedIndex;
if (selectedIndex < 0) return;

const items = feedViewStore.currentItems;
const item = items[selectedIndex];
const item = getSelectedItem();
if (!item) return;

if (item.type === 'article') {
Expand Down Expand Up @@ -198,11 +194,7 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {

// Toggle read/unread on selected item
function toggleSelectedRead() {
const selectedIndex = feedViewStore.selectedIndex;
if (selectedIndex < 0) return;

const items = feedViewStore.currentItems;
const item = items[selectedIndex];
const item = getSelectedItem();
if (!item) return;

if (item.type === 'article' || item.type === 'userShare') {
Expand Down Expand Up @@ -249,10 +241,12 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {
// Navigation actions
async function selectNextItem() {
const currentItems = feedViewStore.currentItems;
const selectedIndex = feedViewStore.selectedIndex;
if (currentItems.length === 0) return;

const nextIndex = Math.min(selectedIndex + 1, currentItems.length - 1);
const selectedKey = feedViewStore.selectedKey;
const currentIndex =
selectedKey === null ? -1 : currentItems.findIndex((i) => i.key === selectedKey);
const nextIndex = Math.min(currentIndex + 1, currentItems.length - 1);
feedViewStore.select(nextIndex);

// If we're at the last item, try to load more
Expand All @@ -266,10 +260,12 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {

async function selectPreviousItem() {
const currentItems = feedViewStore.currentItems;
const selectedIndex = feedViewStore.selectedIndex;
if (currentItems.length === 0) return;

feedViewStore.select(Math.max(selectedIndex - 1, 0));
const selectedKey = feedViewStore.selectedKey;
const currentIndex =
selectedKey === null ? -1 : currentItems.findIndex((i) => i.key === selectedKey);
feedViewStore.select(Math.max(currentIndex - 1, 0));

await tick();
params.scrollToCenter();
Expand All @@ -280,24 +276,24 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {
}

function hasSelected() {
return auth.isAuthenticated && feedViewStore.selectedIndex >= 0;
return auth.isAuthenticated && feedViewStore.selectedKey !== null;
}

// Toggle expand action (or open bookmark reader in bookmarks view)
async function toggleExpand() {
const selectedIndex = feedViewStore.selectedIndex;
if (selectedIndex < 0) return;
const selectedKey = feedViewStore.selectedKey;
if (selectedKey === null) return;

if (feedViewStore.savedFilter && params.openSavedReader) {
params.openSavedReader();
return;
}

const expandedIndex = feedViewStore.expandedIndex;
if (expandedIndex === selectedIndex) {
if (feedViewStore.expandedKey === selectedKey) {
feedViewStore.collapse();
} else {
feedViewStore.expand(selectedIndex);
const idx = feedViewStore.currentItems.findIndex((i) => i.key === selectedKey);
if (idx >= 0) feedViewStore.expand(idx);
}
await tick();
params.scrollToCenter();
Expand Down Expand Up @@ -368,9 +364,7 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {
description: 'Tag item',
category: 'Article',
action: () => {
const idx = feedViewStore.selectedIndex;
if (idx < 0) return;
const item = feedViewStore.currentItems[idx];
const item = getSelectedItem();
if (!item) return;
if (feedViewStore.tagMenuItemKey === item.key) {
feedViewStore.closeTagMenu();
Expand Down Expand Up @@ -415,9 +409,7 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {
description: 'Archive/unarchive saved item',
category: 'Article',
action: () => {
const idx = feedViewStore.selectedIndex;
if (idx < 0) return;
const item = feedViewStore.currentItems[idx];
const item = getSelectedItem();
if (!item) return;
const itemType = item.type === 'userShare' ? 'userShare' : item.type;
itemLabelsStore.toggleArchive(
Expand All @@ -434,7 +426,7 @@ export function useFeedKeyboardShortcuts(params: KeyboardShortcutsParams) {
description: 'Toggle highlight on paragraph',
category: 'Article',
action: () => params.toggleHighlight?.(),
condition: () => hasSelected() && feedViewStore.expandedIndex >= 0,
condition: () => hasSelected() && feedViewStore.expandedKey !== null,
});

// Full-screen reader for feed items (not in bookmarks view, which uses Enter)
Expand Down
52 changes: 35 additions & 17 deletions frontend/src/lib/stores/feedView.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,11 @@ export function matchesReadingLength(wc: number | null, bucket: ReadingLengthFil
function createFeedViewStore() {
// UI state
let showOnlyUnread = $state(true);
let selectedIndex = $state(-1);
let expandedIndex = $state(-1);
// Track selection/expansion by item key (stable across refreshes) rather than
// by array index — list refreshes re-sort items so an index would point at a
// different item after a refresh.
let selectedKey = $state<string | null>(null);
let expandedKey = $state<string | null>(null);
let loadedArticleCount = $state(DEFAULT_PAGE_SIZE);

// Tag menu state (which item key should show the tag menu, null = closed)
Expand Down Expand Up @@ -1056,16 +1059,20 @@ function createFeedViewStore() {
}
}

function select(index: number) {
if (index === selectedIndex) return;
function selectByKey(key: string | null) {
if (key === selectedKey) return;

const items = currentItems;
const item = items[index];
if (key === null) {
selectedKey = null;
expandedKey = null;
return;
}

const item = currentItems.find((i) => i.key === key);
if (!item) return;

// Set selectedIndex first
selectedIndex = index;
expandedIndex = -1;
selectedKey = key;
expandedKey = null;

// Track the item to keep it visible in unread filter for this session
if (item.type === 'article') {
Expand Down Expand Up @@ -1112,23 +1119,27 @@ function createFeedViewStore() {
// userShare items don't auto-mark as read
}

function select(index: number) {
selectByKey(currentItems[index]?.key ?? null);
}

function deselect() {
selectedIndex = -1;
expandedIndex = -1;
selectedKey = null;
expandedKey = null;
// Don't clear session sets - items should stay visible until view changes
}

function expand(index: number) {
expandedIndex = index;
expandedKey = currentItems[index]?.key ?? null;
}

function collapse() {
expandedIndex = -1;
expandedKey = null;
}

function resetSelection() {
selectedIndex = -1;
expandedIndex = -1;
selectedKey = null;
expandedKey = null;
// Clear session sets when switching views/feeds
readArticleGuidsThisSession = new Set();
readShareUrisThisSession = new Set();
Expand Down Expand Up @@ -1248,11 +1259,17 @@ function createFeedViewStore() {
get currentItems() {
return currentItems;
},
get selectedKey() {
return selectedKey;
},
get expandedKey() {
return expandedKey;
},
get selectedIndex() {
return selectedIndex;
return selectedKey === null ? -1 : currentItems.findIndex((i) => i.key === selectedKey);
},
get expandedIndex() {
return expandedIndex;
return expandedKey === null ? -1 : currentItems.findIndex((i) => i.key === expandedKey);
},
get showOnlyUnread() {
return showOnlyUnread;
Expand Down Expand Up @@ -1365,6 +1382,7 @@ function createFeedViewStore() {
loadArticles,
loadMore,
select,
selectByKey,
deselect,
expand,
collapse,
Expand Down
Loading