diff --git a/static/app.js b/static/app.js index efce031..7bcadde 100644 --- a/static/app.js +++ b/static/app.js @@ -84,10 +84,10 @@ function libOptions(mediaType) { // ---- missing badge ---- async function refreshMissingBadge() { try { - const reqs = await api("/api/requests?status=missing"); + const { count } = await api("/api/requests/count?status=missing"); const badge = $("#missing-badge"); - badge.textContent = reqs.length; - badge.hidden = reqs.length === 0; + badge.textContent = count; + badge.hidden = count === 0; } catch {} } @@ -519,6 +519,22 @@ function requestMatchesFilter(r, text) { return hay.includes(text.toLowerCase()); } +// the server already sends newest first +const SORTS = { + title: (a, b) => a.title.localeCompare(b.title, undefined, { numeric: true }), + author: (a, b) => a.authors.localeCompare(b.authors) || SORTS.title(a, b), + series: (a, b) => (a.series || a.title).localeCompare(b.series || b.title) + || (a.volume ?? 0) - (b.volume ?? 0) || SORTS.title(a, b), +}; +for (const status of ["missing", "imported"]) { + const sel = $(`#${status}-sort`); + try { sel.value = localStorage.getItem("wordarr.sort." + status) || "newest"; } catch {} + sel.addEventListener("change", () => { + try { localStorage.setItem("wordarr.sort." + status, sel.value); } catch {} + renderRequests(status); + }); +} + async function loadRequests(status) { const params = new URLSearchParams({ status }); if (status === "missing") { @@ -543,11 +559,15 @@ async function loadRequests(status) { function renderRequests(status) { const text = status === "missing" ? $("#missing-search").value : $("#imported-search").value; const reqs = currentRequests[status].filter((r) => requestMatchesFilter(r, text)); + const sort = SORTS[$(`#${status}-sort`).value]; + if (sort) reqs.sort(sort); const box = status === "missing" ? $("#missing-list") : $("#imported-list"); const bulkBtn = status === "missing" ? $("#missing-bulk-delete") : $("#imported-bulk-delete"); + const moveSel = status === "missing" ? $("#missing-move-library") : null; if (!reqs.length) { box.innerHTML = `
${t("common.nothingHere")}
`; bulkBtn.hidden = true; + if (moveSel) moveSel.hidden = true; return; } box.innerHTML = reqs @@ -582,6 +602,10 @@ function renderRequests(status) { const n = box.querySelectorAll("[data-sel-req]:checked").length; bulkBtn.hidden = n === 0; bulkBtn.textContent = t("common.removeSelectedN", { n }); + if (!moveSel) return; + moveSel.hidden = n === 0; + moveSel.innerHTML = `` + + libraries.map((l) => ``).join(""); }; box.querySelectorAll("[data-sel-req]").forEach((c) => c.addEventListener("change", updateBulkBtn) @@ -596,14 +620,29 @@ async function bulkDeleteRequests(status) { const hint = t(status === "imported" ? "list.confirmDeleteImported" : "list.confirmDeleteRequests", { n: ids.length }); if (!confirm(hint)) return; - for (const id of ids) { - try { await api("/api/requests/" + id, { method: "DELETE" }); } - catch (err) { toast(err.message, true); } - } - toast(t("list.deletedN", { n: ids.length })); + try { + const res = await api("/api/requests/bulk", { + method: "DELETE", body: JSON.stringify({ ids: ids.map(Number) }), + }); + toast(t("list.deletedN", { n: res.deleted })); + } catch (err) { toast(err.message, true); } loadRequests(status); } +$("#missing-move-library").addEventListener("change", async (ev) => { + const lib = libraries.find((l) => String(l.id) === ev.target.value); + if (!lib) return; + const ids = [...$("#missing-list").querySelectorAll("[data-sel-req]:checked")] + .map((c) => Number(c.dataset.selReq)); + try { + const res = await api("/api/requests/bulk", { + method: "PUT", body: JSON.stringify({ ids, library_id: lib.id }), + }); + toast(t("list.movedN", { n: res.updated, library: lib.name })); + } catch (err) { toast(err.message, true); } + loadRequests("missing"); +}); + $("#imported-bulk-delete").addEventListener("click", () => bulkDeleteRequests("imported")); function selectAllRequests(status) { diff --git a/static/i18n.js b/static/i18n.js index f0755a7..745278b 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -106,6 +106,13 @@ const STRINGS = { "list.confirmDeleteRequests": "{n} Anfrage(n) entfernen?", "list.confirmDeleteImported": "{n} Eintrag/Einträge entfernen? Die importierten Dateien in der Library bleiben erhalten.", "list.deletedN": "{n} entfernt", + "list.sort": "Sortierung", + "list.sortNewest": "Neueste zuerst", + "list.sortTitle": "Titel", + "list.sortAuthor": "Autor", + "list.sortSeries": "Serie und Band", + "list.moveTo": "Bibliothek ändern ({n})…", + "list.movedN": "{n} nach {library} verschoben", "detail.statusMissing": "fehlt noch", "detail.statusImported": "importiert", @@ -339,6 +346,13 @@ const STRINGS = { "list.confirmDeleteRequests": "Remove {n} request(s)?", "list.confirmDeleteImported": "Remove {n} entr(y/ies)? The imported files stay in the library.", "list.deletedN": "{n} removed", + "list.sort": "Sort order", + "list.sortNewest": "Newest first", + "list.sortTitle": "Title", + "list.sortAuthor": "Author", + "list.sortSeries": "Series and volume", + "list.moveTo": "Change library ({n})…", + "list.movedN": "{n} moved to {library}", "detail.statusMissing": "still missing", "detail.statusImported": "imported", diff --git a/static/index.html b/static/index.html index 43cb977..73331d6 100644 --- a/static/index.html +++ b/static/index.html @@ -110,7 +110,14 @@ + +