fix(search): link results to book detail page and add cover thumbnails
Release / build-and-push (push) Successful in 2m24s

Search results navigated to /bookshelf with no filters instead of the
selected book's page. Results now link to /media/:id and display cover
thumbnails, with cover URLs resolved server-side via ResolveMediaURL.
Removes the dead selectedBook localStorage plumbing.
This commit is contained in:
2026-07-30 14:55:00 -04:00
parent bf83492bf7
commit 5ac407057e
3 changed files with 14 additions and 30 deletions
+4
View File
@@ -1791,6 +1791,10 @@ func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
"results": []interface{}{},
})
}
for i := range results {
resolved := utils.ResolveMediaURL(results[i].LibraryID, results[i].CoverImagePath)
results[i].CoverImagePath = pgtype.Text{String: resolved, Valid: resolved != ""}
}
return c.JSON(http.StatusOK, results)
}
+10 -20
View File
@@ -1,5 +1,4 @@
import { Alpine } from "./alpine";
import { setSelectedLibrary } from "./storage";
let searchInputTimeout: ReturnType<typeof setTimeout> | null = null;
const SEARCH_DEBOUNCE_MS = 300;
@@ -190,12 +189,6 @@ function showSearchResults(results: MediaItemSummary[], query: string, activeId?
searchResults.dataset.selectedIndex = "-1";
const libraryIconMap: Record<string, string> = {
ebooks: "📚",
comics: "📖",
manga: "🗾",
};
let html = `
<div class="p-3 border-b" style="border-color: var(--border)">
<p class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-secondary)">
@@ -206,19 +199,23 @@ function showSearchResults(results: MediaItemSummary[], query: string, activeId?
`;
results.forEach((item, index) => {
const icon = libraryIconMap[item.library_type_name] || "📁";
const titleHtml = highlightMatch(item.title, query);
const authorHtml = item.author ? highlightMatch(item.author, query) : "";
const coverUrl = item.cover_image_path || "/static/placeholder-book.svg";
html += `
<div class="search-result-item p-3 border-b hover:bg-opacity-50 transition-colors cursor-pointer"
style="border-color: var(--border); background-color: var(--bg-secondary)"
data-index="${index}">
<a href="/bookshelf"
class="block"
onclick="window.selectLibraryAndBook('${item.library_id}', '${item.id}')">
<a href="/media/${item.id}" class="block">
<div class="flex items-start space-x-3">
<div class="text-2xl">${icon}</div>
<div class="w-10 h-14 flex-shrink-0 rounded overflow-hidden bg-gradient-to-br from-gray-700 to-gray-900">
<img src="${coverUrl}"
alt="${searchEscapeHtml(item.title)}"
class="w-full h-full object-cover"
loading="lazy"
onerror="this.src='/static/placeholder-book.svg'">
</div>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-medium truncate" style="color: var(--text-primary)">
${titleHtml}
@@ -323,15 +320,8 @@ function searchEscapeHtml(text: string): string {
return div.innerHTML;
}
function selectLibraryAndBook(libraryId: string, bookId: string): void {
setSelectedLibrary(libraryId);
localStorage.setItem("selectedBook", bookId);
hideSearchResults();
}
export { selectLibraryAndBook, initializeSearch };
export { initializeSearch };
Alpine.data("search", () => ({
selectLibraryAndBook,
initializeSearch,
}));
-10
View File
@@ -45,14 +45,6 @@ function setSelectedLibrary(libraryId: string): void {
document.cookie = `selectedLibrary=${encodeURIComponent(value)};path=/;max-age=${365 * 24 * 60 * 60};samesite=lax`;
}
function getSelectedBook(): string | null {
return localStorage.getItem("selectedBook");
}
function setSelectedBook(bookId: string): void {
localStorage.setItem("selectedBook", bookId);
}
function clearAll(): void {
localStorage.clear();
}
@@ -61,14 +53,12 @@ export {
ALL_LIBRARIES,
clearAll,
getRefreshToken,
getSelectedBook,
getSelectedLibrary,
getTheme,
getToken,
removeRefreshToken,
removeToken,
setRefreshToken,
setSelectedBook,
setSelectedLibrary,
setTheme,
setToken