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
23 changes: 16 additions & 7 deletions frontend/src/lib/components/feed/HomePage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { onMount, onDestroy } from 'svelte';
import NavigationDropdown from '$lib/components/NavigationDropdown.svelte';
import EmptyState from '$lib/components/EmptyState.svelte';
import LibraryEmptyState from '$lib/components/LibraryEmptyState.svelte';
import SavedReader from '$lib/components/feed/SavedReader.svelte';
import MobileBottomBar from '$lib/components/feed/MobileBottomBar.svelte';
import MobileFeedSwitcher from '$lib/components/feed/MobileFeedSwitcher.svelte';
Expand All @@ -15,6 +16,7 @@
import HomeLane from '$lib/components/feed/HomeLane.svelte';
import type { LaneCardVM } from '$lib/components/feed/homeLane';
import { savesStore } from '$lib/stores/saves.svelte';
import { subscriptionsStore } from '$lib/stores/subscriptions.svelte';
import { itemLabelsStore } from '$lib/stores/itemLabels.svelte';
import { filteredViewsStore } from '$lib/stores/filteredViews.svelte';
import { viewTitleStore } from '$lib/stores/viewTitle.svelte';
Expand Down Expand Up @@ -328,13 +330,20 @@
<HomeLane title="Continue reading" icon="clock" items={[]} loading onOpen={() => {}} />
<HomeLane title="Random picks" icon="layers" items={[]} loading onOpen={() => {}} />
{:else if !hasAnyLane}
<EmptyState
title="Nothing to read here yet"
description="Save an article and it collects here: your recent reads, a few to pick back up, and a rotating handful from your pile."
actionHref="/feeds"
actionText="Browse your feeds"
icon="📚"
/>
{#if subscriptionsStore.subscriptions.length === 0}
<LibraryEmptyState
onAddFeed={() => sidebarStore.openAddFeedModal()}
onAddHandle={() => sidebarStore.openAddHandleModal()}
/>
{:else}
<EmptyState
title="Nothing to read here yet"
description="Save an article and it collects here: your recent reads, a few to pick back up, and a rotating handful from your pile."
actionHref="/feeds"
actionText="Browse your feeds"
icon="📚"
/>
{/if}
{:else}
{#if continueItems.length > 0}
<HomeLane
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/lib/stores/preferences.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { browser } from '$app/environment';
export type ArticleFont = 'sans-serif' | 'serif' | 'mono' | 'literata';
export type ArticleFontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export type BaseSortOrder = 'newest' | 'oldest';
// Which reading surface the app opens to on a fresh load (the `/` redirector).
export type DefaultView = 'home' | 'feeds' | 'saved';

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

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

const STORAGE_KEY = 'skyreader-preferences';
Expand All @@ -26,6 +30,7 @@ function createPreferencesStore() {
expandAllItems: true,
sortOrder: 'newest',
linkblogShareConfirmed: false,
defaultView: 'home',
});

// Restore from localStorage on init
Expand All @@ -52,6 +57,13 @@ function createPreferencesStore() {
if (parsed.linkblogShareConfirmed !== undefined) {
state.linkblogShareConfirmed = parsed.linkblogShareConfirmed;
}
if (
parsed.defaultView === 'home' ||
parsed.defaultView === 'feeds' ||
parsed.defaultView === 'saved'
) {
state.defaultView = parsed.defaultView;
}
} catch {
localStorage.removeItem(STORAGE_KEY);
}
Expand Down Expand Up @@ -120,6 +132,11 @@ function createPreferencesStore() {
save();
}

function setDefaultView(view: DefaultView) {
state.defaultView = view;
save();
}

return {
get articleFont() {
return state.articleFont;
Expand All @@ -139,7 +156,11 @@ function createPreferencesStore() {
get linkblogShareConfirmed() {
return state.linkblogShareConfirmed;
},
get defaultView() {
return state.defaultView;
},
confirmLinkblogShare,
setDefaultView,
setArticleFont,
setArticleFontSize,
increaseFontSize,
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@
import { goto } from '$app/navigation';
import { auth } from '$lib/stores/auth.svelte';
import { channelPath } from '$lib/utils/viewNav';
import { preferences } from '$lib/stores/preferences.svelte';
import WelcomePage from '$lib/components/feed/WelcomePage.svelte';

const DEFAULT_VIEW_PATH: Record<typeof preferences.defaultView, string> = {
home: '/home',
feeds: '/feeds',
saved: '/saved',
};

function targetFor(url: URL): string {
const sp = new URLSearchParams(url.search);
if (sp.get('saved')) {
Expand All @@ -27,7 +34,8 @@
const view = sp.get('view');
if (view && [...sp].length === 1) return channelPath(view);
if ([...sp].length > 0) return `/feeds${url.search}`;
return '/home';
// No params: land on the reader's chosen default surface.
return DEFAULT_VIEW_PATH[preferences.defaultView];
}

$effect(() => {
Expand Down
47 changes: 47 additions & 0 deletions frontend/src/routes/settings/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
preferences,
type ArticleFont,
type ArticleFontSize,
type DefaultView,
} from '$lib/stores/preferences.svelte';
import ImportOPMLModal from '$lib/components/ImportOPMLModal.svelte';
import SaveBackingPicker from '$lib/components/settings/SaveBackingPicker.svelte';
Expand Down Expand Up @@ -39,6 +40,12 @@
{ value: 'xl', label: 'XL' },
];

const defaultViewOptions: { value: DefaultView; label: string }[] = [
{ value: 'home', label: 'Home' },
{ value: 'feeds', label: 'Feeds' },
{ value: 'saved', label: 'Saved' },
];

let showImportModal = $state(false);

// "Save from anywhere" — browser bookmarklets + a Share Sheet shortcut.
Expand Down Expand Up @@ -611,6 +618,21 @@

<section class="card">
<h2>Reading</h2>
<div class="setting-row">
<label for="default-view">When you open the app</label>
<div class="font-options">
{#each defaultViewOptions as option}
<button
class="view-option"
class:selected={preferences.defaultView === option.value}
onclick={() => preferences.setDefaultView(option.value)}
>
{option.label}
</button>
{/each}
</div>
</div>
<p class="setting-description">Choose which view loads first when you open Skyreader.</p>
<label class="toggle-setting">
<input
type="checkbox"
Expand Down Expand Up @@ -950,6 +972,31 @@
color: var(--color-primary);
}

.view-option {
padding: 0.5rem 1rem;
background: var(--color-bg);
border: 2px solid var(--color-border);
border-radius: 8px;
color: var(--color-text-secondary);
font-size: var(--text-md);
font-weight: var(--weight-medium);
cursor: pointer;
transition:
border-color 0.15s,
background-color 0.15s,
color 0.15s;
}

.view-option:hover {
border-color: var(--color-primary);
}

.view-option.selected {
border-color: var(--color-primary);
background: var(--color-sidebar-active);
color: var(--color-primary);
}

.font-size-option {
display: flex;
flex-direction: column;
Expand Down
Loading