feat(ui): add Rescan button to Edit Metadata dialog
Release / build-and-push (push) Successful in 2m50s

Adds a Rescan button to the MetadataEditorModal footer that POSTs to the
new per-book rescan endpoint, with rescanning state (disabled + spinner,
matching the existing Mark Read pattern), success/error toasts, and a
page reload to pick up the refreshed cover and metadata.
This commit is contained in:
2026-09-11 23:08:20 -04:00
parent 298330a3a8
commit da1f689263
3 changed files with 52 additions and 3 deletions
+28
View File
@@ -97,6 +97,7 @@ interface MetadataEditorState {
coverFile: Blob | null;
coverAction: string;
saving: boolean;
rescanning: boolean;
userRating: number;
ratingHover: number;
ratingSaving: boolean;
@@ -110,6 +111,7 @@ interface MetadataEditorState {
generateCover(): Promise<void>;
removeCover(): void;
saveMetadata(): Promise<void>;
rescanBook(): Promise<void>;
starFill(i: number): string;
ratingText(): string;
setRating(value: number): Promise<void>;
@@ -149,6 +151,7 @@ Alpine.data("bookDetail", () => {
coverFile: null as Blob | null,
coverAction: "keep",
saving: false,
rescanning: false,
userRating: 0,
ratingHover: 0,
ratingSaving: false,
@@ -559,6 +562,31 @@ Alpine.data("bookDetail", () => {
}
},
async rescanBook() {
if (this.rescanning) return;
this.rescanning = true;
const mediaId = getMediaId();
try {
const resp = await fetch(`/api/media-items/${mediaId}/rescan`, {
method: "POST",
headers: { Authorization: getAuthHeader() },
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
throw new Error(err.error || "Failed to rescan book");
}
showToast("Book rescanned successfully", "success");
setTimeout(() => window.location.reload(), 500);
} catch (e) {
showToast(
e instanceof Error ? e.message : "Failed to rescan book",
"error",
);
} finally {
this.rescanning = false;
}
},
async searchEditorTags() {
const libraryId = document.body.getAttribute("data-library-id") || "";
if (!this.tagSearch || this.tagSearch.length < 2 || !libraryId) {