Skip to content

Commit 5b95048

Browse files
committed
add sort option on feeds #69
1 parent 8b4aebd commit 5b95048

File tree

4 files changed

+90
-18
lines changed

4 files changed

+90
-18
lines changed

src/lib/components/Icon.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
| 'list'
2323
| 'newspaper'
2424
| 'layers'
25-
| 'external-link';
25+
| 'external-link'
26+
| 'arrow-down'
27+
| 'arrow-up';
2628
2729
interface Props {
2830
name: IconName;
@@ -132,6 +134,12 @@
132134
<path d="M15 3h6v6" />
133135
<path d="M10 14 21 3" />
134136
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h6" />
137+
{:else if name === 'arrow-down'}
138+
<path d="M12 5v14" />
139+
<path d="m19 12-7 7-7-7" />
140+
{:else if name === 'arrow-up'}
141+
<path d="M12 19V5" />
142+
<path d="m5 12 7-7 7 7" />
135143
{/if}
136144
</svg>
137145

src/lib/components/feed/FeedPageHeader.svelte

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import Icon from '$lib/components/Icon.svelte';
66
import { sidebarStore } from '$lib/stores/sidebar.svelte';
77
import { formatRelativeTime } from '$lib/utils/date';
8+
import { preferences } from '$lib/stores/preferences.svelte';
89
910
interface Props {
1011
title: string;
@@ -155,6 +156,21 @@
155156
<span class="toggle-divider"></span>
156157
{/if}
157158
{#if showViewToggle}
159+
<button
160+
class="sort-toggle"
161+
onclick={() => preferences.toggleSortOrder()}
162+
aria-label={preferences.sortOrder === 'newest'
163+
? 'Sorted by newest first, click to sort by oldest'
164+
: 'Sorted by oldest first, click to sort by newest'}
165+
title={preferences.sortOrder === 'newest' ? 'Newest first' : 'Oldest first'}
166+
>
167+
<Icon
168+
name={preferences.sortOrder === 'newest' ? 'arrow-down' : 'arrow-up'}
169+
size={16}
170+
/>
171+
<span class="btn-label">{preferences.sortOrder === 'newest' ? 'New' : 'Old'}</span>
172+
</button>
173+
<span class="toggle-divider"></span>
158174
<button
159175
class:active={showOnlyUnread}
160176
onclick={() => onToggleUnread(true)}

src/lib/stores/feedView.svelte.ts

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { readingStore } from './reading.svelte';
44
import { shareReadingStore } from './shareReading.svelte';
55
import { sharesStore } from './shares.svelte';
66
import { socialStore } from './social.svelte';
7+
import { preferences } from './preferences.svelte';
78
import type { Article, SocialShare, CombinedFeedItem, UserShare } from '$lib/types';
89

910
export type ViewMode = 'articles' | 'shares' | 'userShares' | 'combined';
@@ -57,34 +58,44 @@ function createFeedViewStore() {
5758
// Access articlesStore version for reactivity
5859
const allArticles = articlesStore.allArticles;
5960
const positions = readingStore.readPositions;
61+
const sortOrder = preferences.sortOrder;
62+
63+
let articles: Article[];
6064

6165
if (starredFilter) {
6266
// Starred view
63-
return allArticles.filter((a) => positions.get(a.guid)?.starred === true);
64-
}
65-
66-
let articles = allArticles;
67+
articles = allArticles.filter((a) => positions.get(a.guid)?.starred === true);
68+
} else {
69+
articles = allArticles;
6770

68-
// Filter by subscription
69-
if (feedFilter) {
70-
const feedId = parseInt(feedFilter);
71-
articles = articles.filter((a) => a.subscriptionId === feedId);
72-
}
71+
// Filter by subscription
72+
if (feedFilter) {
73+
const feedId = parseInt(feedFilter);
74+
articles = articles.filter((a) => a.subscriptionId === feedId);
75+
}
7376

74-
// Filter to unread only, but keep articles read this session visible
75-
if (showOnlyUnread) {
76-
articles = articles.filter(
77-
(a) => !positions.has(a.guid) || readArticleGuidsThisSession.has(a.guid)
78-
);
77+
// Filter to unread only, but keep articles read this session visible
78+
if (showOnlyUnread) {
79+
articles = articles.filter(
80+
(a) => !positions.has(a.guid) || readArticleGuidsThisSession.has(a.guid)
81+
);
82+
}
7983
}
8084

8185
// Deduplicate by GUID
8286
const seen = new Set<string>();
83-
return articles.filter((a) => {
87+
articles = articles.filter((a) => {
8488
if (seen.has(a.guid)) return false;
8589
seen.add(a.guid);
8690
return true;
8791
});
92+
93+
// Apply sort order (articles come from liveDb sorted newest first)
94+
if (sortOrder === 'oldest') {
95+
articles = [...articles].reverse();
96+
}
97+
98+
return articles;
8899
});
89100

90101
// Derived: paginated articles (limited to loadedArticleCount)
@@ -94,6 +105,7 @@ function createFeedViewStore() {
94105
let displayedShares = $derived.by((): SocialShare[] => {
95106
const shares = socialStore.shares;
96107
const positions = shareReadingStore.shareReadPositions;
108+
const sortOrder = preferences.sortOrder;
97109

98110
let filtered: SocialShare[];
99111
if (sharerFilter) {
@@ -109,15 +121,26 @@ function createFeedViewStore() {
109121
);
110122
}
111123

124+
// Apply sort order
125+
filtered.sort((a, b) => {
126+
const dateA = new Date(a.itemPublishedAt || a.createdAt).getTime();
127+
const dateB = new Date(b.itemPublishedAt || b.createdAt).getTime();
128+
return sortOrder === 'newest' ? dateB - dateA : dateA - dateB;
129+
});
130+
112131
return filtered;
113132
});
114133

115134
// Derived: user's own shares
116135
let displayedUserShares = $derived.by((): UserShare[] => {
117136
if (!sharedFilter) return [];
118137

138+
const sortOrder = preferences.sortOrder;
119139
const shares = Array.from(sharesStore.userShares.values());
120-
shares.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
140+
shares.sort((a, b) => {
141+
const diff = new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
142+
return sortOrder === 'newest' ? diff : -diff;
143+
});
121144
return shares;
122145
});
123146

@@ -138,7 +161,11 @@ function createFeedViewStore() {
138161
})),
139162
];
140163

141-
combined.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
164+
const sortOrder = preferences.sortOrder;
165+
combined.sort((a, b) => {
166+
const diff = new Date(b.date).getTime() - new Date(a.date).getTime();
167+
return sortOrder === 'newest' ? diff : -diff;
168+
});
142169
return combined;
143170
});
144171

src/lib/stores/preferences.svelte.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { browser } from '$app/environment';
22

33
export type ArticleFont = 'sans-serif' | 'serif' | 'mono';
44
export type ArticleFontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
5+
export type SortOrder = 'newest' | 'oldest';
56

67
const FONT_SIZE_ORDER: ArticleFontSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
78

@@ -10,6 +11,7 @@ interface PreferencesState {
1011
articleFontSize: ArticleFontSize;
1112
scrollToMarkAsRead: boolean;
1213
expandAllItems: boolean;
14+
sortOrder: SortOrder;
1315
}
1416

1517
const STORAGE_KEY = 'skyreader-preferences';
@@ -20,6 +22,7 @@ function createPreferencesStore() {
2022
articleFontSize: 'md',
2123
scrollToMarkAsRead: false,
2224
expandAllItems: false,
25+
sortOrder: 'newest',
2326
});
2427

2528
// Restore from localStorage on init
@@ -40,6 +43,9 @@ function createPreferencesStore() {
4043
if (parsed.expandAllItems !== undefined) {
4144
state.expandAllItems = parsed.expandAllItems;
4245
}
46+
if (parsed.sortOrder) {
47+
state.sortOrder = parsed.sortOrder;
48+
}
4349
} catch {
4450
localStorage.removeItem(STORAGE_KEY);
4551
}
@@ -93,6 +99,16 @@ function createPreferencesStore() {
9399
save();
94100
}
95101

102+
function setSortOrder(order: SortOrder) {
103+
state.sortOrder = order;
104+
save();
105+
}
106+
107+
function toggleSortOrder() {
108+
state.sortOrder = state.sortOrder === 'newest' ? 'oldest' : 'newest';
109+
save();
110+
}
111+
96112
return {
97113
get articleFont() {
98114
return state.articleFont;
@@ -106,13 +122,18 @@ function createPreferencesStore() {
106122
get expandAllItems() {
107123
return state.expandAllItems;
108124
},
125+
get sortOrder() {
126+
return state.sortOrder;
127+
},
109128
setArticleFont,
110129
setArticleFontSize,
111130
increaseFontSize,
112131
decreaseFontSize,
113132
resetFontSize,
114133
setScrollToMarkAsRead,
115134
setExpandAllItems,
135+
setSortOrder,
136+
toggleSortOrder,
116137
};
117138
}
118139

0 commit comments

Comments
 (0)