fix(core): don't change unicode characters

This commit is contained in:
Steppenstreuner
2026-08-28 18:52:06 +02:00
parent 6eaab5bc5d
commit 6b2c92e367
6 changed files with 101 additions and 29 deletions
+67 -23
View File
@@ -11,11 +11,27 @@ async function api(path, opts = {}) {
if (!resp.ok) {
let msg = resp.statusText;
try { msg = (await resp.json()).detail || msg; } catch {}
throw new Error(msg);
const err = new Error(msg);
err.status = resp.status;
throw err;
}
return resp.json();
}
// create a request; on a duplicate warning (409) ask the user before retrying
async function createRequest(body) {
try {
return await api("/api/requests", { method: "POST", body: JSON.stringify(body) });
} catch (err) {
if (err.status === 409 && confirm(err.message + "\nTrotzdem anlegen?")) {
return api("/api/requests?allow_duplicate=true", {
method: "POST", body: JSON.stringify(body),
});
}
throw err;
}
}
function toast(msg, isError = false) {
const t = $("#toast");
t.textContent = msg;
@@ -216,15 +232,12 @@ $("#search-form").addEventListener("submit", async (e) => {
if (!libId) { toast("Erst eine Library für diesen Typ anlegen (Tab Libraries)", true); return; }
const volInput = box.querySelector(`[data-vol="${i}"]`);
try {
await api("/api/requests", {
method: "POST",
body: JSON.stringify({
await createRequest({
library_id: parseInt(libId),
title: r.title, authors: r.authors, narrator: r.narrator || "",
external_id: r.external_id,
year: r.year, series: r.series, cover_url: r.cover_url,
volume: volInput && volInput.value ? parseInt(volInput.value) : (r.volume ?? null),
}),
});
setLastLib(type, libId);
btn.textContent = "✓ Angefragt";
@@ -277,16 +290,13 @@ $("#manual-form").addEventListener("submit", async (e) => {
});
toast(`${created.length} Anfragen angelegt`);
} else {
await api("/api/requests", {
method: "POST",
body: JSON.stringify({
library_id: parseInt(data.library_id),
title: data.title, authors: data.authors, series: data.series,
narrator: data.narrator || "",
external_id: data.external_id,
year: data.year ? parseInt(data.year) : null,
volume: data.volume ? parseInt(data.volume) : null,
}),
await createRequest({
library_id: parseInt(data.library_id),
title: data.title, authors: data.authors, series: data.series,
narrator: data.narrator || "",
external_id: data.external_id,
year: data.year ? parseInt(data.year) : null,
volume: data.volume ? parseInt(data.volume) : null,
});
toast("Anfrage angelegt");
}
@@ -675,10 +685,47 @@ function openQuickDialog(i) {
$("#quick-library").innerHTML =
libOptions(item.media_type) || "<option value=''>— keine Library —</option>";
$("#quick-results").innerHTML = "";
renderQuickOpenRequests(item);
$("#quick-dialog").showModal();
runQuickSearch();
}
// offer linking to similar already-open requests instead of creating a duplicate
function renderQuickOpenRequests(item) {
const box = $("#quick-open-requests");
const tokens = cleanFileName(item.name).toLowerCase().split(" ").filter((t) => t.length > 2);
const matches = missingReqs
.filter((r) => r.media_type === item.media_type)
.map((r) => {
const hay = `${r.title} ${r.authors} ${r.series} ${r.narrator}`.toLowerCase();
return { r, hits: tokens.filter((t) => hay.includes(t)).length };
})
.filter((m) => m.hits >= 2)
.sort((a, b) => b.hits - a.hits)
.slice(0, 3);
if (!matches.length) { box.innerHTML = ""; return; }
box.innerHTML =
'<p class="muted">Passende offene Requests:</p>' +
matches
.map(
({ r }) => `<div class="row open-req">
<span>${esc(r.title)}${r.volume != null ? " · Band " + r.volume : ""}${esc(r.authors)} (${esc(r.library_name)})</span>
<button class="secondary" data-link-req="${r.id}">Verbinden</button>
</div>`
)
.join("");
box.querySelectorAll("[data-link-req]").forEach((b) =>
b.addEventListener("click", () => {
const i = quickItemIndex;
scanItems[i].request_id = parseInt(b.dataset.linkReq);
scanItems[i].checked = true;
renderImportTable();
$("#quick-dialog").close();
toast("Mit bestehendem Request verbunden");
})
);
}
async function runQuickSearch() {
const item = scanItems[quickItemIndex];
const box = $("#quick-results");
@@ -726,14 +773,11 @@ async function pickQuickResult(r) {
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, narrator: r.narrator || "",
external_id: r.external_id, volume: r.volume ?? null,
year: r.year, series: r.series, cover_url: r.cover_url,
}),
const req = await createRequest({
library_id: parseInt(libId),
title: r.title, authors: r.authors, narrator: r.narrator || "",
external_id: r.external_id, volume: r.volume ?? null,
year: r.year, series: r.series, cover_url: r.cover_url,
});
setLastLib(scanItems[i].media_type, libId);
missingReqs.push(req);
+1
View File
@@ -179,6 +179,7 @@
<label class="muted"
>Ziel-Library <select id="quick-library"></select
></label>
<div id="quick-open-requests"></div>
<div id="quick-results" class="cards"></div>
<div class="row">
<button type="button" id="quick-close" class="secondary">