Files
KantSites/templates/admin/media.html.twig
T
kantarciandClaude Opus 5 84d471acd9 Das System heißt KantSites
Umut hat den Namen festgelegt: aus dem Arbeitstitel "Kant CMS" wird KantSites.
Geändert in allen Seitentiteln der Verwaltung, im Wortmarken-Schriftzug von
Anmeldung und Seitenleiste, in den Fußzeilen, in den Demoinhalten für eine
frische Installation, in package.json, .claude/launch.json und der
Dokumentation. Die Bezeichnung der Website in der Datenbank heißt jetzt
ebenfalls KantSites.

Nicht angefasst: die bereits gespeicherten Demoinhalte der lokalen
Installation. Titel wie "Die Grundlage · Kant CMS" und der Text im
Kopfbereich sind Inhalt, nicht Systemname - die gehören in den Editor, nicht
in ein Skript, das Revisionen umschreibt.

219 PHP-Tests / 881 Assertions grün, Playwright-Editortest grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:36:50 +02:00

96 lines
6.0 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}
{% if is_granted('ROLE_EDITOR') %}
<form action="{{ path('admin_media_upload') }}" method="post" enctype="multipart/form-data" class="card card-body">
<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' : '' }}">
{# 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>
{% endif %}
<div class="section-head">
<h2>{{ archived ? 'Alle Bilder einschließlich Archiv' : 'Bilder' }} <span class="count">{{ library.total }}</span></h2>
<span class="muted">
{% if archived %}<a href="{{ path('admin_media') }}">Archivierte ausblenden</a>{% else %}<a href="{{ path('admin_media', {archiv: 1}) }}">Archivierte anzeigen</a>{% endif %}
{% if library.pages > 1 %} · Seite {{ library.page }} von {{ library.pages }}{% endif %}
</span>
</div>
{% if library.items is empty %}
<div class="empty">
<h3>{{ archived ? 'Keine Bilder vorhanden.' : 'Keine Bilder in der Mediathek.' }}</h3>
<p>{{ is_granted('ROLE_EDITOR') ? 'Lade oben dein erstes Bild hoch.' : 'Ein Redakteur kann Bilder hochladen.' }}</p>
</div>
{% else %}
<ul class="library-grid">
{% for item in library.items %}
<li class="library-tile">
{# The smallest variant is the thumbnail; the largest opens in a new tab. #}
<a href="{{ (item.variants|last).src }}" target="_blank" rel="noopener" aria-label="{{ item.title }} in Originalgröße öffnen">
<img src="{{ (item.variants|first).src }}" alt="" loading="lazy" width="{{ (item.variants|first).width }}" height="{{ (item.variants|first).height }}">
</a>
<div class="library-body">
<strong>{{ item.title }}</strong>
<span class="muted">{{ (item.variants|last).width }} × {{ (item.variants|last).height }} px{% if item.archived %} · archiviert{% endif %}</span>
<span class="muted">{{ item.alt ? 'Alternativtext gesetzt' : 'Kein Alternativtext' }}</span>
<code>{{ item.id }}</code>
</div>
{% if is_granted('ROLE_EDITOR') %}
<details class="sheet">
<summary aria-label="Angaben zu {{ item.title }}">Angaben</summary>
<div class="sheet-body">
<form action="{{ path('admin_media_save', {id: item.id}) }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('media-form') }}">
<input type="hidden" name="version" value="{{ item.version }}">
<input type="hidden" name="seite" value="{{ library.page }}">
<input type="hidden" name="archiv" value="{{ archived ? '1' : '' }}">
<label for="title-{{ item.id }}">Bildtitel</label>
<input id="title-{{ item.id }}" name="title" maxlength="160" required value="{{ item.title }}">
<label for="alt-{{ item.id }}">Alternativtext</label>
<textarea id="alt-{{ item.id }}" name="alt" maxlength="300" rows="2">{{ item.alt }}</textarea>
<p class="field-help">Beschreibt, was zu sehen ist. Das ist, was Menschen mit Screenreader vom Bild haben. Bei rein dekorativen Bildern leer lassen.</p>
<label class="check-label" for="archived-{{ item.id }}"><input id="archived-{{ item.id }}" name="archived" type="checkbox" value="1"{{ item.archived ? ' checked' : '' }}> Archiviert</label>
<p class="field-help">Archivierte Bilder erscheinen nicht mehr in der Auswahl, bleiben aber ausgeliefert – eine Seite, die das Bild verwendet, verliert es nicht.</p>
<button class="primary" type="submit">Angaben speichern</button>
</form>
</div>
</details>
{% endif %}
</li>
{% endfor %}
</ul>
{% if library.pages > 1 %}
<nav class="pager" aria-label="Seiten der Mediathek">
{% if library.page > 1 %}<a class="secondary" href="{{ path('admin_media', {seite: library.page - 1, archiv: archived ? 1 : null}) }}" rel="prev"><span aria-hidden="true">←</span> Neuere</a>{% endif %}
<span class="muted">Seite {{ library.page }} von {{ library.pages }} · {{ library.total }} Bilder</span>
{% if library.page < library.pages %}<a class="secondary" href="{{ path('admin_media', {seite: library.page + 1, archiv: archived ? 1 : null}) }}" rel="next">Ältere <span aria-hidden="true">→</span></a>{% endif %}
</nav>
{% endif %}
{% endif %}
<p class="footnote">KantSites · Arbeitsstand</p>
{% endblock %}