fix(ui/ux): keep the import search dialog inside the viewport

This commit is contained in:
Steppenstreuner
2026-08-29 04:55:08 +02:00
parent 60dd9e2f47
commit 714a435e2d
3 changed files with 53 additions and 24 deletions
+13 -9
View File
@@ -962,20 +962,24 @@ async function runQuickSearch() {
box.innerHTML = `<p class='muted'>${EMPTY_HINTS[item.media_type]}</p>`; box.innerHTML = `<p class='muted'>${EMPTY_HINTS[item.media_type]}</p>`;
return; return;
} }
// one compact row per hit: cover, title + a single meta line, action right
box.innerHTML = results box.innerHTML = results
.map( .map((r, j) => {
(r, j) => `<div class="card"> const meta = [
r.authors,
r.narrator ? "🎙 " + r.narrator : "",
r.series ? `📚 ${r.series}${r.volume != null ? " #" + r.volume : ""}` : "",
r.year ?? "",
].filter(Boolean).map(esc).join(" · ");
return `<div class="card">
${r.cover_url ? `<img src="${esc(r.cover_url)}" alt="" loading="lazy">` : '<div class="nocover">?</div>'} ${r.cover_url ? `<img src="${esc(r.cover_url)}" alt="" loading="lazy">` : '<div class="nocover">?</div>'}
<div class="card-body"> <div class="card-body">
<strong>${esc(r.title)}</strong> <strong>${esc(r.title)}</strong>
<span>${esc(r.authors)}</span> <span class="muted">${meta}</span>
${r.narrator ? `<span class="muted">🎙 ${esc(r.narrator)}</span>` : ""}
${r.series && r.media_type ? `<span class="muted">📚 ${esc(r.series)}${r.volume != null ? " #" + r.volume : ""}</span>` : ""}
<span class="muted">${r.year ?? ""}</span>
<div class="row"><button data-pick="${j}">Anfragen &amp; verbinden</button></div>
</div> </div>
</div>` <button data-pick="${j}" title="Anfrage anlegen und mit diesem Eintrag verbinden">Verbinden</button>
) </div>`;
})
.join(""); .join("");
box.querySelectorAll("[data-pick]").forEach((btn) => box.querySelectorAll("[data-pick]").forEach((btn) =>
btn.addEventListener("click", () => pickQuickResult(results[btn.dataset.pick])) btn.addEventListener("click", () => pickQuickResult(results[btn.dataset.pick]))
+19 -14
View File
@@ -202,21 +202,26 @@
<label class="muted" <label class="muted"
>Ziel-Library <select id="quick-library"></select >Ziel-Library <select id="quick-library"></select
></label> ></label>
<div id="quick-open-requests"></div> <div id="quick-scroll">
<div id="quick-results" class="cards"></div> <div id="quick-open-requests"></div>
<details id="quick-manual"> <div id="quick-results" class="cards view-list"></div>
<summary>Nicht bei Audible? Manuell anlegen</summary> </div>
<div class="bar wrap"> <div id="quick-foot">
<label>Titel <input id="quick-m-title" /></label> <details id="quick-manual">
<label>Serie <input id="quick-m-series" placeholder="optional" /></label> <summary>Nicht bei Audible? Manuell anlegen</summary>
<label>Folge/Band <input id="quick-m-volume" type="number" min="0" /></label> <div class="quick-manual-fields">
<label>Titel <input id="quick-m-title" /></label>
<label>Serie <input id="quick-m-series" placeholder="optional" /></label>
<label class="narrow"
>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
</button>
</div> </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
</button>
</div> </div>
</dialog> </dialog>
</section> </section>
+21 -1
View File
@@ -105,7 +105,27 @@ dialog label { display: flex; flex-direction: column; gap: 0.2rem; margin: 0.5re
.series-list input[type=checkbox] { flex: none; } .series-list input[type=checkbox] { flex: none; }
.series-list .num { flex: none; min-width: 3.2em; color: var(--muted); font-variant-numeric: tabular-nums; } .series-list .num { flex: none; min-width: 3.2em; color: var(--muted); font-variant-numeric: tabular-nums; }
.series-list .gap { padding: 0.35rem 0.6rem; color: var(--muted); font-size: 0.85rem; } .series-list .gap { padding: 0.35rem 0.6rem; color: var(--muted); font-size: 0.85rem; }
#quick-results { display: flex; flex-direction: column; max-height: 50vh; overflow-y: auto; margin-top: 0.6rem; } /* the dialog keeps head and foot in place; only the results scroll, so the
"manuell anlegen" panel stays reachable no matter how many hits came back */
#quick-dialog { display: flex; flex-direction: column; max-height: 85vh; width: min(680px, 92vw); }
#quick-scroll { flex: 1 1 auto; min-height: 4rem; overflow-y: auto; margin-top: 0.6rem; }
#quick-results { margin: 0; }
#quick-foot { flex: none; border-top: 1px solid var(--border); margin-top: 0.6rem; padding-top: 0.6rem; }
#quick-manual > summary { cursor: pointer; color: var(--accent); font-size: 0.9rem; padding: 0.2rem 0; }
#quick-manual[open] > summary { margin-bottom: 0.3rem; }
.quick-manual-fields { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.quick-manual-fields label { flex: 1 1 10rem; margin: 0.2rem 0; }
.quick-manual-fields label.narrow { flex: 0 0 6.5rem; }
.quick-manual-fields input { min-width: 0; width: 100%; }
#quick-search-form { gap: 0.5rem; }
#quick-q { flex: 1 1 auto; min-width: 0; }
#quick-file { word-break: break-word; }
/* one row per hit: cover | title + meta line | action */
#quick-results .card { align-items: center; gap: 0.7rem; padding: 0.45rem 0.6rem; }
#quick-results .card-body { flex: 1 1 auto; gap: 0.1rem; }
#quick-results .card-body strong { line-height: 1.25; }
#quick-results .card-body .muted { font-size: 0.78rem; }
#quick-results .card > button { flex: none; }
.ok { color: var(--ok); } .ok { color: var(--ok); }
.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); z-index: 10; } #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); z-index: 10; }