feat(search): Library provenance filter (organic / all / search-only)
Replace the binary 'show search-discovered' toggle with a 3-way Source selector in the Library toolbar, so users can also see ONLY search-discovered videos — not just hide or mix them. Backend: feed param library_source = organic (default, hides via_search) | all (both) | search (only via_search), applied in scope=all. Strings in HU/EN/DE.
This commit is contained in:
parent
546be57963
commit
8b19faaed1
6 changed files with 57 additions and 33 deletions
|
|
@ -124,7 +124,7 @@ def _filtered_query(
|
|||
include_live: bool,
|
||||
show: str,
|
||||
scope: str = "my",
|
||||
exclude_search_discovered: bool = True,
|
||||
library_source: str = "organic",
|
||||
exclude_tag_category: str | None = None,
|
||||
) -> tuple[Select, object]:
|
||||
"""Build the feed query (joins + all WHERE filters), shared by /feed and /feed/count.
|
||||
|
|
@ -172,11 +172,16 @@ def _filtered_query(
|
|||
# either way they shouldn't show in any feed view meanwhile.
|
||||
query = query.where(Video.unavailable_since.is_(None))
|
||||
|
||||
# Hide videos that only entered the catalog via a live YouTube search (search-discovered
|
||||
# "noise") from the Library by default. Only meaningful in "all" scope: "my" already
|
||||
# restricts to subscribed channels, where a once-searched video is legitimately the user's.
|
||||
if scope == "all" and exclude_search_discovered:
|
||||
query = query.where(Video.via_search.is_(False))
|
||||
# Provenance filter for the Library: search-discovered videos are "noise" hidden by
|
||||
# default ("organic"), can be mixed in ("all"), or shown exclusively ("search"). Only
|
||||
# meaningful in "all" scope: "my" already restricts to subscribed channels, where a
|
||||
# once-searched video is legitimately the user's.
|
||||
if scope == "all":
|
||||
if library_source == "organic":
|
||||
query = query.where(Video.via_search.is_(False))
|
||||
elif library_source == "search":
|
||||
query = query.where(Video.via_search.is_(True))
|
||||
# "all" → both organic and search-discovered videos
|
||||
|
||||
if channel_id:
|
||||
query = query.where(Video.channel_id == channel_id)
|
||||
|
|
@ -278,7 +283,7 @@ def _feed_params(
|
|||
include_live: bool = False,
|
||||
show: str = "unwatched",
|
||||
scope: str = "my",
|
||||
exclude_search_discovered: bool = True,
|
||||
library_source: str = "organic",
|
||||
) -> dict:
|
||||
return {
|
||||
"tags": tags,
|
||||
|
|
@ -295,7 +300,7 @@ def _feed_params(
|
|||
"include_live": include_live,
|
||||
"show": show,
|
||||
"scope": scope,
|
||||
"exclude_search_discovered": exclude_search_discovered,
|
||||
"library_source": library_source,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -417,20 +417,24 @@ export default function Feed({
|
|||
{filters.scope === "all" && (
|
||||
<>
|
||||
<span className="mx-1 h-5 w-px bg-border" aria-hidden="true" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setFilters({ ...filters, showSearchDiscovered: !filters.showSearchDiscovered })
|
||||
}
|
||||
aria-pressed={!!filters.showSearchDiscovered}
|
||||
title={t("feed.searchDiscoveredTip")}
|
||||
className={`text-xs px-3 py-1.5 rounded-full border transition ${
|
||||
filters.showSearchDiscovered
|
||||
? "bg-accent text-accent-fg border-accent"
|
||||
: "border-border text-muted hover:text-fg hover:border-accent"
|
||||
}`}
|
||||
>
|
||||
{t("feed.searchDiscovered")}
|
||||
</button>
|
||||
<label className="inline-flex items-center gap-1.5 text-xs text-muted">
|
||||
<span>{t("feed.source.label")}</span>
|
||||
<select
|
||||
value={filters.librarySource ?? "organic"}
|
||||
onChange={(e) =>
|
||||
setFilters({
|
||||
...filters,
|
||||
librarySource: e.target.value as FeedFilters["librarySource"],
|
||||
})
|
||||
}
|
||||
title={t("feed.source.tip")}
|
||||
className="bg-card border border-border rounded-lg px-2 py-1 text-xs outline-none focus:border-accent"
|
||||
>
|
||||
<option value="organic">{t("feed.source.organic")}</option>
|
||||
<option value="all">{t("feed.source.all")}</option>
|
||||
<option value="search">{t("feed.source.only")}</option>
|
||||
</select>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,8 +30,13 @@
|
|||
"markedWatchedNamed": "Als angesehen markiert: „{{title}}”",
|
||||
"markedWatched": "Als angesehen markiert",
|
||||
"unwatch": "Nicht mehr als angesehen",
|
||||
"searchDiscovered": "Über Suche gefunden",
|
||||
"searchDiscoveredTip": "Auch Videos anzeigen, die nur über eine Live-YouTube-Suche in die Bibliothek kamen.",
|
||||
"source": {
|
||||
"label": "Quelle",
|
||||
"tip": "Bibliothek danach filtern, wie Videos hierher kamen: nur organischer Katalog, mit Suchergebnissen, oder nur über Live-YouTube-Suche gefundene Videos.",
|
||||
"organic": "Ohne Suchergebnisse",
|
||||
"all": "Mit Suchergebnissen",
|
||||
"only": "Nur Suchergebnisse"
|
||||
},
|
||||
"yt": {
|
||||
"searchFor": "Auf YouTube suchen nach „{{query}}”",
|
||||
"resultsFor": "YouTube-Ergebnisse für „{{query}}”",
|
||||
|
|
|
|||
|
|
@ -30,8 +30,13 @@
|
|||
"markedWatchedNamed": "Marked watched “{{title}}”",
|
||||
"markedWatched": "Marked watched",
|
||||
"unwatch": "Unwatch",
|
||||
"searchDiscovered": "Search-discovered",
|
||||
"searchDiscoveredTip": "Also show videos that entered the library only through a live YouTube search.",
|
||||
"source": {
|
||||
"label": "Source",
|
||||
"tip": "Filter the Library by how videos got here: organic catalog only, plus search results, or only videos found via live YouTube search.",
|
||||
"organic": "Hide search results",
|
||||
"all": "Include search results",
|
||||
"only": "Search results only"
|
||||
},
|
||||
"yt": {
|
||||
"searchFor": "Search YouTube for “{{query}}”",
|
||||
"resultsFor": "YouTube results for “{{query}}”",
|
||||
|
|
|
|||
|
|
@ -30,8 +30,13 @@
|
|||
"markedWatchedNamed": "Megnézettnek jelölve: „{{title}}”",
|
||||
"markedWatched": "Megnézettnek jelölve",
|
||||
"unwatch": "Megnézés visszavonása",
|
||||
"searchDiscovered": "Keresésből talált",
|
||||
"searchDiscoveredTip": "Mutasd azokat a videókat is, amelyek csak élő YouTube-keresésen át kerültek a könyvtárba.",
|
||||
"source": {
|
||||
"label": "Forrás",
|
||||
"tip": "Szűrd a könyvtárat aszerint, hogyan kerültek be a videók: csak organikus katalógus, kereséssel együtt, vagy csak az élő YouTube-keresésből talált videók.",
|
||||
"organic": "Keresés nélkül",
|
||||
"all": "Kereséssel együtt",
|
||||
"only": "Csak keresésből"
|
||||
},
|
||||
"yt": {
|
||||
"searchFor": "Keresés a YouTube-on: „{{query}}”",
|
||||
"resultsFor": "YouTube találatok erre: „{{query}}”",
|
||||
|
|
|
|||
|
|
@ -141,9 +141,9 @@ export interface FeedFilters {
|
|||
includeShorts: boolean;
|
||||
includeLive: boolean;
|
||||
show: string;
|
||||
// Library (scope "all") only: when set, also show videos that entered the catalog via a
|
||||
// live YouTube search. Default (falsy) hides that search-discovered "noise".
|
||||
showSearchDiscovered?: boolean;
|
||||
// Library (scope "all") only — provenance filter: "organic" (default) hides search-
|
||||
// discovered videos, "all" mixes them in, "search" shows only them. Ignored for "my".
|
||||
librarySource?: "organic" | "all" | "search";
|
||||
channelId?: string;
|
||||
channelName?: string;
|
||||
maxAgeDays?: number;
|
||||
|
|
@ -309,8 +309,8 @@ function filterParams(f: FeedFilters): URLSearchParams {
|
|||
if (f.dateTo) p.set("published_before", f.dateTo);
|
||||
if (f.minDuration != null) p.set("min_duration", String(f.minDuration));
|
||||
if (f.maxDuration != null) p.set("max_duration", String(f.maxDuration));
|
||||
// Only relevant in Library scope; the backend ignores it for "my". Default = hide.
|
||||
p.set("exclude_search_discovered", String(!f.showSearchDiscovered));
|
||||
// Only relevant in Library scope; the backend ignores it for "my". Default = hide search.
|
||||
p.set("library_source", f.librarySource ?? "organic");
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue