Files
KantSites/templates/admin/settings.html.twig
T
kantarciandClaude Opus 5 f2ef2a4901
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s
Beitraege optional, Unterstriche nur im Text, eigenes Symbol
Eine Website ohne Beitraege redet nirgends von Beitraegen: Archiv und
Feed antworten mit 404, der Feedverweis faellt aus dem Kopf, und der
Hinweiskasten der Beitragsliste bleibt im Editor statt oeffentlich zu
erscheinen. Das gilt auch je Kategorie, sobald deren letzter Beitrag
weg ist. Dabei gefunden: die Liste fragte je Beitrag nach Bild und
Begriffen - jetzt je eine Abfrage fuer die ganze Liste (22 -> 16
Abfragen auf der Startseite des Nachbaus).

Unterstrichene Links gibt es nur noch im Fliesstext, dort mit einem
Schalter im Designbereich; Karten, Menue, Kontaktzeilen und der
Ansehen-Streifen haben keinen mehr. Die aktuelle Seite im Menue wird
ueber das Schriftgewicht kenntlich - die Akzentfarbe fiel im dunklen
Fussmenue durch die Kontrastpruefung.

Neu: das Symbol der Website laesst sich festlegen. Aus einem Bild der
Mediathek entstehen beim Speichern quadratische PNG-Dateien in 32 und
180 px; schlaegt das fehl, wird die Einstellung nicht gespeichert.

Die Browserpruefung laeuft jetzt gegen jede Installation statt nur
gegen die Demo: geprueft wird, was in der Sitemap steht, und das
Ladebudget trennt Bilder vom Beiwerk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 12:57:23 +02:00

