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>
96 lines
6.0 KiB
Twig
96 lines
6.0 KiB
Twig
{% 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 %}
|