PDFs go through the same upload field as images, are checked by their content and kept unchanged in their own table. Every link field can pick one, and a page that links a PDF opens it in a dialog with the browser's own viewer; touch devices and browsers without a viewer get a new tab instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
6.3 KiB
Twig
89 lines
6.3 KiB
Twig
{% extends 'admin/shell.html.twig' %}
|
||
{% set nav = 'media' %}
|
||
{% block title %}Mediathek{% endblock %}
|
||
{% block scripts %}<script type="module" src="/assets/media-drop.js?v={{ kant_version() }}"></script><script type="module" src="/assets/live-search.js?v={{ kant_version() }}"></script>{% endblock %}
|
||
{% block heading %}Mediathek{% endblock %}
|
||
{% block lead %}Alle Bilder und PDFs, neueste zuerst. Beim Hochladen wird jedes Bild in WebP umgewandelt und in mehreren Breiten abgelegt; die Seite liefert dann die passende aus. PDFs bleiben, wie sie sind, und lassen sich in jedem Linkziel wählen.{% 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="q" value="{{ query }}">
|
||
<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 oder PDFs 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,application/pdf,.pdf" 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 bis 8 MB je Bild, PDF bis 20 MB. Mehrere Dateien sind möglich; das Hosting kann ein kleineres Limit setzen. Hochgeladene Dateien sind über ihre Adresse öffentlich – nichts Vertrauliches hochladen.</p>
|
||
<button class="primary" type="submit">Hochladen</button>
|
||
</form>
|
||
{% endif %}
|
||
|
||
<form method="get" action="{{ path('admin_media') }}" class="list-toolbar" role="search" data-live-search>
|
||
<div class="list-search"><label for="media-list-search">Bilder durchsuchen</label><input id="media-list-search" type="search" name="q" maxlength="160" value="{{ query }}" placeholder="Titel oder Alternativtext …"></div>
|
||
<div><label for="media-list-archive">Status</label><select id="media-list-archive" name="archiv" data-search-default="0"><option value="0"{{ not archived ? ' selected' : '' }}>Aktive Bilder</option><option value="1"{{ archived ? ' selected' : '' }}>Mit archivierten Bildern</option></select></div>
|
||
<button class="secondary search-submit" type="submit">Anwenden</button>
|
||
<a class="text-button" data-search-reset href="{{ path('admin_media') }}"{{ not query and not archived ? ' hidden' : '' }}>Zurücksetzen</a>
|
||
</form>
|
||
{% include 'admin/search-feedback.html.twig' %}
|
||
<div id="list-results" class="stack" role="region" aria-label="Suchergebnisse" tabindex="-1">{% include 'admin/media-results.html.twig' %}</div>
|
||
|
||
{# Die PDFs stehen unter den Bildern und ohne Suche: es sind wenige, und ein Titel reicht, um
|
||
eines wiederzufinden. Der Schalter für das Archiv oben gilt auch hier. #}
|
||
<section class="stack" aria-labelledby="documents-heading">
|
||
<div class="section-head">
|
||
<h2 id="documents-heading">Dokumente <span class="count">{{ documents|length }}</span></h2>
|
||
</div>
|
||
{% if documents is empty %}
|
||
<div class="empty">
|
||
<h3>Keine PDFs in der Mediathek.</h3>
|
||
<p>{{ is_granted('ROLE_EDITOR') ? 'Lade oben ein PDF hoch. Danach lässt es sich in jedem Linkziel wählen – etwa für einen Button „Preisliste“.' : 'Ein Redakteur kann PDFs hochladen.' }}</p>
|
||
</div>
|
||
{% else %}
|
||
<div class="rows">
|
||
{% for item in documents %}
|
||
<article class="row row-stack">
|
||
<div class="row-line">
|
||
<span class="row-icon row-icon-text" aria-hidden="true">PDF</span>
|
||
<div class="row-main">
|
||
<h3><a href="{{ item.src }}" target="_blank" rel="noopener">{{ item.title }}</a></h3>
|
||
<p class="muted">{{ (item.bytes / 1048576)|number_format(1, ',') }} MB · <code>{{ item.src }}</code>{% if item.archived %} · archiviert{% endif %}</p>
|
||
</div>
|
||
</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_document_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="q" value="{{ query }}">
|
||
<input type="hidden" name="archiv" value="{{ archived ? '1' : '' }}">
|
||
<label for="document-title-{{ item.id }}">Titel</label>
|
||
<input id="document-title-{{ item.id }}" name="title" maxlength="160" required value="{{ item.title }}">
|
||
<p class="field-help">Wird zum Dateinamen, wenn jemand das PDF speichert, und steht über dem Betrachter.</p>
|
||
<label class="check-label" for="document-archived-{{ item.id }}"><input id="document-archived-{{ item.id }}" name="archived" type="checkbox" value="1"{{ item.archived ? ' checked' : '' }}> Archiviert</label>
|
||
<p class="field-help">Archivierte PDFs erscheinen nicht mehr in der Auswahl, bleiben aber erreichbar – ein Link darauf geht nicht ins Leere.</p>
|
||
<button class="primary" type="submit">Angaben speichern</button>
|
||
</form>
|
||
</div>
|
||
</details>
|
||
{% endif %}
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</section>
|
||
{% endblock %}
|