feat(plex): P1.5 — Plex as a nav module + integrated search + filter sidebar
Per user feedback: promote Plex from a hidden feed-Source option to a first-class left-nav module, with its own filter sidebar and the shared top search box. - Plex is now a nav-rail module (page='plex', gated on me.plex_enabled) — its own page → correct browser Back/Forward; removed the Feed Source-dropdown 'plex' hack. - PlexSidebar.tsx: left filter column (library scope, watch-state for movies: all/unwatched/in_progress/watched, sort) — per-account persisted (App state). - Header search box now also serves the Plex page (integrated search); the live YouTube-search escalation stays feed-only. - PlexBrowse.tsx reworked: infinite scroll (IntersectionObserver, no 'Load more'), content-visibility for smoother long-list scroll, show drill-down rides history (useHistorySubview) so Back returns to the grid; dropped the stray 'YouTube feed' button on the show page. - backend /browse gains a per-user watch-state filter (show=, movies only). - plex i18n (navLabel + filter.*) en/hu/de. Note: episode-title 'Episode N' on some shows (e.g. Westworld) is Plex-side (unmatched show) — we mirror what Plex has; Match/Refresh in Plex + re-sync fixes it.
This commit is contained in:
parent
62636319b1
commit
219a935121
14 changed files with 290 additions and 133 deletions
|
|
@ -26,10 +26,14 @@ export default function Header({
|
|||
const { t } = useTranslation();
|
||||
const canSearchYt = !me.is_demo;
|
||||
const trimmedQ = filters.q.trim();
|
||||
// The search box serves both the YouTube feed and the Plex module (integrated search); the live
|
||||
// YouTube-search escalation (Enter / button) is feed-only.
|
||||
const isSearchPage = page === "feed" || page === "plex";
|
||||
const isYtCapable = page === "feed" && canSearchYt;
|
||||
|
||||
return (
|
||||
<header className="glass h-14 shrink-0 border-b border-border flex items-center gap-3 px-4 z-20">
|
||||
{page === "feed" ? (
|
||||
{isSearchPage ? (
|
||||
<div className="flex-1 max-w-xl mx-auto flex items-center gap-2">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted" />
|
||||
|
|
@ -40,15 +44,16 @@ export default function Header({
|
|||
// When a search first appears, rank the feed by relevance — set atomically with
|
||||
// the query (race-free vs. per-keystroke updates). Only overrides the default
|
||||
// "newest" sort; a custom sort the user chose is left alone. Cleared in Feed.
|
||||
const startSearch = !filters.q.trim() && !!q.trim() && filters.sort === "newest";
|
||||
const startSearch =
|
||||
page === "feed" && !filters.q.trim() && !!q.trim() && filters.sort === "newest";
|
||||
setFilters({ ...filters, q, ...(startSearch ? { sort: "relevance" } : {}) });
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// Enter runs a live YouTube search for the typed term (the box itself filters
|
||||
// the local catalog as you type; Enter escalates to the YouTube API).
|
||||
if (e.key === "Enter" && trimmedQ && canSearchYt) onYtSearch(trimmedQ);
|
||||
// On the feed, Enter escalates the typed term to a live YouTube search (the box
|
||||
// itself filters the local catalog / Plex library as you type).
|
||||
if (e.key === "Enter" && trimmedQ && isYtCapable) onYtSearch(trimmedQ);
|
||||
}}
|
||||
placeholder={t("header.searchPlaceholder")}
|
||||
placeholder={page === "plex" ? t("plex.searchPlaceholder") : t("header.searchPlaceholder")}
|
||||
className="w-full bg-card border border-border rounded-full pl-9 pr-9 py-2 text-sm outline-none focus:border-accent"
|
||||
/>
|
||||
{filters.q && (
|
||||
|
|
@ -62,7 +67,7 @@ export default function Header({
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
{trimmedQ && canSearchYt && (
|
||||
{trimmedQ && isYtCapable && (
|
||||
<button
|
||||
onClick={() => onYtSearch(trimmedQ)}
|
||||
title={t("header.searchYoutubeTip")}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue