Pick the site icon from the library and crop it, find legal pages by search, list newest first

- The site icon is no longer a select at the end of the company data. It has
  its own section: a button opens the media library (upload included), then
  a square is dragged, resized by its corner, a slider or +/-, and previewed
  at 180 and 32 px. The crop is stored as fractions (faviconCrop) and the
  PNGs are cut from it; faviconVersion changes the link so browsers pick up
  a new cut. Icons without a crop keep their old link.
- openMedia() takes needsAlt: false, since the icon never appears as
  content. The dialog styles moved from editor.css to media-dialog.css so
  the dialog works outside the editor.
- Privacy page and imprint are chosen with the content search, like the 404
  page. The picker can store the path instead of the id (valueKey/data-value).
- The page and post lists in the administration sort newest first; the
  subpage indentation is gone there, as it would point at the wrong parent.

Tests: 581 PHPUnit, 35 node, test:editor green (the consent step now picks
the privacy page by search). test:admin --admin-only fails only at the known
nested-interactive menu summaries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-18 07:43:05 +02:00
1 parent 6632836580
commit 106f84dfd5
19 files changed
+300 -69

No files matched your search

+16 -13
View File
@@ -3,7 +3,8 @@
{% block title %}Einstellungen{% endblock %}
{% block heading %}Unternehmen und Adressen{% endblock %}
{% block lead %}Aus diesen Angaben entstehen die strukturierten Daten der Startseite. Was hier steht, ist genau das, was Suchmaschinen lesen – es gibt keine zweite, unsichtbare Fassung.{% endblock %}
{% block scripts %}<script type="module" src="/assets/page-picker.js?v={{ kant_version() }}"></script>{% endblock %}
{% block styles %}<link rel="stylesheet" href="/assets/media-dialog.css?v={{ kant_version() }}">{% endblock %}
{% block scripts %}<script type="module" src="/assets/page-picker.js?v={{ kant_version() }}"></script><script type="module" src="/assets/favicon-field.js?v={{ kant_version() }}"></script>{% endblock %}
{% block content %}
{% if missingLegal is not empty %}
@@ -129,15 +130,19 @@
<p class="field-help">Braucht mindestens Straße und Ort. Sinnvoll bei einem Standort mit Kundenverkehr.</p>
<label class="check-label" for="llmsTxt"><input id="llmsTxt" name="llmsTxt" type="checkbox" value="1"{{ settings.llmsTxt ? ' checked' : '' }}> <code>llms.txt</code> ausliefern</label>
<p class="field-help">Eine maschinenlesbare Liste der veröffentlichten, indexierbaren Seiten. Optional. Kein Rankingfaktor und keine Voraussetzung für irgendeine Suchfunktion; ohne Aktivierung antwortet die Adresse mit 404.</p>
<label for="faviconMediaId">Symbol der Website</label>
<div class="favicon-field">
{% if settings.faviconMediaId %}<img class="favicon-preview" src="/favicon.png?v={{ settings.faviconMediaId|slice(0, 8) }}" alt="" width="32" height="32">{% endif %}
<select id="faviconMediaId" name="faviconMediaId">
<option value="">Mitgeliefertes Zeichen</option>
{% for image in images %}<option value="{{ image.id }}"{{ settings.faviconMediaId == image.id ? ' selected' : '' }}>{{ image.title }}</option>{% endfor %}
</select>
<div class="section-head"><h2 id="favicon-title">Symbol der Website</h2><span class="muted">Favicon im Browser-Reiter und auf dem Startbildschirm</span></div>
<div class="favicon-field" data-favicon-field aria-labelledby="favicon-title" role="group">
<input type="hidden" name="faviconMediaId" value="{{ settings.faviconMediaId }}">
<input type="hidden" name="faviconCrop" value="{{ settings.faviconCrop }}">
<div class="favicon-current">
{% if settings.faviconMediaId %}<img class="favicon-preview" src="/favicon.png?v={{ settings.faviconVersion ?: settings.faviconMediaId|slice(0, 8) }}" alt="" width="32" height="32"> <span>Eigenes Symbol</span>{% else %}<img class="favicon-preview" src="/favicon.svg" alt="" width="32" height="32"> <span>Mitgeliefertes Zeichen</span>{% endif %}
</div>
<div class="favicon-actions">
<button type="button" class="secondary" data-favicon-choose>{{ settings.faviconMediaId ? 'Anderes Bild wählen' : 'Bild wählen oder hochladen' }}</button>
<button type="button" class="text-button danger-text" data-favicon-remove{{ settings.faviconMediaId ? '' : ' hidden' }}>Mitgeliefertes Zeichen verwenden</button>
</div>
</div>
<p class="field-help">Das Bild im Browser-Reiter und auf dem Startbildschirm. Aus dem gewählten Bild entsteht beim Speichern ein quadratischer Ausschnitt aus der Mitte in 32 und 180 Pixeln – ein Logo mit viel Rand wird darin klein, ein quadratisches Zeichen funktioniert am besten.</p>
<p class="field-help">Das Bild kommt aus der Mediathek, dort kann es auch gleich hochgeladen werden. Danach das Quadrat auf den gewünschten Ausschnitt ziehen und die Größe einstellen. Beim Speichern entstehen daraus PNG-Dateien in 32 und 180 Pixeln.</p>
<div class="section-head"><h2>Knopf „nach oben"</h2><span class="muted">Erscheint beim Scrollen, in der Ecke</span></div>
<label class="check-label" for="scrollTop"><input id="scrollTop" name="scrollTop" type="checkbox" value="1"{{ settings.scrollTop ? ' checked' : '' }}> Knopf „nach oben" anzeigen</label>
<p class="field-help">Springt an den Seitenanfang. Er braucht kein JavaScript: In Browsern mit Scroll-Zeitachse blendet er sich beim Scrollen ein, in den übrigen steht er von Anfang an da.</p>
@@ -261,13 +266,11 @@
</fieldset>
<div class="fields">
<div>
<label for="privacyPath">Adresse der Datenschutzseite</label>
<input id="privacyPath" name="privacyPath" maxlength="400" value="{{ settings.privacyPath }}" placeholder="datenschutz">
{% include 'admin/content-picker.html.twig' with {id: 'privacyPath', name: 'privacyPath', type: 'page', valueKey: 'path', label: 'Datenschutzseite', selected: legalSelection.privacyPath} only %}
<p class="field-help">Pflicht, sobald der Hinweis an ist, und die Seite muss existieren.</p>
</div>
<div>
<label for="imprintPath">Adresse des Impressums</label>
<input id="imprintPath" name="imprintPath" maxlength="400" value="{{ settings.imprintPath }}" placeholder="impressum">
{% include 'admin/content-picker.html.twig' with {id: 'imprintPath', name: 'imprintPath', type: 'page', valueKey: 'path', label: 'Impressum', selected: legalSelection.imprintPath} only %}
</div>
</div>
<p class="field-help">Die Entscheidung liegt in einem Cookie dieser Website, ein halbes Jahr lang, ohne Kennung und ohne Übermittlung. Unter <code>/einwilligung</code> kann sie jederzeit geändert werden; auf jeder Seite steht dafür ein Link.</p>