diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4e890b5..ff3dfcf 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -39,6 +39,7 @@ const DEFAULT_FILTERS: FeedFilters = { tagMode: "or", q: "", sort: "newest", + scope: "my", includeNormal: true, includeShorts: false, includeLive: false, diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 09a05cf..7380bb0 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -148,7 +148,9 @@ export default function Feed({ if (query.isLoading) return
{t("feed.loading")}
; if (query.isError) return
{t("feed.loadError")}
; if (items.length === 0) { - if (!canRead) + // No YouTube connection and looking at their own (empty) subscriptions: offer both + // connecting YouTube and just browsing the shared library (no read scope needed). + if (!canRead && filters.scope === "my") return (
@@ -160,6 +162,12 @@ export default function Feed({ > {t("feed.setUp")} +
); diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 7474091..15dc8d0 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { BarChart3, Home, Info, LogOut, Search, Settings, Shield, Tv } from "lucide-react"; +import { BarChart3, Home, Info, Library, LogOut, Search, Settings, Shield, Tv, User } from "lucide-react"; import type { FeedFilters, Me } from "../lib/api"; import type { Page } from "../lib/urlState"; import { type LangCode } from "../i18n"; @@ -49,6 +49,35 @@ export default function Header({ + {page === "feed" && ( +
+ {(["my", "all"] as const).map((s) => ( + + ))} +
+ )} + {page === "feed" ? (
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 6e37c9a..3ab6c97 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -60,8 +60,9 @@ const DATE_PRESETS: { days: number; key: string }[] = [ { days: 365, key: "1year" }, ]; -// Filter values owned by the sidebar (everything except the header search `q`). -const DEFAULT_SIDEBAR_FILTERS: Omit = { +// Filter values owned by the sidebar (everything except the header search `q` and the +// `scope` mode, both preserved across a "Clear all"). +const DEFAULT_SIDEBAR_FILTERS: Omit = { tags: [], tagMode: "or", sort: "newest", @@ -170,7 +171,7 @@ export default function Sidebar({ function clearAll() { setCustomDates(false); - setFilters({ ...DEFAULT_SIDEBAR_FILTERS, q: filters.q }); + setFilters({ ...DEFAULT_SIDEBAR_FILTERS, q: filters.q, scope: filters.scope }); } const available: Record = { diff --git a/frontend/src/i18n/locales/de/feed.json b/frontend/src/i18n/locales/de/feed.json index 07e9185..3a46ab8 100644 --- a/frontend/src/i18n/locales/de/feed.json +++ b/frontend/src/i18n/locales/de/feed.json @@ -4,6 +4,7 @@ "emptyTitle": "Dein Feed ist leer", "emptyBody": "Verbinde dein YouTube-Konto, um deine Abos zu importieren und deinen Feed aufzubauen.", "setUp": "Meinen Feed einrichten", + "browseShared": "Oder einfach die gemeinsame Bibliothek durchsuchen", "noMatches": "Keine Videos entsprechen diesen Filtern.", "videoCount_one": "{{formattedCount}} Video", "videoCount_other": "{{formattedCount}} Videos", diff --git a/frontend/src/i18n/locales/de/header.json b/frontend/src/i18n/locales/de/header.json index 4465368..b634f4f 100644 --- a/frontend/src/i18n/locales/de/header.json +++ b/frontend/src/i18n/locales/de/header.json @@ -3,6 +3,13 @@ "searchPlaceholder": "Deine Abos durchsuchen…", "channelManager": "Kanalverwaltung", "usageStats": "Nutzung & Statistik", + "scope": { + "label": "Feed-Quelle", + "my": "Meine", + "myTip": "Nur Videos aus deinen eigenen Abos", + "all": "Bibliothek", + "allTip": "Alle Videos im gemeinsamen Katalog" + }, "account": { "admin": "Admin", "feed": "Feed", diff --git a/frontend/src/i18n/locales/en/feed.json b/frontend/src/i18n/locales/en/feed.json index 571247c..8e30dda 100644 --- a/frontend/src/i18n/locales/en/feed.json +++ b/frontend/src/i18n/locales/en/feed.json @@ -4,6 +4,7 @@ "emptyTitle": "Your feed is empty", "emptyBody": "Connect your YouTube account to import your subscriptions and build your feed.", "setUp": "Set up my feed", + "browseShared": "Or just browse the shared library", "noMatches": "No videos match these filters.", "videoCount_one": "{{formattedCount}} video", "videoCount_other": "{{formattedCount}} videos", diff --git a/frontend/src/i18n/locales/en/header.json b/frontend/src/i18n/locales/en/header.json index 7cbe22d..d01e782 100644 --- a/frontend/src/i18n/locales/en/header.json +++ b/frontend/src/i18n/locales/en/header.json @@ -3,6 +3,13 @@ "searchPlaceholder": "Search your subscriptions…", "channelManager": "Channel manager", "usageStats": "Usage & stats", + "scope": { + "label": "Feed source", + "my": "Mine", + "myTip": "Only videos from your own subscriptions", + "all": "Library", + "allTip": "Every video in the shared catalog" + }, "account": { "admin": "Admin", "feed": "Feed", diff --git a/frontend/src/i18n/locales/hu/feed.json b/frontend/src/i18n/locales/hu/feed.json index 0c30c5e..2995492 100644 --- a/frontend/src/i18n/locales/hu/feed.json +++ b/frontend/src/i18n/locales/hu/feed.json @@ -4,6 +4,7 @@ "emptyTitle": "A hírfolyamod üres", "emptyBody": "Kösd össze a YouTube-fiókodat, hogy importáld a feliratkozásaidat, és felépítsd a hírfolyamodat.", "setUp": "Hírfolyam beállítása", + "browseShared": "Vagy csak böngészd a közös könyvtárat", "noMatches": "Egy videó sem felel meg ezeknek a szűrőknek.", "videoCount_one": "{{formattedCount}} videó", "videoCount_other": "{{formattedCount}} videó", diff --git a/frontend/src/i18n/locales/hu/header.json b/frontend/src/i18n/locales/hu/header.json index 44dcb04..535fad8 100644 --- a/frontend/src/i18n/locales/hu/header.json +++ b/frontend/src/i18n/locales/hu/header.json @@ -3,6 +3,13 @@ "searchPlaceholder": "Keresés a feliratkozásaid között…", "channelManager": "Csatornakezelő", "usageStats": "Használat és statisztika", + "scope": { + "label": "Hírfolyam forrása", + "my": "Sajátom", + "myTip": "Csak a saját feliratkozásaid videói", + "all": "Könyvtár", + "allTip": "A közös könyvtár összes videója" + }, "account": { "admin": "Adminisztrátor", "feed": "Hírfolyam", diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 95a9c43..e813069 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -76,6 +76,8 @@ export interface FeedFilters { // Re-roll token for the "shuffle" sort; bumped by the reshuffle button so the // feed re-queries with a fresh order. Ignored by every other sort. seed?: number; + // "my" = only your subscriptions (default); "all" = the whole shared catalog. + scope: "my" | "all"; includeNormal: boolean; includeShorts: boolean; includeLive: boolean; @@ -157,6 +159,7 @@ function feedQuery(f: FeedFilters, offset: number, limit: number): string { if (f.q) p.set("q", f.q); p.set("sort", f.sort); if (f.sort === "shuffle" && f.seed) p.set("seed", String(f.seed)); + p.set("scope", f.scope); p.set("show_normal", String(f.includeNormal)); p.set("include_shorts", String(f.includeShorts)); p.set("include_live", String(f.includeLive));