Mediathek mit Ablagefläche, Updateverfahren geplant

Das Auswahlfeld des Browsers sah aus wie das Auswahlfeld des Browsers.
Hochgeladen wird jetzt über eine Ablagefläche: Bilder hineinziehen oder
klicken, darunter steht, welche Dateien gewählt sind und welche übersprungen
wurden.

Darunter liegt weiter ein echtes input[type=file] - unsichtbar, aber
fokussierbar, mit der Fläche als label und dem Fokusring über :focus-within.
Ohne JavaScript bleibt genau das übrig: ein Dateifeld mit Beschriftung. Das
Skript ergänzt Ziehen und benennt die Auswahl; abgeschickt wird nie von
selbst. Kein "required", weil der Browser seine Meldung nicht an etwas
Unsichtbares hängen kann - der Server weist einen leeren Upload mit einem
lesbaren Satz ab.

docs/UPDATE.md plant das Update über einen Git-Tag: Vorprüfung, Sicherung,
Wartungszustand mit 503 für die Website und offenem /admin, Checkout,
composer, Migration, Cache, Rauchtest, Protokoll - und den Rückweg in
derselben Reihenfolge rückwärts. Migrationen bleiben nicht umkehrbar, der
Rückweg ist die Sicherung. Kein Updater, der eine Adresse aus dem Formular
zieht, und kein automatisches Update im Hintergrund. Zwei Entscheidungen
hängen am Hosting: Composer auf dem Server oder vendor/ im Tag, und ob es
Shell-Zugriff gibt. Gebaut ist davon nichts.

219 PHP-Tests / 881 Assertions unter PHP 8.4.22, darunter Upload über das
Formular mit zwei Dateien, CSRF-Pflicht, Versionskonflikt, Archivfilter und
Blättern mit 26 Bildern. Playwright prüft die Ablagefläche am echten
Dateifeld; axe bleibt auf elf Bildschirmen ohne Verstöße.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 18:22:37 +02:00
1 parent 14031a5880
commit 68380c6295
11 files changed
+273 -6

No files matched your search

+15 -2
View File
@@ -1,6 +1,7 @@
{% extends 'admin/shell.html.twig' %}
{% set nav = 'media' %}
{% block title %}Mediathek{% endblock %}
{% block scripts %}<script type="module" src="/assets/media-drop.js"></script>{% endblock %}
{% block heading %}Mediathek{% endblock %}
{% block lead %}Alle Bilder, neueste zuerst. Beim Hochladen wird jedes Bild in WebP umgewandelt und in mehreren Breiten abgelegt; die Seite liefert dann die passende aus.{% endblock %}
{% block content %}
@@ -10,8 +11,20 @@
<input type="hidden" name="_csrf_token" value="{{ csrf_token('media-form') }}">
<input type="hidden" name="seite" value="{{ library.page }}">
<input type="hidden" name="archiv" value="{{ archived ? '1' : '' }}">
<label for="files">Bilder hochladen</label>
<input id="files" name="files[]" type="file" accept="image/jpeg,image/png,image/webp" multiple required>
{# A drop area rather than the browser's file widget. The input stays a real file input and
stays focusable, so the form works by keyboard and without JavaScript; the script only adds
dragging and the list of chosen files. No "required": the input is visually hidden, and a
browser cannot show its validation bubble on something invisible. The server refuses an
empty upload with a readable message instead. #}
<div class="dropzone" data-dropzone>
<label class="dropzone-label" for="files">
<span class="dropzone-icon" aria-hidden="true">▩</span>
<strong>Bilder hierher ziehen</strong>
<span class="dropzone-hint">oder klicken, um Dateien zu wählen</span>
</label>
<input id="files" name="files[]" type="file" accept="image/jpeg,image/png,image/webp" multiple class="dropzone-input">
</div>
<p class="dropzone-chosen" data-chosen role="status">Noch keine Datei gewählt.</p>
<p class="field-help">JPEG, PNG oder WebP, höchstens 8 MB je Bild. Mehrere Dateien sind möglich; das Hosting kann ein kleineres Limit setzen.</p>
<button class="primary" type="submit">Hochladen</button>
</form>