feat(feed): a card's menu filters the feed by its channel, and back

Under "Open the channel" a video card's menu (the right-click on its
channel name included) adds the channel to the feed's channel filter, or,
when it is already in, takes it out. Channels OR together, so it widens a
filter that names others, as "Show in feed" does. Not on a channel page,
whose view is that channel already, nor in a live YouTube search or on Home.
This commit is contained in:
2026-10-09 00:44:46 +02:00
parent ece74a42ab
commit 4f076b5d45
6 changed files with 98 additions and 4 deletions
+26 -1
View File
@@ -8,6 +8,8 @@ import {
Download,
Eye,
EyeOff,
Filter,
FilterX,
Info,
ListPlus,
Play,
@@ -39,6 +41,9 @@ export interface VideoActionHandlers {
onResetState?: (id: string) => void;
/** Omitted where the host menu leads with its own Play row (Home's shelf card resolves `next`). */
onOpen?: (v: Video, startAt?: number | null) => void;
/** The feed's channel filter, where the menu can narrow it: the library feed. Omitted on a channel
* page (the view is that channel already), in a live YouTube search and on Home. */
channelFilter?: { ids: string[]; set: (ids: string[]) => void };
}
const icon = "w-4 h-4";
@@ -65,7 +70,7 @@ export function useVideoActions(handlers: VideoActionHandlers, open: boolean) {
anchor: Element | null,
{ pin: withPin = true }: { pin?: boolean } = {},
): ActionMenuItem[] => {
const { onState, onToggleSave, onResetState, onOpen } = handlers;
const { onState, onToggleSave, onResetState, onOpen, channelFilter } = handlers;
const watched = v.status === "watched";
const hidden = v.status === "hidden";
const inProgress = v.position_seconds > 0 && !watched;
@@ -112,6 +117,26 @@ export function useVideoActions(handlers: VideoActionHandlers, open: boolean) {
icon: <Tv className={icon} />,
onSelect: () => openChannel(v.channel_id, v.channel_title ?? t("card.thisChannel")),
},
// The channel's videos in this feed, or the channel out of the filter again: channels OR
// together, so adding one widens a filter that already names others (as "Show in feed" does).
...(channelFilter
? [
channelFilter.ids.includes(v.channel_id)
? {
id: "channel-filter",
label: t("card.menu.channelFilterRemove"),
icon: <FilterX className={icon} />,
onSelect: () =>
channelFilter.set(channelFilter.ids.filter((id) => id !== v.channel_id)),
}
: {
id: "channel-filter",
label: t("card.menu.channelFilterAdd"),
icon: <Filter className={icon} />,
onSelect: () => channelFilter.set([...channelFilter.ids, v.channel_id]),
},
]
: []),
{
id: "watched",
label: watched ? t("card.menu.unmarkWatched") : t("card.markWatched"),