improvement(filters): count Source + Library scope as active; unify Clear all; add search clear (X)
The "Filters N active" badge and Clear all only tracked the sidebar facets (tags/show/sort/content/date/channel), so the toolbar-level Source and the Mine/Library scope neither bumped the count nor reset — inconsistent with filtersToParams (which treats them as filters, so Save view/Share already captured them). - activeCount now includes Library scope and a non-default Source; "relevance" (auto-applied while searching, no manual option) is excluded from the sort count so typing a query no longer inflates the badge. - Clear all resets scope back to "my" (Mine) and drops Source to organic, alongside the facets. The search q is intentionally preserved. - The header search box gets a clear (X) button when it holds a value, so the search has its own reset (clearing it reverts the relevance sort to newest).
This commit is contained in:
parent
53e4cd9aa3
commit
dc31d12fb0
5 changed files with 31 additions and 5 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Library, Search, User, Youtube } from "lucide-react";
|
import { Library, Search, User, X, Youtube } from "lucide-react";
|
||||||
import type { FeedFilters, Me } from "../lib/api";
|
import type { FeedFilters, Me } from "../lib/api";
|
||||||
import type { Page } from "../lib/urlState";
|
import type { Page } from "../lib/urlState";
|
||||||
import SyncStatus from "./SyncStatus";
|
import SyncStatus from "./SyncStatus";
|
||||||
|
|
@ -81,8 +81,18 @@ export default function Header({
|
||||||
if (e.key === "Enter" && trimmedQ && canSearchYt) onYtSearch(trimmedQ);
|
if (e.key === "Enter" && trimmedQ && canSearchYt) onYtSearch(trimmedQ);
|
||||||
}}
|
}}
|
||||||
placeholder={t("header.searchPlaceholder")}
|
placeholder={t("header.searchPlaceholder")}
|
||||||
className="w-full bg-card border border-border rounded-full pl-9 pr-4 py-2 text-sm outline-none focus:border-accent"
|
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 && (
|
||||||
|
<button
|
||||||
|
onClick={() => setFilters({ ...filters, q: "" })}
|
||||||
|
title={t("header.clearSearch")}
|
||||||
|
aria-label={t("header.clearSearch")}
|
||||||
|
className="absolute right-2.5 top-1/2 -translate-y-1/2 p-0.5 rounded-full text-muted hover:text-fg hover:bg-border/60 transition"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{trimmedQ && canSearchYt && (
|
{trimmedQ && canSearchYt && (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -207,13 +207,23 @@ export default function Sidebar({
|
||||||
const dateActive = !!filters.maxAgeDays || !!filters.dateFrom || !!filters.dateTo;
|
const dateActive = !!filters.maxAgeDays || !!filters.dateFrom || !!filters.dateTo;
|
||||||
const contentChanged =
|
const contentChanged =
|
||||||
!filters.includeNormal || filters.includeShorts || filters.includeLive;
|
!filters.includeNormal || filters.includeShorts || filters.includeLive;
|
||||||
|
// Library (vs the default "my"/Mine) and a non-default provenance Source both narrow the feed,
|
||||||
|
// so they count as active filters too (Source only applies in Library scope). The header search
|
||||||
|
// `q` deliberately stays out — it has its own clear (✕) in the search box and isn't reset here.
|
||||||
|
const scopeActive = filters.scope === "all";
|
||||||
|
const sourceActive =
|
||||||
|
filters.scope === "all" && !!filters.librarySource && filters.librarySource !== "organic";
|
||||||
const activeCount =
|
const activeCount =
|
||||||
filters.tags.length +
|
filters.tags.length +
|
||||||
(filters.show !== "unwatched" ? 1 : 0) +
|
(filters.show !== "unwatched" ? 1 : 0) +
|
||||||
(filters.sort !== "newest" ? 1 : 0) +
|
// "relevance" is auto-applied while searching (no manual dropdown option), so it's a search
|
||||||
|
// artifact, not a user-chosen sort filter — don't let typing a query inflate the count.
|
||||||
|
(filters.sort !== "newest" && filters.sort !== "relevance" ? 1 : 0) +
|
||||||
(contentChanged ? 1 : 0) +
|
(contentChanged ? 1 : 0) +
|
||||||
(filters.channelId ? 1 : 0) +
|
(filters.channelId ? 1 : 0) +
|
||||||
(dateActive ? 1 : 0);
|
(dateActive ? 1 : 0) +
|
||||||
|
(scopeActive ? 1 : 0) +
|
||||||
|
(sourceActive ? 1 : 0);
|
||||||
const active = activeCount > 0;
|
const active = activeCount > 0;
|
||||||
|
|
||||||
async function shareView() {
|
async function shareView() {
|
||||||
|
|
@ -227,7 +237,10 @@ export default function Sidebar({
|
||||||
|
|
||||||
function clearAll() {
|
function clearAll() {
|
||||||
setCustomDates(false);
|
setCustomDates(false);
|
||||||
setFilters({ ...DEFAULT_SIDEBAR_FILTERS, q: filters.q, scope: filters.scope });
|
// Reset every filter dimension to its default — including scope back to "my" (Mine) and the
|
||||||
|
// provenance Source (dropped with the rest, so it falls back to "organic"). The search `q` is
|
||||||
|
// intentionally preserved (it has its own ✕ in the search box).
|
||||||
|
setFilters({ ...DEFAULT_SIDEBAR_FILTERS, q: filters.q, scope: "my" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const available: Record<WidgetId, boolean> = {
|
const available: Record<WidgetId, boolean> = {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"feed": "Feed",
|
"feed": "Feed",
|
||||||
"searchPlaceholder": "Deine Abos durchsuchen…",
|
"searchPlaceholder": "Deine Abos durchsuchen…",
|
||||||
|
"clearSearch": "Suche löschen",
|
||||||
"searchYoutube": "Auf YouTube suchen",
|
"searchYoutube": "Auf YouTube suchen",
|
||||||
"searchYoutubeTip": "Live auf YouTube nach diesem Begriff suchen (verbraucht gemeinsames API-Kontingent). Du kannst auch Enter drücken.",
|
"searchYoutubeTip": "Live auf YouTube nach diesem Begriff suchen (verbraucht gemeinsames API-Kontingent). Du kannst auch Enter drücken.",
|
||||||
"channelManager": "Kanalverwaltung",
|
"channelManager": "Kanalverwaltung",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"feed": "Feed",
|
"feed": "Feed",
|
||||||
"searchPlaceholder": "Search your subscriptions…",
|
"searchPlaceholder": "Search your subscriptions…",
|
||||||
|
"clearSearch": "Clear search",
|
||||||
"searchYoutube": "Search YouTube",
|
"searchYoutube": "Search YouTube",
|
||||||
"searchYoutubeTip": "Search live on YouTube for this term (uses shared API quota). You can also press Enter.",
|
"searchYoutubeTip": "Search live on YouTube for this term (uses shared API quota). You can also press Enter.",
|
||||||
"channelManager": "Channel manager",
|
"channelManager": "Channel manager",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"feed": "Hírfolyam",
|
"feed": "Hírfolyam",
|
||||||
"searchPlaceholder": "Keresés a feliratkozásaid között…",
|
"searchPlaceholder": "Keresés a feliratkozásaid között…",
|
||||||
|
"clearSearch": "Keresés törlése",
|
||||||
"searchYoutube": "Keresés a YouTube-on",
|
"searchYoutube": "Keresés a YouTube-on",
|
||||||
"searchYoutubeTip": "Élő keresés a YouTube-on erre a kifejezésre (a közös API-kvótát fogyasztja). Entert is nyomhatsz.",
|
"searchYoutubeTip": "Élő keresés a YouTube-on erre a kifejezésre (a közös API-kvótát fogyasztja). Entert is nyomhatsz.",
|
||||||
"channelManager": "Csatornakezelő",
|
"channelManager": "Csatornakezelő",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue