Skip to content

Commit 9db4371

Browse files
committed
add setting to pick default view
1 parent bc907ac commit 9db4371

3 files changed

Lines changed: 77 additions & 1 deletion

File tree

frontend/src/lib/stores/preferences.svelte.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { browser } from '$app/environment';
33
export type ArticleFont = 'sans-serif' | 'serif' | 'mono' | 'literata';
44
export type ArticleFontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
55
export type BaseSortOrder = 'newest' | 'oldest';
6+
// Which reading surface the app opens to on a fresh load (the `/` redirector).
7+
export type DefaultView = 'home' | 'feeds' | 'saved';
68

79
const FONT_SIZE_ORDER: ArticleFontSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
810

@@ -14,6 +16,8 @@ interface PreferencesState {
1416
sortOrder: BaseSortOrder;
1517
// Set once the user has acknowledged the first-share "this is public" confirmation.
1618
linkblogShareConfirmed: boolean;
19+
// Which surface a cold app load lands on (consumed by the `/` redirector).
20+
defaultView: DefaultView;
1721
}
1822

1923
const STORAGE_KEY = 'skyreader-preferences';
@@ -26,6 +30,7 @@ function createPreferencesStore() {
2630
expandAllItems: true,
2731
sortOrder: 'newest',
2832
linkblogShareConfirmed: false,
33+
defaultView: 'home',
2934
});
3035

3136
// Restore from localStorage on init
@@ -52,6 +57,13 @@ function createPreferencesStore() {
5257
if (parsed.linkblogShareConfirmed !== undefined) {
5358
state.linkblogShareConfirmed = parsed.linkblogShareConfirmed;
5459
}
60+
if (
61+
parsed.defaultView === 'home' ||
62+
parsed.defaultView === 'feeds' ||
63+
parsed.defaultView === 'saved'
64+
) {
65+
state.defaultView = parsed.defaultView;
66+
}
5567
} catch {
5668
localStorage.removeItem(STORAGE_KEY);
5769
}
@@ -120,6 +132,11 @@ function createPreferencesStore() {
120132
save();
121133
}
122134

135+
function setDefaultView(view: DefaultView) {
136+
state.defaultView = view;
137+
save();
138+
}
139+
123140
return {
124141
get articleFont() {
125142
return state.articleFont;
@@ -139,7 +156,11 @@ function createPreferencesStore() {
139156
get linkblogShareConfirmed() {
140157
return state.linkblogShareConfirmed;
141158
},
159+
get defaultView() {
160+
return state.defaultView;
161+
},
142162
confirmLinkblogShare,
163+
setDefaultView,
143164
setArticleFont,
144165
setArticleFontSize,
145166
increaseFontSize,

frontend/src/routes/+page.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,15 @@
1212
import { goto } from '$app/navigation';
1313
import { auth } from '$lib/stores/auth.svelte';
1414
import { channelPath } from '$lib/utils/viewNav';
15+
import { preferences } from '$lib/stores/preferences.svelte';
1516
import WelcomePage from '$lib/components/feed/WelcomePage.svelte';
1617
18+
const DEFAULT_VIEW_PATH: Record<typeof preferences.defaultView, string> = {
19+
home: '/home',
20+
feeds: '/feeds',
21+
saved: '/saved',
22+
};
23+
1724
function targetFor(url: URL): string {
1825
const sp = new URLSearchParams(url.search);
1926
if (sp.get('saved')) {
@@ -27,7 +34,8 @@
2734
const view = sp.get('view');
2835
if (view && [...sp].length === 1) return channelPath(view);
2936
if ([...sp].length > 0) return `/feeds${url.search}`;
30-
return '/home';
37+
// No params: land on the reader's chosen default surface.
38+
return DEFAULT_VIEW_PATH[preferences.defaultView];
3139
}
3240
3341
$effect(() => {

frontend/src/routes/settings/+page.svelte

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
preferences,
1010
type ArticleFont,
1111
type ArticleFontSize,
12+
type DefaultView,
1213
} from '$lib/stores/preferences.svelte';
1314
import ImportOPMLModal from '$lib/components/ImportOPMLModal.svelte';
1415
import SaveBackingPicker from '$lib/components/settings/SaveBackingPicker.svelte';
@@ -39,6 +40,12 @@
3940
{ value: 'xl', label: 'XL' },
4041
];
4142
43+
const defaultViewOptions: { value: DefaultView; label: string }[] = [
44+
{ value: 'home', label: 'Home' },
45+
{ value: 'feeds', label: 'Feeds' },
46+
{ value: 'saved', label: 'Saved' },
47+
];
48+
4249
let showImportModal = $state(false);
4350
4451
// "Save from anywhere" — browser bookmarklets + a Share Sheet shortcut.
@@ -611,6 +618,21 @@
611618

612619
<section class="card">
613620
<h2>Reading</h2>
621+
<div class="setting-row">
622+
<label for="default-view">When you open the app</label>
623+
<div class="font-options">
624+
{#each defaultViewOptions as option}
625+
<button
626+
class="view-option"
627+
class:selected={preferences.defaultView === option.value}
628+
onclick={() => preferences.setDefaultView(option.value)}
629+
>
630+
{option.label}
631+
</button>
632+
{/each}
633+
</div>
634+
</div>
635+
<p class="setting-description">Choose which view loads first when you open Skyreader.</p>
614636
<label class="toggle-setting">
615637
<input
616638
type="checkbox"
@@ -950,6 +972,31 @@
950972
color: var(--color-primary);
951973
}
952974
975+
.view-option {
976+
padding: 0.5rem 1rem;
977+
background: var(--color-bg);
978+
border: 2px solid var(--color-border);
979+
border-radius: 8px;
980+
color: var(--color-text-secondary);
981+
font-size: var(--text-md);
982+
font-weight: var(--weight-medium);
983+
cursor: pointer;
984+
transition:
985+
border-color 0.15s,
986+
background-color 0.15s,
987+
color 0.15s;
988+
}
989+
990+
.view-option:hover {
991+
border-color: var(--color-primary);
992+
}
993+
994+
.view-option.selected {
995+
border-color: var(--color-primary);
996+
background: var(--color-sidebar-active);
997+
color: var(--color-primary);
998+
}
999+
9531000
.font-size-option {
9541001
display: flex;
9551002
flex-direction: column;

0 commit comments

Comments
 (0)