feat(tags): tag UX overhaul + per-card video reset
- Channel-row tags show only what's attached; a '+' opens a per-channel picker. Clicking a tag filters the feed by it; a 'Your tags' sidebar widget makes that filter visible/clearable. - Tag manager dialog (add/rename/delete; delete only confirms when the tag is in use), reachable from the Channel manager and the feed sidebar; hovering a tag's count lists its channels, each a link that focuses it in the Channel manager (DataTable gains an external filter input). - Video cards get a reset action that clears all watch state (incl. an in-progress position). - api.req() now raises the error dialog on 5xx and 400/409/422 with the server's reason.
This commit is contained in:
parent
b55a944e7c
commit
1e530d23a6
22 changed files with 445 additions and 49 deletions
|
|
@ -36,6 +36,7 @@ import {
|
|||
} from "../lib/sidebarLayout";
|
||||
import { shareUrl } from "../lib/urlState";
|
||||
import { notify } from "../lib/notifications";
|
||||
import TagManager from "./TagManager";
|
||||
|
||||
// Filter ids; display labels resolved at render time via t("sidebar.sort.<id>") etc.
|
||||
const SORT_IDS = [
|
||||
|
|
@ -114,11 +115,13 @@ export default function Sidebar({
|
|||
setFilters,
|
||||
layout,
|
||||
setLayout,
|
||||
onFocusChannel,
|
||||
}: {
|
||||
filters: FeedFilters;
|
||||
setFilters: (f: FeedFilters) => void;
|
||||
layout: SidebarLayout;
|
||||
setLayout: (l: SidebarLayout) => void;
|
||||
onFocusChannel: (name: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const tagsQuery = useQuery({ queryKey: ["tags"], queryFn: api.tags });
|
||||
|
|
@ -172,7 +175,11 @@ export default function Sidebar({
|
|||
: t("sidebar.thisChannel"));
|
||||
const languages = tags.filter((t) => t.category === "language");
|
||||
const topics = tags.filter((t) => t.category === "topic");
|
||||
// The user's own (non-system) tags — those assigned to channels in the Channel manager.
|
||||
// Facets only cover language/topic, so these show their static channel_count.
|
||||
const userTags = tags.filter((t) => !t.system);
|
||||
const [customDates, setCustomDates] = useState(false);
|
||||
const [tagManagerOpen, setTagManagerOpen] = useState(false);
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const sensors = useSensors(
|
||||
|
|
@ -217,6 +224,7 @@ export default function Sidebar({
|
|||
date: true,
|
||||
language: languages.length > 0,
|
||||
topic: topics.length > 0,
|
||||
tags: userTags.length > 0,
|
||||
};
|
||||
|
||||
function toggleCollapse(id: WidgetId) {
|
||||
|
|
@ -393,6 +401,28 @@ export default function Sidebar({
|
|||
</>
|
||||
);
|
||||
}
|
||||
case "tags":
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{userTags.map((tg) => (
|
||||
<TagChip
|
||||
key={tg.id}
|
||||
tag={tg}
|
||||
count={tg.channel_count}
|
||||
active={filters.tags.includes(tg.id)}
|
||||
onClick={() => toggleTag(tg.id)}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
onClick={() => setTagManagerOpen(true)}
|
||||
title={t("sidebar.manageTags")}
|
||||
className="inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full border border-dashed border-border text-muted hover:text-accent hover:border-accent transition"
|
||||
>
|
||||
<Pencil className="w-3 h-3" />
|
||||
{t("sidebar.manageTags")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -496,6 +526,9 @@ export default function Sidebar({
|
|||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
{tagManagerOpen && (
|
||||
<TagManager onClose={() => setTagManagerOpen(false)} onFocusChannel={onFocusChannel} />
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue