Back up unsaved editor work locally, structure the settings, fix archive accessibility

- The editor keeps unsaved changes in the browser and offers them
  after a crash, a closed tab or an expired session. No timed save on
  the server: every save is a full revision.
- The save status stays visible in narrow windows.
- Settings get a sticky section bar, two folded groups and a save bar
  that stays in view.
- The admin post list fetches terms and images once, not per post.
- Archive post titles are second-level headings, the archive head
  keeps full text contrast, the skip link sits above a sticky header,
  a linked image without alternative text names its link.
- The editor check starts through the dev router like the others.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Fable 5.2 committed 2026-11-10 13:01:23 +01:00
1 parent 5e46c98825
commit b08862b139
17 files changed
+182 -26

No files matched your search

+2 -1
View File
@@ -130,7 +130,8 @@ final class MediaLibrary
'SELECT * FROM media WHERE id IN ('.implode(',', array_fill(0, count($ids), '?')).')',
$ids,
) as $row) {
$found[$row['id']] = $this->present($row);
// Auch für find(): Wer die Bilder einer Liste vorab holt, erspart jede Einzelabfrage danach.
$found[$row['id']] = $this->gemerkt[$row['id']] = $this->present($row);
}
return $found;
+34
View File
@@ -313,6 +313,40 @@ final class PostRepository
}
/** Term names of one page, as the comma separated list the metadata form edits. */
/**
* Kategorien und Schlagwörter mehrerer Beiträge in einer Abfrage, als Text je Beitrag.
*
* Die Liste der Verwaltung fragte vorher für jeden Beitrag einzeln nach: zwanzig Beiträge,
* vierzig Abfragen.
*
* @param list<string> $pageIds
*
* @return array<string, array{category: string, tag: string}>
*/
public function namesForMany(array $pageIds): array
{
$names = [];
foreach ($this->termsForMany($pageIds) as $id => $terms) {
$names[$id] = [
'category' => implode(', ', array_column($terms['category'], 'name')),
'tag' => implode(', ', array_column($terms['tag'], 'name')),
];
}
return $names;
}
/**
* Holt die Bilder einer Liste in einer Abfrage, damit die Vorlage sie danach einzeln findet,
* ohne je Zeile die Datenbank zu fragen.
*
* @param list<string|null> $mediaIds
*/
public function preloadImages(array $mediaIds): void
{
$this->media->findMany(array_map('strval', array_filter($mediaIds)));
}
public function namesFor(string $pageId): array
{
$terms = $this->termsFor($pageId);
+3 -1
View File
@@ -49,11 +49,13 @@ final class EditorController extends AbstractController
$result = $this->pages->editorPage($type, $request->query->getInt('seite', 1), $request->query->getInt('limit', 20), $query, $status);
$pages = $result['items'];
$parentChoices = [];
$terms = 'post' === $type ? $this->posts->namesForMany(array_column($pages, 'id')) : [];
$this->posts->preloadImages([...array_column($pages, 'featured_media_id'), ...array_column($pages, 'og_media_id')]);
foreach ($pages as &$page) {
$page['document'] = json_decode($page['document'] ?? '{}', true, 512, JSON_THROW_ON_ERROR);
$page['title'] = $page['document']['title'] ?? $page['slug'];
$page['depth'] = substr_count((string) $page['path'], '/');
$page['terms'] = 'post' === $page['type'] ? $this->posts->namesFor($page['id']) : ['category' => '', 'tag' => ''];
$page['terms'] = $terms[$page['id']] ?? ['category' => '', 'tag' => ''];
if ($page['parent_id'] && !isset($parentChoices[$page['parent_id']])) {
$parentChoices[$page['parent_id']] = $this->pages->choices(id: $page['parent_id'])[0] ?? null;
}
+1 -1
View File
@@ -960,7 +960,7 @@ final class PageController extends AbstractController
'blocks' => [
['id' => 'archiv-kopf', 'type' => 'section', 'layout' => ['tone' => 'mint', 'width' => 'normal'], 'children' => $kopf],
['id' => 'archiv', 'type' => 'section', 'layout' => ['tone' => 'paper', 'width' => 'normal'], 'children' => [
['id' => 'archiv-liste', 'type' => 'postlist', 'props' => ['count' => PostRepository::PER_PAGE, 'category' => 'category' === $kind && null !== $term ? $term['slug'] : '', 'tag' => 'tag' === $kind && null !== $term ? $term['slug'] : '', 'layout' => 'karten', 'paginate' => true]],
['id' => 'archiv-liste', 'type' => 'postlist', 'props' => ['count' => PostRepository::PER_PAGE, 'category' => 'category' === $kind && null !== $term ? $term['slug'] : '', 'tag' => 'tag' === $kind && null !== $term ? $term['slug'] : '', 'layout' => 'karten', 'paginate' => true, 'headingLevel' => 2]],
]],
],
];