refactor(feed): lift the sort vocabulary out of Feed into lib/feedSort

The active-filter chips need to label a sort, and parseSort/SORT_MAP were
module-private in Feed. Pure move, no behaviour change.
This commit is contained in:
2026-07-15 22:03:40 +02:00
parent 3596d83acd
commit 837b48cbfa
2 changed files with 69 additions and 26 deletions
+8 -26
View File
@@ -6,6 +6,14 @@ import { api, HttpError, type FeedFilters, type Video } from "../lib/api";
import i18n from "../i18n";
import { notify, resolveVideo } from "../lib/notifications";
import { useDebounced } from "../lib/useDebounced";
import {
buildSort,
DIRECTIONLESS,
parseSort,
SORT_DEFAULT_DIR,
SORT_KEYS,
type SortKey,
} from "../lib/feedSort";
import VirtualFeed from "./VirtualFeed";
// Lazy: the in-app YouTube player (IFrame API) loads only when a video is first opened.
const PlayerModal = lazy(() => import("./PlayerModal"));
@@ -25,32 +33,6 @@ const rollSeed = () => Math.floor(Math.random() * 1_000_000_000);
// Ordering = a key + direction (like the Playlists page), mapped to the backend sort strings
// (which encode both). One entry per concept; a single arrow flips the direction.
// "relevance" (full-text search ranking) and "shuffle" are directionless single-string sorts.
type SortKey = "date" | "popular" | "duration" | "title" | "subscribers" | "priority" | "shuffle" | "relevance";
const SORT_KEYS: SortKey[] = ["date", "popular", "duration", "title", "subscribers", "priority", "shuffle"];
const DIRECTIONLESS: SortKey[] = ["shuffle", "relevance"];
const SORT_MAP: Record<Exclude<SortKey, "shuffle" | "relevance">, { asc: string; desc: string }> = {
date: { desc: "newest", asc: "oldest" },
popular: { desc: "views", asc: "views_asc" },
duration: { desc: "duration_desc", asc: "duration_asc" },
title: { asc: "title", desc: "title_desc" },
subscribers: { desc: "subscribers", asc: "subscribers_asc" },
priority: { desc: "priority", asc: "priority_asc" },
};
const SORT_DEFAULT_DIR: Record<Exclude<SortKey, "shuffle" | "relevance">, "asc" | "desc"> = {
date: "desc", popular: "desc", duration: "desc", title: "asc", subscribers: "desc", priority: "desc",
};
function parseSort(s: string): { key: SortKey; dir: "asc" | "desc" } {
if (s === "shuffle" || s === "relevance") return { key: s, dir: "desc" };
for (const k of Object.keys(SORT_MAP) as (keyof typeof SORT_MAP)[]) {
if (SORT_MAP[k].asc === s) return { key: k, dir: "asc" };
if (SORT_MAP[k].desc === s) return { key: k, dir: "desc" };
}
return { key: "date", dir: "desc" };
}
function buildSort(key: SortKey, dir: "asc" | "desc"): string {
if (key === "shuffle" || key === "relevance") return key;
return SORT_MAP[key][dir];
}
function matchesView(status: string, show: string): boolean {
switch (show) {
+61
View File
@@ -0,0 +1,61 @@
// The feed's sort vocabulary. `FeedFilters.sort` is a single wire string ("newest", "views_asc",
// …); the UI thinks in a key + direction. Kept here rather than inside Feed so the active-filter
// chips can label a sort without re-deriving the mapping.
export type SortKey =
| "date"
| "popular"
| "duration"
| "title"
| "subscribers"
| "priority"
| "shuffle"
| "relevance";
export const SORT_KEYS: SortKey[] = [
"date",
"popular",
"duration",
"title",
"subscribers",
"priority",
"shuffle",
];
/** Sorts with no meaningful asc/desc flip. */
export const DIRECTIONLESS: SortKey[] = ["shuffle", "relevance"];
const SORT_MAP: Record<Exclude<SortKey, "shuffle" | "relevance">, { asc: string; desc: string }> = {
date: { desc: "newest", asc: "oldest" },
popular: { desc: "views", asc: "views_asc" },
duration: { desc: "duration_desc", asc: "duration_asc" },
title: { asc: "title", desc: "title_desc" },
subscribers: { desc: "subscribers", asc: "subscribers_asc" },
priority: { desc: "priority", asc: "priority_asc" },
};
export const SORT_DEFAULT_DIR: Record<
Exclude<SortKey, "shuffle" | "relevance">,
"asc" | "desc"
> = {
date: "desc",
popular: "desc",
duration: "desc",
title: "asc",
subscribers: "desc",
priority: "desc",
};
export function parseSort(s: string): { key: SortKey; dir: "asc" | "desc" } {
if (s === "shuffle" || s === "relevance") return { key: s, dir: "desc" };
for (const k of Object.keys(SORT_MAP) as (keyof typeof SORT_MAP)[]) {
if (SORT_MAP[k].asc === s) return { key: k, dir: "asc" };
if (SORT_MAP[k].desc === s) return { key: k, dir: "desc" };
}
return { key: "date", dir: "desc" };
}
export function buildSort(key: SortKey, dir: "asc" | "desc"): string {
if (key === "shuffle" || key === "relevance") return key;
return SORT_MAP[key][dir];
}