diff --git a/web/src/linking.ts b/web/src/linking.ts deleted file mode 100644 index d88a8ff0..00000000 --- a/web/src/linking.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { Alpine } from "./alpine"; -import { authHeaders } from "./storage"; -import { showToast } from "./toast"; - -async function loadUnlinkedBooks(): Promise { - // cookie auth carries the call; a null bearer must not strand it - - try { - const response = await fetch("/api/sync/unlinked-books", { - headers: authHeaders(), - }); - - if (response.ok) { - const data = await response.json(); - renderUnlinkedBooks(data.unlinked || []); - } - } catch (error) { - console.error("Failed to load unlinked books:", error); - } -} - -function renderUnlinkedBooks(books: UnlinkedBookData[]): void { - const container = document.getElementById("unlinked-books-list"); - if (!container) return; - - if (books.length === 0) { - container.innerHTML = - '

No unlinked books

'; - return; - } - - container.innerHTML = books - .map( - (book) => ` -
-
-
-

${book.title_from_device}

-

${book.device_name} (${book.device_type})

-

${book.file_path}

-

Confidence: ${Math.round(book.confidence_score * 100)}%

-
-
- -
-
- ${ - book.potential_matches && book.potential_matches.length > 0 - ? ` -
-

Potential Matches:

- ${book.potential_matches - .slice(0, 3) - .map( - (match) => ` -
-
-

${match.title}

-

${match.author} (${Math.round(match.confidence * 100)}%)

-
- -
- `, - ) - .join("")} -
- ` - : "" - } -
- `, - ) - .join(""); -} - -async function linkBook(progressId: string, mediaItemId: string): Promise { - // cookie auth carries the call; a null bearer must not strand it - - try { - const response = await fetch("/api/sync/link-book", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - progress_id: progressId, - media_item_id: mediaItemId, - }), - }); - - if (response.ok) { - showToast("Book linked successfully", "success"); - loadUnlinkedBooks(); - } else { - const error = await response.json(); - showToast(error.error || "Failed to link book", "error"); - } - } catch (error) { - console.error("Failed to link book:", error); - showToast("Failed to link book", "error"); - } -} - -async function autoLinkBooks(): Promise { - // cookie auth carries the call; a null bearer must not strand it - - if (!confirm("Auto-link all books with high confidence matches?")) return; - - try { - const response = await fetch("/api/sync/auto-link", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ confidence_threshold: 0.9 }), - }); - - if (response.ok) { - const data = await response.json(); - showToast(`Auto-linked ${data.linked_count || 0} books`, "success"); - loadUnlinkedBooks(); - } - } catch (error) { - console.error("Failed to auto-link:", error); - showToast("Failed to auto-link books", "error"); - } -} - -async function getSuggestions(progressId: string): Promise { - // cookie auth carries the call; a null bearer must not strand it - - try { - const response = await fetch(`/api/sync/suggestions/${progressId}`, { - headers: authHeaders(), - }); - - if (response.ok) { - const suggestions = await response.json(); - showSuggestionsModal(progressId, suggestions); - } - } catch (error) { - console.error("Failed to get suggestions:", error); - } -} - -function showSuggestionsModal(progressId: string, suggestions: PotentialMatchData[]): void { - const modal = document.getElementById("match-modal"); - const content = document.getElementById("match-modal-content"); - - if (!modal || !content) return; - - content.innerHTML = ` -
-

Select a match

-
- ${suggestions - .map( - (s) => ` -
-

${s.title}

-

${s.author}

-

${Math.round(s.confidence * 100)}% match

-
- `, - ) - .join("")} -
- -
- `; - - modal.classList.remove("hidden"); -} - -function hideMatchModal(): void { - const modal = document.getElementById("match-modal"); - if (modal) { - modal.classList.add("hidden"); - } -} - -// Delegated click wiring for the generated markup above: the CSP build -// refuses inline onclick= handlers (and module functions are not globals). -// The row-level "Link" opens the suggestions flow via getSuggestions — the -// old window.showMatchModal it referenced never existed. -document.addEventListener("click", (e) => { - const target = e.target as HTMLElement; - const show = target.closest("[data-show-match]"); - if (show) { - void getSuggestions(show.dataset.showMatch ?? ""); - return; - } - const link = target.closest("[data-link-book]"); - if (link) { - void linkBook(link.dataset.linkBook ?? "", link.dataset.mediaId ?? ""); - hideMatchModal(); - return; - } - if (target.closest("[data-hide-match]")) { - hideMatchModal(); - } -}); - -Alpine.data("linking", () => ({ - autoLinkBooks, - getSuggestions, - hideMatchModal, - linkBook, - loadUnlinkedBooks, - showSuggestionsModal, -})); diff --git a/web/src/main.ts b/web/src/main.ts index 1aa4fdc7..93fc8e75 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -20,7 +20,6 @@ import "./index"; import "./image_fallback"; import { armAuthFormRedirects } from "./auth_forms"; import "./library-switcher"; -import "./linking"; import "./login"; import "./password_validation"; import "./processing_issues";