import { Alpine } from "./alpine"; import { showToast } from "./toast"; import { getToken } from "./storage"; let selectedMediaItem: string | null = null; function searchMatches( progressId: string, sha256: string, title: string, ): void { const container = document.getElementById(`matches-${progressId}`); const matchesList = document.getElementById(`matches-list-${progressId}`); if (!container || !matchesList) return; container.classList.remove("hidden"); matchesList.innerHTML = '
Searching...
'; const token = getToken(); const url = sha256 ? `/api/books/match?sha256=${sha256}` : `/api/books/match?title=${encodeURIComponent(title)}`; fetch(url, { headers: { Authorization: `Bearer ${token}`, }, }) .then((response) => response.json()) .then((result) => { if (result.matches && result.matches.length > 0) { matchesList.innerHTML = result.matches .map( (match: any) => `by ${match.author || "Unknown"}
No matches found. Try manual linking.
'; } }) .catch((error) => { console.error("Failed to search", error); matchesList.innerHTML = 'Failed to search
'; }); } function autoLinkBook( progressId: string, mediaItemId: string, confidence: number, ): void { if ( !confirm( "Link this book? The confidence score is " + Math.round(confidence * 100) + "%", ) ) { return; } const progressElement = document.getElementById(`matches-${progressId}`); const codeElement = progressElement?.querySelector("code"); const sha256Element = progressElement?.querySelector('[title="SHA-256"]'); const data = { device_file: { file_path: codeElement?.textContent || "", sha256: sha256Element?.textContent || "", }, media_item_id: mediaItemId, confidence_score: confidence, }; const token = getToken(); fetch(`/api/devices/sync/link-book`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(data), }) .then((response) => response.json()) .then(() => { showToast("Book linked successfully", "success"); window.location.reload(); }) .catch((error) => { console.error("Failed to link book", error); showToast("Failed to link book", "error"); }); } function showManualLinkModal(progressId: string, bookTitle: string): void { const modal = document.getElementById("manual-link-modal"); const progressIdInput = document.getElementById( "link-progress-id", ) as HTMLInputElement; const bookTitleInput = document.getElementById( "link-book-title", ) as HTMLInputElement; const searchResults = document.getElementById("link-search-results"); if (modal) modal.classList.remove("hidden"); if (progressIdInput) progressIdInput.value = progressId; if (bookTitleInput) bookTitleInput.value = bookTitle; if (searchResults) searchResults.innerHTML = 'Search for books to link
'; selectedMediaItem = null; } function hideManualLinkModal(): void { const modal = document.getElementById("manual-link-modal"); const searchInput = document.getElementById( "link-search-input", ) as HTMLInputElement; if (modal) modal.classList.add("hidden"); if (searchInput) searchInput.value = ""; selectedMediaItem = null; } function searchBooksForLink(): void { const searchInput = document.getElementById( "link-search-input", ) as HTMLInputElement; const resultsContainer = document.getElementById("link-search-results"); if (!searchInput || !resultsContainer) return; const searchTerm = searchInput.value; if (searchTerm.length < 2) { resultsContainer.innerHTML = 'Enter at least 2 characters
'; return; } resultsContainer.innerHTML = 'Searching...
'; const token = getToken(); fetch(`/api/books/match?title=${encodeURIComponent(searchTerm)}`, { headers: { Authorization: `Bearer ${token}`, }, }) .then((response) => response.json()) .then((result) => { if (result.matches && result.matches.length > 0) { resultsContainer.innerHTML = result.matches .map( (match: any) => `by ${match.author || "Unknown"}
${Math.round(match.confidence * 100)}% confidence
No matches found
'; } }) .catch((error) => { console.error("Failed to search", error); resultsContainer.innerHTML = 'Failed to search
'; }); } function selectBookForLink( mediaItemId: string, title: string, _coverPath: string, ): void { selectedMediaItem = mediaItemId; const resultsContainer = document.getElementById("link-search-results"); if (!resultsContainer) return; const cards = resultsContainer.querySelectorAll(".card"); cards.forEach((card) => { card.classList.remove("border-2", "border-blue-500"); if ((card as HTMLElement).dataset.mediaItemId === mediaItemId) { card.classList.add("border-2", "border-blue-500"); } }); } function confirmManualLink(): void { if (!selectedMediaItem) { showToast("Please select a book to link", "error"); return; } const progressIdInput = document.getElementById( "link-progress-id", ) as HTMLInputElement; const confidenceInput = document.getElementById( "link-confidence", ) as HTMLInputElement; const bookTitleInput = document.getElementById( "link-book-title", ) as HTMLInputElement; const sha256Input = document.getElementById( "link-book-sha256", ) as HTMLInputElement; if (!progressIdInput) return; const progressId = progressIdInput.value; const confidence = parseFloat(confidenceInput?.value || "0"); const bookTitle = bookTitleInput?.value || ""; const sha256 = sha256Input?.value || ""; const data = { device_file: { file_path: bookTitle, sha256: sha256, }, media_item_id: selectedMediaItem, confidence_score: confidence, }; const token = getToken(); fetch(`/api/devices/sync/link-book`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(data), }) .then((response) => response.json()) .then(() => { showToast("Book linked successfully", "success"); hideManualLinkModal(); window.location.reload(); }) .catch((error) => { console.error("Failed to link book", error); showToast("Failed to link book", "error"); }); } function toggleAllUnlinked(): void { const selectAll = document.getElementById( "select-all-unlinked", ) as HTMLInputElement; if (!selectAll) return; document.querySelectorAll(".unlinked-checkbox").forEach((cb) => { (cb as HTMLInputElement).checked = selectAll.checked; }); updateSelectedCount(); } function getSelectedUnlinked(): { progressId: string; title: string }[] { return Array.from( document.querySelectorAll(".unlinked-checkbox:checked"), ).map((cb) => ({ progressId: cb.getAttribute("data-progress-id") || "", title: cb.getAttribute("data-title") || "", })); } function updateSelectedCount(): void { const count = document.querySelectorAll(".unlinked-checkbox:checked").length; const countElement = document.getElementById("selected-count"); if (countElement) { countElement.textContent = `${count} selected`; } } async function bulkAutoLink(): PromiseNo matches found
'; return; } suggestions.forEach((match) => { const div = document.createElement("div"); div.className = "p-3 border rounded cursor-pointer hover:bg-opacity-80 transition-colors"; div.style.cssText = `background-color: var(--bg-primary); border-color: var(--border);`; div.innerHTML = `Author: ${match.author || "Unknown"}