add(metadata): chose and request entity while scanning download/
This commit is contained in:
+98
-1
@@ -292,13 +292,110 @@ function renderImportTable() {
|
|||||||
<td><input type="checkbox" data-check="${i}" ${item.suggested_request_id ? "checked" : ""}></td>
|
<td><input type="checkbox" data-check="${i}" ${item.suggested_request_id ? "checked" : ""}></td>
|
||||||
<td class="mono">${esc(item.name)}${item.is_dir ? " 📁" : ""}</td>
|
<td class="mono">${esc(item.name)}${item.is_dir ? " 📁" : ""}</td>
|
||||||
<td>${esc(item.media_type)}</td>
|
<td>${esc(item.media_type)}</td>
|
||||||
<td><select data-select="${i}"><option value="">— überspringen —</option>${opts}</select></td>
|
<td class="row">
|
||||||
|
<select data-select="${i}"><option value="">— überspringen —</option>${opts}</select>
|
||||||
|
<button class="secondary" data-quick="${i}" title="Metadaten suchen und Anfrage direkt verbinden">🔍</button>
|
||||||
|
</td>
|
||||||
<td>${item.suggested_request_id ? item.score : "—"}</td>
|
<td>${item.suggested_request_id ? item.score : "—"}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
table.hidden = false;
|
table.hidden = false;
|
||||||
$("#import-btn").hidden = false;
|
$("#import-btn").hidden = false;
|
||||||
|
tbody.querySelectorAll("[data-quick]").forEach((b) =>
|
||||||
|
b.addEventListener("click", () => openQuickDialog(parseInt(b.dataset.quick)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- quick request from a scanned file ----
|
||||||
|
let quickItemIndex = null;
|
||||||
|
|
||||||
|
function cleanFileName(name) {
|
||||||
|
return name
|
||||||
|
.replace(/[._\-\[\]()]+/g, " ")
|
||||||
|
.replace(/\b(mp3|m4b|flac|epub|pdf|cbz|cbr|retail|unabridged|kompl.*)\b/gi, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openQuickDialog(i) {
|
||||||
|
quickItemIndex = i;
|
||||||
|
const item = scanItems[i];
|
||||||
|
$("#quick-file").textContent = item.name;
|
||||||
|
$("#quick-q").value = cleanFileName(item.name);
|
||||||
|
$("#quick-library").innerHTML =
|
||||||
|
libOptions(item.media_type) || "<option value=''>— keine Library —</option>";
|
||||||
|
$("#quick-results").innerHTML = "";
|
||||||
|
$("#quick-dialog").showModal();
|
||||||
|
runQuickSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runQuickSearch() {
|
||||||
|
const item = scanItems[quickItemIndex];
|
||||||
|
const box = $("#quick-results");
|
||||||
|
box.innerHTML = "<p class='muted'>Suche läuft…</p>";
|
||||||
|
try {
|
||||||
|
const results = await api(
|
||||||
|
`/api/search?media_type=${item.media_type}&q=${encodeURIComponent($("#quick-q").value)}`
|
||||||
|
);
|
||||||
|
if (!results.length) { box.innerHTML = "<p class='muted'>Keine Treffer — Suchbegriff anpassen.</p>"; return; }
|
||||||
|
box.innerHTML = results
|
||||||
|
.map(
|
||||||
|
(r, j) => `<div class="card">
|
||||||
|
${r.cover_url ? `<img src="${esc(r.cover_url)}" alt="">` : '<div class="nocover">?</div>'}
|
||||||
|
<div class="card-body">
|
||||||
|
<strong>${esc(r.title)}</strong>
|
||||||
|
<span>${esc(r.authors)}</span>
|
||||||
|
<span class="muted">${r.year ?? ""}</span>
|
||||||
|
<div class="row"><button data-pick="${j}">Anfragen & verbinden</button></div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
box.querySelectorAll("[data-pick]").forEach((btn) =>
|
||||||
|
btn.addEventListener("click", () => pickQuickResult(results[btn.dataset.pick]))
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
box.innerHTML = "";
|
||||||
|
toast(err.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#quick-search-form").addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
runQuickSearch();
|
||||||
|
});
|
||||||
|
$("#quick-close").addEventListener("click", () => $("#quick-dialog").close());
|
||||||
|
|
||||||
|
async function pickQuickResult(r) {
|
||||||
|
const libId = $("#quick-library").value;
|
||||||
|
if (!libId) { toast("Erst eine Library für diesen Typ anlegen", true); return; }
|
||||||
|
const i = quickItemIndex;
|
||||||
|
try {
|
||||||
|
const req = await api("/api/requests", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
library_id: parseInt(libId),
|
||||||
|
title: r.title, authors: r.authors, external_id: r.external_id,
|
||||||
|
year: r.year, series: r.series, cover_url: r.cover_url,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
missingReqs.push(req);
|
||||||
|
// add the new request to every dropdown of the same media type, then link this row
|
||||||
|
const item = scanItems[i];
|
||||||
|
scanItems.forEach((it, j) => {
|
||||||
|
if (it.media_type !== item.media_type) return;
|
||||||
|
const sel = document.querySelector(`[data-select="${j}"]`);
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = req.id;
|
||||||
|
opt.textContent = `${req.title} — ${req.authors} (${req.library_name})`;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
});
|
||||||
|
document.querySelector(`[data-select="${i}"]`).value = req.id;
|
||||||
|
document.querySelector(`[data-check="${i}"]`).checked = true;
|
||||||
|
$("#quick-dialog").close();
|
||||||
|
toast(`„${req.title}" angefragt und verbunden`);
|
||||||
|
} catch (err) { toast(err.message, true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#import-btn").addEventListener("click", async () => {
|
$("#import-btn").addEventListener("click", async () => {
|
||||||
|
|||||||
@@ -113,6 +113,24 @@
|
|||||||
<button id="import-btn" hidden>Ausgewählte importieren</button>
|
<button id="import-btn" hidden>Ausgewählte importieren</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="import-results"></div>
|
<div id="import-results"></div>
|
||||||
|
|
||||||
|
<dialog id="quick-dialog">
|
||||||
|
<h3>Suchen & Anfragen</h3>
|
||||||
|
<p class="muted mono" id="quick-file"></p>
|
||||||
|
<form id="quick-search-form" class="bar">
|
||||||
|
<input id="quick-q" placeholder="Titel/Serie suchen…" required />
|
||||||
|
<button type="submit">Suchen</button>
|
||||||
|
</form>
|
||||||
|
<label class="muted"
|
||||||
|
>Ziel-Library <select id="quick-library"></select
|
||||||
|
></label>
|
||||||
|
<div id="quick-results" class="cards"></div>
|
||||||
|
<div class="row">
|
||||||
|
<button type="button" id="quick-close" class="secondary">
|
||||||
|
Schließen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="view-settings" hidden>
|
<section id="view-settings" hidden>
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ td select { max-width: 340px; }
|
|||||||
dialog { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 1.2rem; min-width: 340px; }
|
dialog { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 1.2rem; min-width: 340px; }
|
||||||
dialog::backdrop { background: rgba(0,0,0,0.6); }
|
dialog::backdrop { background: rgba(0,0,0,0.6); }
|
||||||
dialog label { display: flex; flex-direction: column; gap: 0.2rem; margin: 0.5rem 0; font-size: 0.9rem; color: var(--muted); }
|
dialog label { display: flex; flex-direction: column; gap: 0.2rem; margin: 0.5rem 0; font-size: 0.9rem; color: var(--muted); }
|
||||||
|
#quick-dialog { min-width: 480px; max-width: 640px; }
|
||||||
|
#quick-results { max-height: 50vh; overflow-y: auto; margin-top: 0.6rem; }
|
||||||
.ok { color: #6fbf73; }
|
.ok { color: #6fbf73; }
|
||||||
.error { color: #e08585; }
|
.error { color: #e08585; }
|
||||||
#toast { position: fixed; bottom: 1.2rem; right: 1.2rem; background: var(--accent); color: #fff; padding: 0.7rem 1.1rem; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.4); }
|
#toast { position: fixed; bottom: 1.2rem; right: 1.2rem; background: var(--accent); color: #fff; padding: 0.7rem 1.1rem; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.4); }
|
||||||
|
|||||||
Reference in New Issue
Block a user