From 02a89aee6843de72dde8d9de1fbf88b74b6da057 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 27 May 2026 21:15:58 +0000 Subject: [PATCH] Use /search/:term path-based search routing --- frontend/src/App.vue | 77 +++++++++++++++++++++++++++++------------- frontend/src/router.ts | 5 +++ 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 9ee881c..a1386c5 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -212,11 +212,20 @@ function normalizeSearchQuery(value: unknown): string { } function getRouteSearchQuery() { - return normalizeSearchQuery(route.query.q) + if (route.name !== "search") { + return "" + } + return normalizeSearchQuery(route.params.term) +} + +function getSearchPath(term: string) { + return router.resolve({ name: "search", params: { term } }).path } function getBrowsePath() { - return route.meta.view === "series" ? "/series" : "/movies" + if (route.meta.view === "series") return "/series" + if (route.meta.view === "movies") return "/movies" + return searchReturnPath.value ?? "/movies" } function getBrowseViewFromPath(path: string): "movies" | "series" { @@ -445,6 +454,28 @@ function getSearchExitTargetFromFocusedCard(): { path: "/movies" | "/series"; it return null } +function getDetailSearchPath(): string | null { + if (!route.params.id) return null + + const stateSearchPathRaw = window.history.state?.searchPath + if (typeof stateSearchPathRaw === "string") { + const stateSearchPath = normalizeHistoryPath(stateSearchPathRaw) + if (stateSearchPath.startsWith("/search/")) { + return stateSearchPath + } + } + + const backPath = + typeof window.history.state?.back === "string" + ? normalizeHistoryPath(window.history.state.back) + : "" + if (backPath.startsWith("/search/")) { + return backPath + } + + return null +} + function clearSearch(options: { preferBack?: boolean; targetPath?: string } = {}) { const currentQuery = getRouteSearchQuery() const targetPath = options.targetPath ?? getBrowsePath() @@ -498,11 +529,11 @@ function updateSearchQuery(nextValue: string) { saveFocusForPage(currentView.value) } searchReturnPath.value = browsePath - void router.push({ path: browsePath, query: { q: nextQuery } }) + void router.push(getSearchPath(nextQuery)) return } - void router.replace({ path: browsePath, query: { q: nextQuery } }) + void router.replace(getSearchPath(nextQuery)) } // Handle Escape key for navigation hierarchy @@ -516,13 +547,13 @@ function handleEscapeKey(event: KeyboardEvent) { } const path = route.path - const activeSearchQuery = getRouteSearchQuery() + const detailSearchPath = getDetailSearchPath() - if (route.params.id && activeSearchQuery) { + if (route.params.id && detailSearchPath) { event.preventDefault() - const targetPath = getBrowsePath() - router.push({ path: targetPath, query: { q: activeSearchQuery } }) - restoreFocusForPage(getBrowseViewFromPath(targetPath)) + void router.push(detailSearchPath).finally(() => { + restoreFocusForPage("movies") + }) return } @@ -582,12 +613,12 @@ onUnmounted(() => { // Handle back navigation (Escape key or Back button) function goBack() { const path = route.path - const activeSearchQuery = getRouteSearchQuery() + const detailSearchPath = getDetailSearchPath() - if (route.params.id && activeSearchQuery) { - const targetPath = getBrowsePath() - router.push({ path: targetPath, query: { q: activeSearchQuery } }) - restoreFocusForPage(getBrowseViewFromPath(targetPath)) + if (route.params.id && detailSearchPath) { + void router.push(detailSearchPath).finally(() => { + restoreFocusForPage("movies") + }) return } @@ -615,7 +646,7 @@ const currentView = computed(() => { }) const headerCurrentView = computed<"movies" | "series" | "search">(() => { - return selectedItem.value && searchQuery.value ? "search" : currentView.value + return selectedItem.value && getDetailSearchPath() ? "search" : currentView.value }) // Header position based on current page @@ -688,21 +719,21 @@ function showDetail(item: MediaItem) { if (playableFile) { handlePlay(playableFile) } else { - const query = searchQuery.value ? { q: searchQuery.value } : undefined - router.push({ path: `/series/${epData.series.id}`, query }) + const searchPath = searchQuery.value ? getSearchPath(searchQuery.value) : null + router.push({ path: `/series/${epData.series.id}`, state: searchPath ? { searchPath } : undefined }) } } else { - const query = searchQuery.value ? { q: searchQuery.value } : undefined - router.push({ path: `/${item.type}/${item.id}`, query }) + const searchPath = searchQuery.value ? getSearchPath(searchQuery.value) : null + router.push({ path: `/${item.type}/${item.id}`, state: searchPath ? { searchPath } : undefined }) } } // Close detail by navigating back to list function closeDetail() { - const activeSearchQuery = getRouteSearchQuery() + const detailSearchPath = getDetailSearchPath() const targetPath = getBrowsePath() - if (activeSearchQuery) { - router.push({ path: targetPath, query: { q: activeSearchQuery } }) + if (detailSearchPath) { + router.push(detailSearchPath) } else { router.push(targetPath) } @@ -1046,7 +1077,7 @@ watch( if (nextQuery !== searchQuery.value) { searchQuery.value = nextQuery } - if (!nextQuery) { + if (!nextQuery && route.name !== "search") { searchReturnPath.value = null } }, diff --git a/frontend/src/router.ts b/frontend/src/router.ts index 0ba37fe..30b2b04 100644 --- a/frontend/src/router.ts +++ b/frontend/src/router.ts @@ -31,6 +31,11 @@ const router = createRouter({ component: EmptyRouteComponent, meta: { view: "movies" }, }, + { + path: "/search/:term", + name: "search", + component: EmptyRouteComponent, + }, { path: "/series", name: "series",