feat(plex): field-scoped search (S2) + instant search typeahead
Add a search scope-pill to the Plex header search box (GitHub-style, one field-scope at a time) and an instant typeahead of the top matches below it. Backend (unified_library): - New `field` param: all | title | people | summary | episode. `all` is byte-identical to the pre-scope behaviour (untouched weighted search_vector); a specific field keeps the GIN `search_vector @@ tsq` prefilter AND ANDs a per-column tsvector recheck, so scoped hits are a strict subset of Everywhere. Per-field expressions mirror the search_vector Computed def (same TS config, same left(summary,1000) truncation). - `episode` scope empties the movie/show grid and title-only-matches the grouped Episodes section; the title/people/summary scopes suppress that section. - Unknown field falls back to `all`. New DB-lane characterization tests (test_plex_field_scoped_search.py): each scope narrows correctly, field=all is identical to today, episode empties the grid and needs shows in scope. Frontend: - PlexSearchBox wraps the shared SearchBar with the scope-pill (reusing ActionMenu) and the typeahead; SearchBar gains generic leading/below/focus/ keydown slots so other modules' search boxes are untouched. - Episode scope offered only when shows are in scope (movie-only hides it), healed at the point of use like the sort state. - Typeahead reuses /api/plex/library (limit=10), honours scope+field (not the sidebar filters), shows poster+title+year, keyboard nav (arrows/Enter/Esc), and jumps straight to a movie's info page or a show's drill-down via a new provider jumpTo hand-off (mirrors the playlistOpen pattern). - `field` persisted per-account; en/hu labels added.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
// touch only the shared `req`/`beacon` from core plus the local `appendPlexFilters` helper.
|
||||
import { req, beacon } from "./core";
|
||||
import { type SortState } from "../columnSort";
|
||||
import { type PlexSearchField } from "../plexSearch";
|
||||
|
||||
// Admin: result of testing the Plex server connection (also drives the library-picker).
|
||||
interface PlexSection {
|
||||
@@ -275,6 +276,7 @@ export const plexApi = {
|
||||
plexLibrary: (p: {
|
||||
scope: string; // movie | show | both
|
||||
q?: string;
|
||||
field?: PlexSearchField; // search scope-pill (all | title | people | summary | episode)
|
||||
sort?: SortState;
|
||||
show?: string;
|
||||
offset?: number;
|
||||
@@ -283,6 +285,7 @@ export const plexApi = {
|
||||
}): Promise<PlexUnifiedResult> => {
|
||||
const u = new URLSearchParams({ scope: p.scope });
|
||||
if (p.q) u.set("q", p.q);
|
||||
if (p.field && p.field !== "all") u.set("field", p.field);
|
||||
if (p.sort) {
|
||||
u.set("sort", p.sort.key);
|
||||
u.set("sort_dir", p.sort.dir);
|
||||
|
||||
Reference in New Issue
Block a user