diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 0223735..13ac40a 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -23,9 +23,11 @@ const rollSeed = () => Math.floor(Math.random() * 1_000_000_000); // Ordering = a key + direction (like the Playlists page), mapped to the backend sort strings // (which encode both). One entry per concept; a single arrow flips the direction. -type SortKey = "date" | "popular" | "duration" | "title" | "subscribers" | "priority" | "shuffle"; +// "relevance" (full-text search ranking) and "shuffle" are directionless single-string sorts. +type SortKey = "date" | "popular" | "duration" | "title" | "subscribers" | "priority" | "shuffle" | "relevance"; const SORT_KEYS: SortKey[] = ["date", "popular", "duration", "title", "subscribers", "priority", "shuffle"]; -const SORT_MAP: Record, { asc: string; desc: string }> = { +const DIRECTIONLESS: SortKey[] = ["shuffle", "relevance"]; +const SORT_MAP: Record, { asc: string; desc: string }> = { date: { desc: "newest", asc: "oldest" }, popular: { desc: "views", asc: "views_asc" }, duration: { desc: "duration_desc", asc: "duration_asc" }, @@ -33,11 +35,11 @@ const SORT_MAP: Record, { asc: string; desc: string subscribers: { desc: "subscribers", asc: "subscribers_asc" }, priority: { desc: "priority", asc: "priority_asc" }, }; -const SORT_DEFAULT_DIR: Record, "asc" | "desc"> = { +const SORT_DEFAULT_DIR: Record, "asc" | "desc"> = { date: "desc", popular: "desc", duration: "desc", title: "asc", subscribers: "desc", priority: "desc", }; function parseSort(s: string): { key: SortKey; dir: "asc" | "desc" } { - if (s === "shuffle") return { key: "shuffle", dir: "desc" }; + if (s === "shuffle" || s === "relevance") return { key: s, dir: "desc" }; for (const k of Object.keys(SORT_MAP) as (keyof typeof SORT_MAP)[]) { if (SORT_MAP[k].asc === s) return { key: k, dir: "asc" }; if (SORT_MAP[k].desc === s) return { key: k, dir: "desc" }; @@ -45,7 +47,8 @@ function parseSort(s: string): { key: SortKey; dir: "asc" | "desc" } { return { key: "date", dir: "desc" }; } function buildSort(key: SortKey, dir: "asc" | "desc"): string { - return key === "shuffle" ? "shuffle" : SORT_MAP[key][dir]; + if (key === "shuffle" || key === "relevance") return key; + return SORT_MAP[key][dir]; } function matchesView(status: string, show: string): boolean { @@ -134,6 +137,22 @@ export default function Feed({ retry: false, }); + // When a search term first appears, rank the local feed by relevance (YouTube-like); when + // it's cleared, fall back to the default newest sort. Only fires on the empty↔non-empty + // transition, so it never overrides a sort the user picks while a query stays active. + const qEmpty = !filters.q.trim(); + const prevQEmptyRef = useRef(qEmpty); + useEffect(() => { + const sortKeyNow = parseSort(filters.sort).key; + if (prevQEmptyRef.current && !qEmpty && sortKeyNow !== "relevance") { + setFilters({ ...filters, sort: "relevance" }); + } else if (!prevQEmptyRef.current && qEmpty && sortKeyNow === "relevance") { + setFilters({ ...filters, sort: buildSort("date", "desc") }); + } + prevQEmptyRef.current = qEmpty; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [qEmpty]); + // Drop optimistic status overrides when filters change OR fresh server data // arrives — after a refetch the server is authoritative, so a stale override // (e.g. a video reverted to "new" from the notification center) won't keep it @@ -296,6 +315,9 @@ export default function Feed({ // The search just ingested new catalog videos, so the normal feed (which was // disabled and still holds its pre-search cache) is stale. Drop those caches so // it re-fetches fresh on return instead of showing the old (often empty) result. + // Surface the just-searched videos in the feed you return to: switch the Source + // filter to "search" so your own search finds show (filtered by the kept term). + setFilters({ ...filters, librarySource: "search" }); onExitYtSearch(); qc.removeQueries({ queryKey: ["feed"] }); qc.removeQueries({ queryKey: ["feed-count"] }); @@ -445,7 +467,7 @@ export default function Feed({ ); })} - {filters.scope === "all" && ( + {( <>