167 lines
12 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 = 'settings' %}
{% 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 content %}
{% if missingLegal is not empty %}
{# Not a hint, a warning: in Germany both pages are required, and they have to be reachable from
every page of the site. #}
<div class="card">
<div class="card-head"><h2>Pflichtseiten fehlen</h2><span class="muted">{{ missingLegal|join(' und ') }}</span></div>
<div class="card-body">
<p class="notice error" role="alert">Ohne {{ missingLegal|join(' und ') }} ist diese Website in Deutschland nicht rechtskonform – beide müssen von jeder Seite aus erreichbar sein. KantSites verlinkt sie automatisch im Fuß jeder Seite, sobald sie hier eingetragen sind.</p>
<p class="field-help">Der Knopf legt die fehlenden Seiten mit einem Gerüst an und verknüpft sie. Jede Pflichtangabe darin ist mit <code>NOCH AUSZUFÜLLEN</code> markiert – den Text musst du selbst schreiben oder von jemandem schreiben lassen, der dafür einstehen kann. Vorhandene Seiten werden nie überschrieben.</p>
<form action="{{ path('admin_legal_pages') }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('settings') }}">
<button class="primary" type="submit">Fehlende Pflichtseiten anlegen</button>
</form>
</div>
</div>
{% endif %}
<section class="stack-section">
<div class="section-head"><h2>Unternehmensdaten</h2><span class="muted">Grundlage für Organization und LocalBusiness</span></div>
<form action="{{ path('admin_settings_save') }}" method="post" class="card card-body">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('settings') }}">
<div class="fields">
<div>
<label for="legalName">Name des Unternehmens</label>
<input id="legalName" name="legalName" maxlength="200" value="{{ settings.legalName }}">
</div>
<div>
<label for="phone">Telefon</label>
<input id="phone" name="phone" maxlength="40" value="{{ settings.phone }}">
</div>
<div>
<label for="email">E-Mail</label>
<input id="email" name="email" type="email" maxlength="120" value="{{ settings.email }}">
</div>
<div>
<label for="street">Straße und Hausnummer</label>
<input id="street" name="street" maxlength="120" value="{{ settings.street }}">
</div>
<div>
<label for="postalCode">Postleitzahl</label>
<input id="postalCode" name="postalCode" maxlength="16" value="{{ settings.postalCode }}">
</div>
<div>
<label for="city">Ort</label>
<input id="city" name="city" maxlength="120" value="{{ settings.city }}">
</div>
<div>
<label for="country">Land</label>
<input id="country" name="country" maxlength="2" pattern="[A-Za-z]{2}" value="{{ settings.country }}">
<p class="field-help">Zweibuchstabiger Code, zum Beispiel DE.</p>
</div>
</div>
<label for="homePath">Startseite</label>
<select id="homePath" name="homePath">
{% for page in choosableHome %}<option value="{{ page.path }}"{{ settings.homePath == page.path ? ' selected' : '' }}>{{ page.title }} (/{{ page.path }})</option>{% endfor %}
</select>
<p class="field-help">Diese Seite antwortet unter <code>/</code>; ihre eigene Adresse leitet dauerhaft dorthin. Nur veröffentlichte Seiten sind wählbar, und die Startseite lässt sich nicht umbenennen oder in den Papierkorb legen.</p>
<label for="description">Kurzbeschreibung</label>
<input id="description" name="description" maxlength="300" value="{{ settings.description }}">
<label for="sameAs">Profile</label>
<textarea id="sameAs" name="sameAs" rows="3" placeholder="https://www.linkedin.com/company/…">{{ settings.sameAs|join('\n') }}</textarea>
<p class="field-help">Eine HTTPS-Adresse je Zeile, höchstens zwanzig.</p>
<label class="check-label" for="localBusiness"><input id="localBusiness" name="localBusiness" type="checkbox" value="1"{{ settings.localBusiness ? ' checked' : '' }}> Als lokales Unternehmen auszeichnen</label>
<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>
<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>
<label for="robotsExtra">Zusätzliche Zeilen für robots.txt</label>
<textarea id="robotsExtra" name="robotsExtra" rows="3">{{ settings.robotsExtra }}</textarea>
<p class="field-help">Nur Kommentarzeilen und einfache Anweisungen wie <code>Disallow: /pfad</code>. Höchstens dreißig Zeilen.</p>
<div class="section-head"><h2>Einwilligung</h2><span class="muted">Nur nötig, wenn die Website etwas von Dritten lädt</span></div>
<label class="check-label" for="stickyHeader"><input id="stickyHeader" name="stickyHeader" type="checkbox" value="1"{{ settings.stickyHeader ? ' checked' : '' }}> Kopfbereich beim Scrollen oben halten</label>
<label class="check-label" for="postMetaHeader"><input id="postMetaHeader" name="postMetaHeader" type="checkbox" value="1"{{ settings.postMetaHeader ? ' checked' : '' }}> Datum, Autor und Kategorie über einem Beitrag zeigen</label>
<label class="check-label" for="consentBanner"><input id="consentBanner" name="consentBanner" type="checkbox" value="1"{{ settings.consentBanner ? ' checked' : '' }}> Einwilligungshinweis anzeigen</label>
<p class="field-help">Ohne eingebettete Inhalte anderer Anbieter braucht diese Website keine Einwilligung – dann ist ein Banner nur im Weg und sollte aus bleiben. Eingebettete Inhalte laden ohnehin erst nach Zustimmung, unabhängig von diesem Schalter.</p>
<fieldset class="field-set">
<legend>Worüber gefragt wird</legend>
{% for value, label in consentGroups %}
<label class="check-label" for="group-{{ value }}"><input id="group-{{ value }}" name="consentGroups[]" type="checkbox" value="{{ value }}"{{ value in settings.consentGroups ? ' checked' : '' }}> {{ label }}</label>
<p class="field-help">{{ consentGroupHelp[value] }}</p>
{% endfor %}
</fieldset>
<label for="consentText">Text im Hinweis</label>
<textarea id="consentText" name="consentText" rows="3" maxlength="600">{{ settings.consentText }}</textarea>
<p class="field-help">Leer lassen für den Standardtext. Er soll sagen, was geladen wird und was ohne Zustimmung passiert – nicht zum Klicken drängen.</p>
<div class="fields">
<div>
<label for="privacyPath">Adresse der Datenschutzseite</label>
<input id="privacyPath" name="privacyPath" maxlength="400" value="{{ settings.privacyPath }}" placeholder="datenschutz">
<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">
</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>
<button class="primary" type="submit">Einstellungen speichern</button>
</form>
</section>
<section class="stack-section">
<div class="section-head"><h2>Weiterleitungen <span class="count">{{ redirects.total }}</span></h2><span class="muted">Werden vor dem 404 geprüft.</span></div>
<form action="{{ path('admin_redirect_save') }}" method="post" class="card card-body">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('settings') }}">
<div class="fields">
<div>
<label for="from">Alte Adresse</label>
<input id="from" name="from" maxlength="400" required placeholder="alte-seite">
</div>
<div>
<label for="to">Neue Adresse</label>
<input id="to" name="to" maxlength="400" placeholder="leistungen/webdesign">
<p class="field-help">Bei 410 leer lassen.</p>
</div>
<div>
<label for="status">Art</label>
<select id="status" name="status">
<option value="301">301 – dauerhaft verschoben</option>
<option value="410">410 – entfernt</option>
</select>
</div>
</div>
<button class="primary" type="submit">Weiterleitung speichern</button>
</form>
{% if redirects.items is not empty %}
<table class="table redirect-table">
<caption class="visually-hidden">Gespeicherte Weiterleitungen</caption>
<thead><tr><th scope="col">Alte Adresse</th><th scope="col">Ziel</th><th scope="col">Art</th><th scope="col">Entfernen</th></tr></thead>
<tbody>
{% for entry in redirects.items %}
<tr>
<td><code>/{{ entry.from }}</code></td>
<td>{% if entry.to %}<code>/{{ entry.to }}</code>{% else %}<span class="muted">—</span>{% endif %}</td>
<td>{{ entry.status }}</td>
<td>
<form action="{{ path('admin_redirect_save') }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('settings') }}">
<input type="hidden" name="remove" value="{{ entry.id }}">
<button class="text-button danger-text" type="submit" aria-label="Weiterleitung von /{{ entry.from }} entfernen">Entfernen</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include 'admin/pager.html.twig' with {result: redirects, route: 'admin_settings', label: 'Seiten der Weiterleitungen'} only %}
{% endif %}
</section>
<p class="footnote">KantSites · Arbeitsstand</p>
{% endblock %}