feat(search): ephemeral results UX, count selector, channel blocklist (frontend)
- Live-search view: a results-count selector (20/40/60/100) replaces manual load-more (the free scrape source pages until that many are gathered); an 'these results are temporary' banner with a 'Clear now' button that discards them 'as if never added' (api.clearSearch) and returns to the feed. - Channel blocklist: a Block/Unblock toggle + 'Blocked' badge on the channel page (blocked channels don't auto-explore and their videos are hidden), and a 'Blocked channels' section in the Channel manager with one-click unblock. ChannelDetail.blocked from the backend. - Admin: a 'Purge discovery' button on the Scheduler page (immediate un-kept search/explore cleanup). EN/HU/DE throughout.
This commit is contained in:
parent
f27f31b6db
commit
0c18845c34
21 changed files with 263 additions and 65 deletions
|
|
@ -96,6 +96,7 @@ export interface ChannelDetail {
|
|||
country: string | null;
|
||||
subscribed: boolean;
|
||||
explored: boolean;
|
||||
blocked: boolean;
|
||||
stored_videos: number;
|
||||
from_explore: boolean;
|
||||
details_synced: boolean;
|
||||
|
|
@ -601,11 +602,16 @@ export const api = {
|
|||
// `quiet` so the YT-search panel can render quota/limit errors (incl. 429) inline instead of
|
||||
// popping the global modal. `cursor` is the next-page token (an InnerTube continuation token
|
||||
// in scrape mode, or a Data API pageToken in api mode); the response's `source` says which.
|
||||
searchYoutube: (q: string, cursor: string | null): Promise<FeedResponse> => {
|
||||
searchYoutube: (q: string, cursor: string | null, limit?: number): Promise<FeedResponse> => {
|
||||
const p = new URLSearchParams({ q });
|
||||
if (cursor) p.set("cursor", cursor);
|
||||
if (limit) p.set("limit", String(limit));
|
||||
return req(`/api/search/youtube?${p.toString()}`, {}, { quiet: true });
|
||||
},
|
||||
// Discard a set of live-search results "as if never added" (drops your search-finds + deletes
|
||||
// the now-orphaned, un-kept videos). Returns how many videos were removed.
|
||||
clearSearch: (videoIds: string[]): Promise<{ removed: number }> =>
|
||||
req("/api/search/clear", { method: "POST", body: JSON.stringify({ video_ids: videoIds }) }),
|
||||
facets: (f: FeedFilters): Promise<{ counts: Record<string, number> }> =>
|
||||
req(`/api/facets?${filterParams(f).toString()}`),
|
||||
// idempotent: setting a state / saving a progress checkpoint is safe to replay, so these
|
||||
|
|
@ -674,6 +680,14 @@ export const api = {
|
|||
{ quiet: true }
|
||||
);
|
||||
},
|
||||
// --- per-user channel blocklist (excluded from live search + feed/explore) ---
|
||||
blockedChannels: (): Promise<{ id: string; title: string | null; handle: string | null; thumbnail_url: string | null }[]> =>
|
||||
req("/api/channels/blocked/list"),
|
||||
blockChannel: (id: string) => req(`/api/channels/${id}/block`, { method: "POST" }),
|
||||
unblockChannel: (id: string) => req(`/api/channels/${id}/block`, { method: "DELETE" }),
|
||||
// Admin: reclaim all un-kept discovery content (search results + explored channels) now.
|
||||
purgeDiscovery: (): Promise<Record<string, number>> =>
|
||||
req("/api/admin/purge-discovery", { method: "POST" }),
|
||||
|
||||
// --- playlists ---
|
||||
playlists: (containsVideoId?: string): Promise<Playlist[]> =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue