Make the registry search live
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-10-14 11:13:38 +02:00
1 parent 7cdb431fd4
commit bb736e8258
8 files changed
+220 -77

No files matched your search

+3 -1
View File
@@ -8,7 +8,9 @@
**Geprüft:** Lokal mit 63 Demo-Installationen im Browser: Seite 2 von 3, Suche + Filter kombiniert (8 Treffer), kein Treffer, Seitenzahl außerhalb des Bereichs wird begrenzt, 375 px ohne Querscrollen. `php scripts/zentrale-check.php` grün. **Geprüft:** Lokal mit 63 Demo-Installationen im Browser: Seite 2 von 3, Suche + Filter kombiniert (8 Treffer), kein Treffer, Seitenzahl außerhalb des Bereichs wird begrenzt, 375 px ohne Querscrollen. `php scripts/zentrale-check.php` grün.
**Nächster Schritt:** Umut lädt `zentrale/app/` und `zentrale/public/app.css` neu hoch (Paket `var/zentrale-upload.zip`); `config.php` und `var/` bleiben. **Nachtrag, gleicher Tag – Live-Suche:** Umut: „Suche sollte Ajax-Livesearch sein, nicht Enter-Suche“. `zentrale/public/app.js` lädt beim Tippen (250 ms Pause) nur die Liste nach (`/?…&teil=liste`, Vorlage `_liste.php`), ebenso bei Klick auf Kacheln und Seitenzahlen; Adresse per `history`, Zurück-Taste funktioniert, Trefferzeile bleibt Statusmeldung für Screenreader, abgelaufene Sitzung fällt auf normales Laden zurück. Der Suchen-Knopf verschwindet mit JavaScript. CSP erlaubt jetzt `script-src 'self'` und `connect-src 'self'`. Nebenbei behoben: `.knopf` überschrieb das `hidden`-Attribut. Geprüft im Browser mit 57 Demo-Installationen: Tippen ohne Neuladen, Filter + Suche, Zurücksetzen, Seite 2, Zurück-Taste, 375 px, keine Konsolenfehler. Die lokale Demo-Datenbank ist wieder gelöscht.
**Nächster Schritt:** Umut lädt `zentrale/app/` und `zentrale/public/` neu hoch (Paket `var/zentrale-upload.zip`); `config.php` und `var/` auf dem Server bleiben unangetastet.
## Zentrale sites.kant-it.de: Meldung und Update aus der Ferne (14.10.2026) ## Zentrale sites.kant-it.de: Meldung und Update aus der Ferne (14.10.2026)
+3 -1
View File
@@ -41,7 +41,9 @@ Zugangsdaten. Die Zentrale speichert zusätzlich die IP-Adresse der letzten Meld
Die Zählerkacheln oben sind zugleich Filter (aktuell, veraltet, verstummt, ohne Kunde). Die Suche Die Zählerkacheln oben sind zugleich Filter (aktuell, veraltet, verstummt, ohne Kunde). Die Suche
findet Domain, frühere Domain, Kunde/Notiz, Fassung und IP-Adresse; Suche und Filter lassen sich findet Domain, frühere Domain, Kunde/Notiz, Fassung und IP-Adresse; Suche und Filter lassen sich
kombinieren. 25 Installationen je Seite, Domains natürlich sortiert und ohne „www.“. kombinieren. Die Suche filtert schon beim Tippen; Kacheln und Seitenzahlen wechseln nur die
Liste (`public/app.js`, ohne JavaScript funktioniert alles als normales Formular). 25 Installationen
je Seite, Domains natürlich sortiert und ohne „www.“.
## Update aus der Ferne ## Update aus der Ferne
+19 -6
View File
@@ -154,8 +154,18 @@ final class Anwendung
$seiten = max(1, (int) ceil(count($treffer) / $proSeite)); $seiten = max(1, (int) ceil(count($treffer) / $proSeite));
$seite = min($seiten, max(1, (int) ($_GET['seite'] ?? 1))); $seite = min($seiten, max(1, (int) ($_GET['seite'] ?? 1)));
$this->zeigen('uebersicht', [ $namen = ['aktuell' => 'aktuell', 'veraltet' => 'veraltet', 'still' => 'ohne Meldung seit 48 h', 'unbekannt' => 'ohne Kundenzuordnung'];
$von = [] === $treffer ? 0 : ($seite - 1) * $proSeite + 1;
$bis = min(count($treffer), $seite * $proSeite);
$trefferText = [] === $treffer ? 'Keine Treffer.' : sprintf('%d–%d von %d %s', $von, $bis, count($treffer), 1 === count($treffer) ? 'Installation' : 'Installationen')
.(null !== $gewaehlt ? ' · Filter: '.$namen[$gewaehlt] : '')
.('' !== $suche ? ' · Suche: „'.$suche.'“' : '');
// Die Live-Suche holt nur die Liste; ohne JavaScript kommt die ganze Seite.
$teil = 'liste' === ($_GET['teil'] ?? null);
$this->zeigen($teil ? '_liste' : 'uebersicht', [
'titel' => 'Installationen', 'titel' => 'Installationen',
'trefferText' => $trefferText,
'neueste' => $neueste, 'neueste' => $neueste,
'installationen' => array_slice($treffer, ($seite - 1) * $proSeite, $proSeite), 'installationen' => array_slice($treffer, ($seite - 1) * $proSeite, $proSeite),
'treffer' => count($treffer), 'treffer' => count($treffer),
@@ -163,12 +173,10 @@ final class Anwendung
'filter' => $gewaehlt, 'filter' => $gewaehlt,
'seite' => $seite, 'seite' => $seite,
'seiten' => $seiten, 'seiten' => $seiten,
'von' => [] === $treffer ? 0 : ($seite - 1) * $proSeite + 1,
'bis' => min(count($treffer), $seite * $proSeite),
'zahlen' => $zahlen, 'zahlen' => $zahlen,
// Verstummte bleiben außen vor: Sie holen den Auftrag womöglich nie ab. Wartende haben schon einen. // Verstummte bleiben außen vor: Sie holen den Auftrag womöglich nie ab. Wartende haben schon einen.
'aktualisierbar' => array_values(array_filter($alle, static fn (array $i): bool => $i['kann_update'] && !$i['still'] && 'wartet' !== ($i['auftrag_zustand']['art'] ?? null))), 'aktualisierbar' => array_values(array_filter($alle, static fn (array $i): bool => $i['kann_update'] && !$i['still'] && 'wartet' !== ($i['auftrag_zustand']['art'] ?? null))),
]); ], true, $teil);
} }
private function detail(Bestand $bestand, Fassungen $fassungen): void private function detail(Bestand $bestand, Fassungen $fassungen): void
@@ -353,7 +361,7 @@ final class Anwendung
private function kopfzeilen(): void private function kopfzeilen(): void
{ {
header("Content-Security-Policy: default-src 'none'; style-src 'self'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"); header("Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; style-src 'self'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'; base-uri 'none'");
header('X-Content-Type-Options: nosniff'); header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: no-referrer'); header('Referrer-Policy: no-referrer');
header('X-Robots-Tag: noindex, nofollow'); header('X-Robots-Tag: noindex, nofollow');
@@ -361,9 +369,14 @@ final class Anwendung
} }
/** @param array<string, mixed> $daten */ /** @param array<string, mixed> $daten */
private function zeigen(string $vorlage, array $daten, bool $angemeldet = true): void private function zeigen(string $vorlage, array $daten, bool $angemeldet = true, bool $ohneRahmen = false): void
{ {
header('Content-Type: text/html; charset=UTF-8'); header('Content-Type: text/html; charset=UTF-8');
if ($ohneRahmen) {
echo (new Ansicht((string) ($_SESSION['csrf'] ?? '')))->vorlage($this->wurzel.'/app/vorlagen/'.$vorlage.'.php', $daten);
return;
}
$hinweis = $_SESSION['hinweis'] ?? null; $hinweis = $_SESSION['hinweis'] ?? null;
unset($_SESSION['hinweis']); unset($_SESSION['hinweis']);
$v = new Ansicht((string) ($_SESSION['csrf'] ?? '')); $v = new Ansicht((string) ($_SESSION['csrf'] ?? ''));
+66
View File
@@ -0,0 +1,66 @@
<?php /** @var Zentrale\Ansicht $v */ ?>
<div id="liste" tabindex="-1" data-treffer="<?= $v->e($trefferText) ?>">
<?php if (0 === $zahlen['alle']) { ?>
<div class="karte leer">
<h2>Noch keine Meldung</h2>
<p>Sobald eine KantSites-Installation ab Fassung 1.36.0 läuft, meldet sie sich hier – spätestens 15 Minuten nach dem ersten Besuch oder Cron-Aufruf.</p>
</div>
<?php } elseif (0 === $treffer) { ?>
<div class="karte leer">
<h2>Nichts gefunden</h2>
<p>Keine Installation passt zu Suche und Filter. <a href="/">Alle anzeigen</a></p>
</div>
<?php } else { ?>
<div class="tabelle-rahmen">
<table class="tabelle">
<caption class="unsichtbar">Gemeldete Installationen, Seite <?= $seite ?> von <?= $seiten ?></caption>
<thead>
<tr><th scope="col">Domain</th><th scope="col">Kunde</th><th scope="col">Fassung</th><th scope="col">Letzte Meldung</th><th scope="col">Zustand</th><th scope="col"><span class="unsichtbar">Aktion</span></th></tr>
</thead>
<tbody>
<?php foreach ($installationen as $i) { ?>
<tr>
<th scope="row" data-label="Domain"><a href="/installation?id=<?= $v->e(rawurlencode($i['id'])) ?>"><?= $v->e($i['domain']) ?></a>
<?php if (null !== $i['domain_vorher']) { ?><span class="klein">vorher <?= $v->e($i['domain_vorher']) ?></span><?php } ?></th>
<td data-label="Kunde"><?= $i['unbekannt'] ? '<span class="marke-fremd">Unbekannt</span>' : $v->e($i['notiz']) ?></td>
<td data-label="Fassung"><span class="fassung <?= $i['veraltet'] ? 'f-alt' : 'f-ok' ?>"><?= $v->e($i['fassung']) ?></span><?php if ('git' === $i['art']) { ?> <span class="klein">Git</span><?php } ?></td>
<td data-label="Letzte Meldung"><span class="<?= $i['still'] ? 'still' : '' ?>"><?= $v->e($v->vor($i['alter'])) ?></span></td>
<td data-label="Zustand">
<?php if ($i['wartung']) { ?><span class="pille p-warn">Wartung</span> <?php } ?>
<?php if (null !== $i['auftrag_zustand']) { ?><span class="pille p-<?= $v->e($i['auftrag_zustand']['art']) ?>"><?= $v->e($i['auftrag_zustand']['text']) ?></span><?php } elseif (!$i['wartung']) { ?><span class="klein">–</span><?php } ?>
</td>
<td class="aktion">
<?php if ($i['kann_update'] && null !== $neueste && 'wartet' !== ($i['auftrag_zustand']['art'] ?? null)) { ?>
<form method="post" action="/auftrag">
<?= $v->csrf() ?>
<input type="hidden" name="id" value="<?= $v->e($i['id']) ?>">
<button type="submit" class="knopf knopf-klein">Auf <?= $v->e($neueste['version']) ?><span class="unsichtbar"> aktualisieren: <?= $v->e($i['domain']) ?></span></button>
</form>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php if ($seiten > 1) { ?>
<nav class="seiten" aria-label="Seiten">
<?php $basis = ['q' => $suche, 'filter' => $filter]; ?>
<?php if ($seite > 1) { ?><a class="knopf knopf-leise" href="<?= $v->e($v->adresse([...$basis, 'seite' => $seite - 1])) ?>" rel="prev">← Zurück</a><?php } ?>
<ul>
<?php
// Erste, letzte und zwei um die aktuelle; dazwischen Auslassungen.
$zeigen = array_unique(array_filter([1, $seite - 2, $seite - 1, $seite, $seite + 1, $seite + 2, $seiten], static fn (int $n): bool => $n >= 1 && $n <= $seiten));
sort($zeigen);
$vorher = 0;
foreach ($zeigen as $n) {
if ($n > $vorher + 1) { ?><li aria-hidden="true">…</li><?php }
$vorher = $n; ?>
<li><?php if ($n === $seite) { ?><span aria-current="page"><span class="unsichtbar">Seite </span><?= $n ?></span><?php } else { ?><a href="<?= $v->e($v->adresse([...$basis, 'seite' => $n])) ?>"><span class="unsichtbar">Seite </span><?= $n ?></a><?php } ?></li>
<?php } ?>
</ul>
<?php if ($seite < $seiten) { ?><a class="knopf knopf-leise" href="<?= $v->e($v->adresse([...$basis, 'seite' => $seite + 1])) ?>" rel="next">Weiter →</a><?php } ?>
</nav>
<?php } ?>
<?php } ?>
</div>
+2 -1
View File
@@ -6,7 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<title><?= $v->e($titel) ?> · KantSites Zentrale</title> <title><?= $v->e($titel) ?> · KantSites Zentrale</title>
<link rel="stylesheet" href="/app.css?v=1"> <link rel="stylesheet" href="/app.css?v=3">
<script src="/app.js?v=1" defer></script>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23173f6b'/%3E%3Cpath d='M9 8v16M9 16l9-8M12.5 13l7 11' stroke='%23fff' stroke-width='3.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23173f6b'/%3E%3Cpath d='M9 8v16M9 16l9-8M12.5 13l7 11' stroke='%23fff' stroke-width='3.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E">
</head> </head>
<body> <body>
+4 -68
View File
@@ -43,76 +43,12 @@ $kacheln = [
<?php if (null !== $filter) { ?><input type="hidden" name="filter" value="<?= $v->e($filter) ?>"><?php } ?> <?php if (null !== $filter) { ?><input type="hidden" name="filter" value="<?= $v->e($filter) ?>"><?php } ?>
<label for="q">Suchen</label> <label for="q">Suchen</label>
<div class="suche-zeile"> <div class="suche-zeile">
<input id="q" name="q" type="search" value="<?= $v->e($suche) ?>" placeholder="Domain, Kunde, Fassung oder IP" aria-describedby="treffer"> <input id="q" name="q" type="search" value="<?= $v->e($suche) ?>" placeholder="Domain, Kunde, Fassung oder IP" autocomplete="off" aria-describedby="treffer" aria-controls="liste">
<button type="submit" class="knopf">Suchen</button> <button type="submit" class="knopf">Suchen</button>
<?php if ('' !== $suche || null !== $filter) { ?><a class="knopf knopf-leise" href="/">Zurücksetzen</a><?php } ?> <a class="knopf knopf-leise zuruecksetzen" href="/"<?= '' === $suche && null === $filter ? ' hidden' : '' ?>>Zurücksetzen</a>
</div> </div>
<p class="klein" id="treffer" role="status"> <p class="klein" id="treffer" role="status"><?= $v->e($trefferText) ?></p>
<?php if (0 === $treffer) { ?>Keine Treffer.<?php } else { ?><?= $von ?>–<?= $bis ?> von <?= $treffer ?> <?= 1 === $treffer ? 'Installation' : 'Installationen' ?><?= null !== $filter ? ' · Filter: '.$v->e($kacheln[$filter][1]) : '' ?><?= '' !== $suche ? ' · Suche: „'.$v->e($suche).'“' : '' ?><?php } ?>
</p>
</form> </form>
<?php } ?> <?php } ?>
<?php if (0 === $zahlen['alle']) { ?> <?php require __DIR__.'/_liste.php'; ?>
<div class="karte leer">
<h2>Noch keine Meldung</h2>
<p>Sobald eine KantSites-Installation ab Fassung 1.36.0 läuft, meldet sie sich hier – spätestens 15 Minuten nach dem ersten Besuch oder Cron-Aufruf.</p>
</div>
<?php } elseif (0 === $treffer) { ?>
<div class="karte leer">
<h2>Nichts gefunden</h2>
<p>Keine Installation passt zu Suche und Filter. <a href="/">Alle anzeigen</a></p>
</div>
<?php } else { ?>
<div class="tabelle-rahmen">
<table class="tabelle">
<caption class="unsichtbar">Gemeldete Installationen, Seite <?= $seite ?> von <?= $seiten ?></caption>
<thead>
<tr><th scope="col">Domain</th><th scope="col">Kunde</th><th scope="col">Fassung</th><th scope="col">Letzte Meldung</th><th scope="col">Zustand</th><th scope="col"><span class="unsichtbar">Aktion</span></th></tr>
</thead>
<tbody>
<?php foreach ($installationen as $i) { ?>
<tr>
<th scope="row" data-label="Domain"><a href="/installation?id=<?= $v->e(rawurlencode($i['id'])) ?>"><?= $v->e($i['domain']) ?></a>
<?php if (null !== $i['domain_vorher']) { ?><span class="klein">vorher <?= $v->e($i['domain_vorher']) ?></span><?php } ?></th>
<td data-label="Kunde"><?= $i['unbekannt'] ? '<span class="marke-fremd">Unbekannt</span>' : $v->e($i['notiz']) ?></td>
<td data-label="Fassung"><span class="fassung <?= $i['veraltet'] ? 'f-alt' : 'f-ok' ?>"><?= $v->e($i['fassung']) ?></span><?php if ('git' === $i['art']) { ?> <span class="klein">Git</span><?php } ?></td>
<td data-label="Letzte Meldung"><span class="<?= $i['still'] ? 'still' : '' ?>"><?= $v->e($v->vor($i['alter'])) ?></span></td>
<td data-label="Zustand">
<?php if ($i['wartung']) { ?><span class="pille p-warn">Wartung</span> <?php } ?>
<?php if (null !== $i['auftrag_zustand']) { ?><span class="pille p-<?= $v->e($i['auftrag_zustand']['art']) ?>"><?= $v->e($i['auftrag_zustand']['text']) ?></span><?php } elseif (!$i['wartung']) { ?><span class="klein">–</span><?php } ?>
</td>
<td class="aktion">
<?php if ($i['kann_update'] && null !== $neueste && 'wartet' !== ($i['auftrag_zustand']['art'] ?? null)) { ?>
<form method="post" action="/auftrag">
<?= $v->csrf() ?>
<input type="hidden" name="id" value="<?= $v->e($i['id']) ?>">
<button type="submit" class="knopf knopf-klein">Auf <?= $v->e($neueste['version']) ?><span class="unsichtbar"> aktualisieren: <?= $v->e($i['domain']) ?></span></button>
</form>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php if ($seiten > 1) { ?>
<nav class="seiten" aria-label="Seiten">
<?php $basis = ['q' => $suche, 'filter' => $filter]; ?>
<?php if ($seite > 1) { ?><a class="knopf knopf-leise" href="<?= $v->e($v->adresse([...$basis, 'seite' => $seite - 1])) ?>" rel="prev">← Zurück</a><?php } ?>
<ul>
<?php
// Erste, letzte und zwei um die aktuelle; dazwischen Auslassungen.
$zeigen = array_unique(array_filter([1, $seite - 2, $seite - 1, $seite, $seite + 1, $seite + 2, $seiten], static fn (int $n): bool => $n >= 1 && $n <= $seiten));
sort($zeigen);
$vorher = 0;
foreach ($zeigen as $n) {
if ($n > $vorher + 1) { ?><li aria-hidden="true">…</li><?php }
$vorher = $n; ?>
<li><?php if ($n === $seite) { ?><span aria-current="page"><span class="unsichtbar">Seite </span><?= $n ?></span><?php } else { ?><a href="<?= $v->e($v->adresse([...$basis, 'seite' => $n])) ?>"><span class="unsichtbar">Seite </span><?= $n ?></a><?php } ?></li>
<?php } ?>
</ul>
<?php if ($seite < $seiten) { ?><a class="knopf knopf-leise" href="<?= $v->e($v->adresse([...$basis, 'seite' => $seite + 1])) ?>" rel="next">Weiter →</a><?php } ?>
</nav>
<?php } ?>
<?php } ?>
+5
View File
@@ -21,6 +21,8 @@
* { box-sizing: border-box; } * { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; } html { -webkit-text-size-adjust: 100%; }
body { body {
@@ -176,3 +178,6 @@ input[type="text"]:focus-visible, input[type="password"]:focus-visible { border-
.fakten { grid-template-columns: 1fr; gap: 0; } .fakten { grid-template-columns: 1fr; gap: 0; }
.fakten dd { margin-bottom: 8px; } .fakten dd { margin-bottom: 8px; }
} }
#liste:focus { outline: none; }
#liste[aria-busy="true"] { opacity: .6; transition: opacity .15s; }
+118
View File
@@ -0,0 +1,118 @@
// Live-Suche der Übersicht: Tippen, Filter und Seitenwechsel laden nur die Liste nach.
// Ohne JavaScript bleibt alles ein gewöhnliches Formular mit Links.
(() => {
const form = document.querySelector('form.suche');
const liste = document.getElementById('liste');
if (!form || !liste || !window.fetch || !window.URL) {
return;
}
const feld = form.querySelector('#q');
const status = document.getElementById('treffer');
const zuruecksetzen = form.querySelector('.zuruecksetzen');
const knopf = form.querySelector('button[type="submit"]');
const filterFeld = () => form.querySelector('input[name="filter"]');
knopf.hidden = true;
let warten = 0;
let laufend = null;
/** Die Adresse aus Suchfeld und Filter, ohne leere Werte. */
const adresseAusFormular = () => {
const url = new URL(form.action, location.href);
const q = feld.value.trim();
if (q !== '') url.searchParams.set('q', q);
const filter = filterFeld();
if (filter && filter.value !== '') url.searchParams.set('filter', filter.value);
return url;
};
/** Kacheln und Filterfeld an Suche und Filter der neuen Adresse angleichen. */
const umgebungAnpassen = (url) => {
const q = url.searchParams.get('q') || '';
const filter = url.searchParams.get('filter') || '';
document.querySelectorAll('.zahlen a').forEach((a) => {
const ziel = new URL(a.href, location.href);
if (q === '') ziel.searchParams.delete('q'); else ziel.searchParams.set('q', q);
ziel.searchParams.delete('seite');
a.href = ziel.pathname + ziel.search;
const gewaehlt = (ziel.searchParams.get('filter') || '') === filter;
a.parentElement.classList.toggle('gewaehlt', gewaehlt);
if (gewaehlt) a.setAttribute('aria-current', 'true'); else a.removeAttribute('aria-current');
});
let feldFilter = filterFeld();
if (filter !== '' && !feldFilter) {
feldFilter = document.createElement('input');
feldFilter.type = 'hidden';
feldFilter.name = 'filter';
form.prepend(feldFilter);
}
if (feldFilter) {
if (filter === '') feldFilter.remove(); else feldFilter.value = filter;
}
if (feld.value.trim() !== q) feld.value = q;
zuruecksetzen.hidden = q === '' && filter === '';
};
const laden = (url, verlauf) => {
if (laufend) laufend.abort();
laufend = new AbortController();
const abruf = new URL(url, location.href);
abruf.searchParams.set('teil', 'liste');
liste.setAttribute('aria-busy', 'true');
fetch(abruf, { signal: laufend.signal, credentials: 'same-origin', headers: { Accept: 'text/html' } })
.then((antwort) => {
// Abgelaufene Sitzung: Die Antwort ist die Anmeldeseite, nicht die Liste.
if (!antwort.ok || antwort.redirected) throw new Error('voll');
return antwort.text();
})
.then((html) => {
const neu = new DOMParser().parseFromString(html, 'text/html').getElementById('liste');
if (!neu) throw new Error('voll');
liste.replaceChildren(...neu.childNodes);
liste.dataset.treffer = neu.dataset.treffer || '';
status.textContent = liste.dataset.treffer;
const sichtbar = new URL(url, location.href);
if (verlauf === 'push') history.pushState(null, '', sichtbar.pathname + sichtbar.search);
else if (verlauf === 'replace') history.replaceState(null, '', sichtbar.pathname + sichtbar.search);
umgebungAnpassen(sichtbar);
})
.catch((fehler) => {
if (fehler.name !== 'AbortError') location.href = url.toString();
})
.finally(() => liste.removeAttribute('aria-busy'));
};
feld.addEventListener('input', () => {
clearTimeout(warten);
warten = setTimeout(() => laden(adresseAusFormular(), 'replace'), 250);
});
form.addEventListener('submit', (ereignis) => {
ereignis.preventDefault();
clearTimeout(warten);
laden(adresseAusFormular(), 'replace');
});
zuruecksetzen.addEventListener('click', (ereignis) => {
ereignis.preventDefault();
feld.value = '';
const filter = filterFeld();
if (filter) filter.remove();
laden(new URL('/', location.href), 'push');
feld.focus();
});
// Filterkacheln und Seitenzahlen: nur die Liste wechseln.
document.addEventListener('click', (ereignis) => {
const link = ereignis.target.closest('.zahlen a, #liste .seiten a');
if (!link || ereignis.ctrlKey || ereignis.metaKey || ereignis.shiftKey || ereignis.button !== 0) return;
ereignis.preventDefault();
laden(new URL(link.href, location.href), 'push');
if (link.closest('.seiten')) {
form.scrollIntoView({ block: 'start' });
liste.focus({ preventScroll: true });
}
});
window.addEventListener('popstate', () => laden(new URL(location.href), null));
})();