Upload PDFs to the library, link them from any link target, show them in a viewer

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>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-22 07:56:29 +02:00
1 parent 04927b9563
commit e8a9dcdf5c
20 files changed
+667 -30

No files matched your search

+51 -4
View File
@@ -3,7 +3,7 @@
{% 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, neueste zuerst. Beim Hochladen wird jedes Bild in WebP umgewandelt und in mehreren Breiten abgelegt; die Seite liefert dann die passende aus.{% 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') %}
@@ -19,13 +19,13 @@
<div class="dropzone" data-dropzone>
<label class="dropzone-label" for="files">
<span class="dropzone-icon" aria-hidden="true">▩</span>
<strong>Bilder hierher ziehen</strong>
<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" multiple class="dropzone-input">
<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, höchstens 8 MB je Bild. Mehrere Dateien sind möglich; das Hosting kann ein kleineres Limit setzen.</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 %}
@@ -38,4 +38,51 @@
</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 %}