add(ui/ux): create a request manually from the import dialog

This commit is contained in:
Steppenstreuner
2026-08-28 21:23:32 +02:00
parent 60b9e44a11
commit 60dd9e2f47
3 changed files with 38 additions and 8 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ Sonarr/Radarr-Style Request- & Import-Manager für **Ebooks**, **Comics/Mangas**
1. **Anfragen**: Im Web-UI per Titel/Autor/ISBN suchen (Ebooks: Open Library · Audiobooks: Audible · Manga: AniList) oder manuell anlegen. Beim Request wählst du die **Ziel-Library** (z.B. deine 8 Bookorbit-Libraries oder die 3 Audiobookshelf-Libraries english/adult/kids). Der Titel erscheint als **Missing**.
Gehört ein Audiobook-Treffer zu einer Serie, legt **„Ganze Serie…"** alle Folgen auf einmal an (Folgennummer + echter Titel von Audible, optional auf einen Folgenbereich eingegrenzt). Audible hat keine Serien-Abfrage — wordarr sammelt die Folgen über mehrere Suchläufe ein; sollte eine fehlen, zeigt der Dialog die Lücke an.
2. **Download-Ordner**: wordarr überwacht keinen Downloader aktiv — du legst Dateien selbst in den Download-Ordner (`/mnt/downloads`).
3. **Import missing**: Im Tab *Import* den Ordner scannen. wordarr schlägt per Fuzzy-Matching Datei→Request-Zuordnungen vor (bei Serien zählt die Folgennummer im Ordnernamen plus der Serienname im Pfad, `???` und `Fragezeichen` gelten als dasselbe); du bestätigst oder korrigierst. Über 🔍 lässt sich zu einem Ordner direkt eine Anfrage suchen — eine führende Folgennummer (`017 - Titel`) wird dabei aus der Suchanfrage genommen, weil Audible sonst schlechter trifft, und stattdessen als Band/Folge übernommen. Beim Import wird die Datei nach dem Namensschema der Library **umbenannt und verschoben**. Ordner mit mehreren Audio-Dateien werden als ein Audiobook behandelt (`Titel - Part 01.mp3`, …). Auch Ordner, die in Disc-Unterordner (`CD`, `CD1`, `CD 2`, `Disc 3`, `Teil 1`) aufgeteilt sind, gelten als **ein** Audiobook — der Ordnername darüber liefert den Titel, die Parts werden über alle Discs hinweg durchnummeriert. Unterordner ohne Audio (`Cover`, Scans, Booklets) werden dabei ignoriert. Mehrteiler in Unterordnern (`Teil A`/`Teil B`, `A - Titel`/`B - Titel`) zählen ebenfalls als ein Hörbuch. Liegen die Teile dagegen **nebeneinander** (`100 - Toteninsel Teil 1`, `… Teil 2`, …), markierst du sie und klickst *Ausgewählte zusammenfassen* — sie werden als ein Hörbuch mit durchlaufenden Parts importiert (✂️ löst das wieder auf). Sind mehrere Einträge derselben Anfrage zugeordnet, fragt wordarr vor dem Import nach und fasst sie zusammen — sonst importiert nur der erste und der Rest scheitert. Fehlt einem bereits importierten Hörbuch später ein Teil, wählst du es im Zuordnungs-Dropdown unter *Bereits importiert — Teile anhängen* (↩︎); die neuen Dateien werden hinten angehängt und alle Tracks neu getaggt.
3. **Import missing**: Im Tab *Import* den Ordner scannen. wordarr schlägt per Fuzzy-Matching Datei→Request-Zuordnungen vor (bei Serien zählt die Folgennummer im Ordnernamen plus der Serienname im Pfad, `???` und `Fragezeichen` gelten als dasselbe); du bestätigst oder korrigierst. Über 🔍 lässt sich zu einem Ordner direkt eine Anfrage suchen — eine führende Folgennummer (`017 - Titel`) wird dabei aus der Suchanfrage genommen, weil Audible sonst schlechter trifft, und stattdessen als Band/Folge übernommen. Was Audible gar nicht führt (Live-Hörspiele, Fan-Editionen), legst du im selben Dialog unter *Nicht bei Audible? Manuell anlegen* an — Titel und Folge sind aus dem Ordnernamen vorbelegt, die Serie bleibt für den nächsten Eintrag stehen. Beim Import wird die Datei nach dem Namensschema der Library **umbenannt und verschoben**. Ordner mit mehreren Audio-Dateien werden als ein Audiobook behandelt (`Titel - Part 01.mp3`, …). Auch Ordner, die in Disc-Unterordner (`CD`, `CD1`, `CD 2`, `Disc 3`, `Teil 1`) aufgeteilt sind, gelten als **ein** Audiobook — der Ordnername darüber liefert den Titel, die Parts werden über alle Discs hinweg durchnummeriert. Unterordner ohne Audio (`Cover`, Scans, Booklets) werden dabei ignoriert. Mehrteiler in Unterordnern (`Teil A`/`Teil B`, `A - Titel`/`B - Titel`) zählen ebenfalls als ein Hörbuch. Liegen die Teile dagegen **nebeneinander** (`100 - Toteninsel Teil 1`, `… Teil 2`, …), markierst du sie und klickst *Ausgewählte zusammenfassen* — sie werden als ein Hörbuch mit durchlaufenden Parts importiert (✂️ löst das wieder auf). Sind mehrere Einträge derselben Anfrage zugeordnet, fragt wordarr vor dem Import nach und fasst sie zusammen — sonst importiert nur der erste und der Rest scheitert. Fehlt einem bereits importierten Hörbuch später ein Teil, wählst du es im Zuordnungs-Dropdown unter *Bereits importiert — Teile anhängen* (↩︎); die neuen Dateien werden hinten angehängt und alle Tracks neu getaggt.
## Setup (Docker)
+28 -7
View File
@@ -861,6 +861,7 @@ function splitMergedItem(i) {
// ---- quick request from a scanned file ----
let quickItemIndex = null;
let quickEpisode = null;
let lastSeries = "";
// "017 - Titel", "Folge 17: Titel", "[003] Titel" -> episode number + rest.
// A bare number without a separator ("100 Stunden") or without a leading zero is
@@ -901,6 +902,10 @@ function openQuickDialog(i) {
$("#quick-file").textContent = (item.rel_dir ? item.rel_dir + "/" : "") + item.name;
$("#quick-episode").textContent = number != null ? `Folge ${number} — nicht mitgesucht` : "";
$("#quick-q").value = cleanFileName(rest);
$("#quick-m-title").value = cleanFileName(rest);
$("#quick-m-volume").value = number ?? "";
$("#quick-m-series").value = lastSeries;
$("#quick-manual").open = false;
$("#quick-library").innerHTML =
libOptions(item.media_type) || "<option value=''>— keine Library —</option>";
$("#quick-results").innerHTML = "";
@@ -987,17 +992,13 @@ $("#quick-search-form").addEventListener("submit", (e) => {
});
$("#quick-close").addEventListener("click", () => $("#quick-dialog").close());
async function pickQuickResult(r) {
// create the request and wire it to the scanned entry the dialog was opened for
async function requestAndConnect(body) {
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 createRequest({
library_id: parseInt(libId),
title: r.title, authors: r.authors, narrator: r.narrator || "",
external_id: r.external_id, volume: r.volume ?? quickEpisode,
year: r.year, series: r.series, cover_url: r.cover_url,
});
const req = await createRequest({ library_id: parseInt(libId), ...body });
setLastLib(scanItems[i].media_type, libId);
missingReqs.push(req);
scanItems[i].request_id = req.id;
@@ -1009,6 +1010,26 @@ async function pickQuickResult(r) {
} catch (err) { toast(err.message, true); }
}
function pickQuickResult(r) {
return requestAndConnect({
title: r.title, authors: r.authors, narrator: r.narrator || "",
external_id: r.external_id, volume: r.volume ?? quickEpisode,
year: r.year, series: r.series, cover_url: r.cover_url,
});
}
// live shows, box sets and fan releases are not in the Audible catalogue at all
$("#quick-m-submit").addEventListener("click", () => {
const title = $("#quick-m-title").value.trim();
if (!title) { toast("Titel angeben", true); return; }
const volume = $("#quick-m-volume").value;
return requestAndConnect({
title,
series: (lastSeries = $("#quick-m-series").value.trim()),
volume: volume ? parseInt(volume) : null,
});
});
// ---- import execution (batched, with progress) ----
const IMPORT_BATCH_SIZE = 20;
+9
View File
@@ -204,6 +204,15 @@
></label>
<div id="quick-open-requests"></div>
<div id="quick-results" class="cards"></div>
<details id="quick-manual">
<summary>Nicht bei Audible? Manuell anlegen</summary>
<div class="bar wrap">
<label>Titel <input id="quick-m-title" /></label>
<label>Serie <input id="quick-m-series" placeholder="optional" /></label>
<label>Folge/Band <input id="quick-m-volume" type="number" min="0" /></label>
</div>
<button type="button" id="quick-m-submit">Anlegen &amp; verbinden</button>
</details>
<div class="row">
<button type="button" id="quick-close" class="secondary">
Schließen