The back-to-top button was hidden as long as the consent question was open, on every device - and on a phone the question tends to stay open. Instead of `display:none` it now anchors itself 12px above the bar wherever the bar covers its corner: below 800px for the centred card, at every width for the full-width bar and the corner card. Browsers without anchor positioning keep hiding it there. The bar renders before the button so it can be an anchor, and the reserved bottom padding grows to 320px below 620px, where the bar with its default text is up to 299px tall. The notice itself gets a face. Settings → Einwilligung now carries layout (card, bar, corner), shape (soft, round, square - one setting for the box and its buttons), a heading and four colours, each as #hex or @palette key. `Theme::cssColor()` resolves both, replacing the copy inside the back-to-top code; `Consent::styles()` writes the values as `--kant-consent-*` into the hashed style block. The base layout changes with it: an accent stripe and shield icon, equal-width buttons, a disclosure with a chevron, underlined legal links, a two-column bar on wide screens. Declining stays an outlined button of the same size next to accepting. The archive and the consent page carry the same variables - the archive had no palette at all until now. In the editor, rows with an own value are labelled in orange rather than bold with a dot; the device chips sit 6px apart with the focus ring inside the gap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
329 lines
26 KiB
Twig
329 lines
26 KiB
Twig
{% 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 scripts %}<script type="module" src="/assets/page-picker.js?v={{ kant_version() }}"></script>{% 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" id="not-found" aria-labelledby="not-found-title">
|
||
<div class="section-head"><h2 id="not-found-title">404-Seite</h2><a class="text-button" href="/__404-vorschau__" target="_blank" rel="noopener">404-Vorschau ↗</a></div>
|
||
<div class="card card-body">
|
||
<p>Bei unbekannten Adressen erscheint diese Seite mit dem Standard-Header, Standard-Footer und Design deiner Website.</p>
|
||
{% if notFoundSelection %}
|
||
<p class="notice">{{ notFoundSelection.published ? 'Aktiv:' : 'Zugewiesener Entwurf:' }} <strong>{{ notFoundSelection.title }}</strong>{% if not notFoundSelection.published %} · Bis zur Veröffentlichung erscheint die Standard-404.{% endif %}</p>
|
||
<p><a class="secondary" href="{{ path('admin_editor', {id: notFoundSelection.id}) }}">404-Seite bearbeiten</a></p>
|
||
{% else %}
|
||
<p class="notice">Aktiv: Standard-404 im Website-Design.</p>
|
||
{% endif %}
|
||
{% if is_granted('ROLE_PUBLISHER') %}
|
||
<form action="{{ path('admin_not_found_save') }}" method="post" id="not-found-form">
|
||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('settings') }}">
|
||
<input type="hidden" name="expectedId" value="{{ settings.notFoundPageId }}">
|
||
{% include 'admin/content-picker.html.twig' with {id: 'not-found-picker', name: 'notFoundPageId', type: 'page', label: '404-Seite suchen', selected: notFoundSelection} only %}
|
||
<p class="field-help">Nur der veröffentlichte Stand wird angezeigt. Ohne Auswahl wird die Standard-404 verwendet.</p>
|
||
<button class="primary" type="submit">404-Zuordnung speichern</button>
|
||
</form>
|
||
{% if not notFoundSelection %}
|
||
<form action="{{ path('admin_not_found_save') }}" method="post">
|
||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('settings') }}">
|
||
<input type="hidden" name="expectedId" value="{{ settings.notFoundPageId }}">
|
||
<input type="hidden" name="action" value="create">
|
||
<p class="field-help">Noch keine passende Seite? Lege eine fertige Vorlage an und bearbeite sie im Editor.</p>
|
||
<button class="secondary" type="submit">404-Vorlage anlegen</button>
|
||
</form>
|
||
{% endif %}
|
||
{% else %}<p class="field-help">Die Zuordnung kann von einem Benutzer mit Veröffentlichungsrechten geändert werden.</p>{% endif %}
|
||
</div>
|
||
</section>
|
||
<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. Die sozialen Profile unten stehen automatisch mit in den strukturierten Daten.</p>
|
||
<fieldset class="field-set">
|
||
<legend>Soziale Profile</legend>
|
||
<p class="field-help">Einmal hier eintragen, dann mit dem Element „Soziale Profile“ an jeder Stelle einsetzen – im Kopf, im Fuß, in einer Kontaktsektion. Größe, Form und Farbe bestimmt das Element. Leere Felder erscheinen nicht.</p>
|
||
<div class="fields">
|
||
{% for key, name in socialNetworks %}
|
||
<div>
|
||
<label for="social-{{ key }}">{{ name }}</label>
|
||
<input id="social-{{ key }}" name="social[{{ key }}]" type="url" maxlength="400" value="{{ settings.social[key]|default('') }}" placeholder="{{ socialPlaceholders[key] }}">
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</fieldset>
|
||
<fieldset class="field-set">
|
||
<legend>Öffnungszeiten</legend>
|
||
<p class="field-help">Als Uhrzeit wie 07:30. Ein Tag ohne Angabe gilt als geschlossen. Erscheint als <code>openingHoursSpecification</code> in den strukturierten Daten – der Text auf der Website ist davon unabhängig.</p>
|
||
<div class="fields">
|
||
{% for day, label in weekdays %}
|
||
{% set row = (settings.openingHours|filter(h => h.day == day)|first) ?: {opens: '', closes: ''} %}
|
||
<div>
|
||
<label for="hours-{{ day }}-opens">{{ label }}</label>
|
||
<div class="hours-row"><input id="hours-{{ day }}-opens" name="hours[{{ day }}][opens]" type="time" value="{{ row.opens }}" aria-label="{{ label }}, öffnet"> <span aria-hidden="true">–</span> <input id="hours-{{ day }}-closes" name="hours[{{ day }}][closes]" type="time" value="{{ row.closes }}" aria-label="{{ label }}, schließt"></div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</fieldset>
|
||
<label for="areaServed">Einsatzgebiet</label>
|
||
<textarea id="areaServed" name="areaServed" rows="3" placeholder="Baden-Baden Rastatt Karlsruhe">{{ settings.areaServed|join('\n') }}</textarea>
|
||
<p class="field-help">Ein Ort je Zeile, höchstens vierzig. Erscheint als <code>areaServed</code> in den strukturierten Daten.</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>
|
||
<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>
|
||
<div class="fields">
|
||
<div>
|
||
<label for="scrollTopPosition">Seite</label>
|
||
<select id="scrollTopPosition" name="scrollTopPosition">
|
||
<option value="rechts"{{ settings.scrollTopPosition == 'rechts' ? ' selected' : '' }}>Rechts unten</option>
|
||
<option value="links"{{ settings.scrollTopPosition == 'links' ? ' selected' : '' }}>Links unten</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="scrollTopShape">Form</label>
|
||
<select id="scrollTopShape" name="scrollTopShape">
|
||
<option value="rund"{{ settings.scrollTopShape == 'rund' ? ' selected' : '' }}>Rund</option>
|
||
<option value="weich"{{ settings.scrollTopShape == 'weich' ? ' selected' : '' }}>Abgerundet</option>
|
||
<option value="eckig"{{ settings.scrollTopShape == 'eckig' ? ' selected' : '' }}>Eckig</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="scrollTopSize">Größe</label>
|
||
<input id="scrollTopSize" name="scrollTopSize" type="number" min="36" max="72" value="{{ settings.scrollTopSize }}">
|
||
<p class="field-help">36 bis 72 Pixel. Unter 44 wird er auf dem Telefon schwer zu treffen.</p>
|
||
</div>
|
||
<div>
|
||
<label for="scrollTopAfter">Erscheint nach</label>
|
||
<input id="scrollTopAfter" name="scrollTopAfter" type="number" min="0" max="4000" step="50" value="{{ settings.scrollTopAfter }}">
|
||
<p class="field-help">Pixel Scrollweg. 0 heißt: sofort.</p>
|
||
</div>
|
||
<div>
|
||
<label for="scrollTopColor">Farbe</label>
|
||
<input id="scrollTopColor" name="scrollTopColor" value="{{ settings.scrollTopColor }}" placeholder="@accent" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||
</div>
|
||
<div>
|
||
<label for="scrollTopInk">Farbe des Pfeils</label>
|
||
<input id="scrollTopInk" name="scrollTopInk" value="{{ settings.scrollTopInk }}" placeholder="#ffffff" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||
</div>
|
||
<div>
|
||
<label for="scrollTopLabel">Beschriftung für Vorlesesoftware</label>
|
||
<input id="scrollTopLabel" name="scrollTopLabel" maxlength="60" value="{{ settings.scrollTopLabel }}">
|
||
</div>
|
||
</div>
|
||
<p class="field-help">Farben entweder als <code>@schlüssel</code> aus der Palette oder als Wert wie <code>#1a2b3c</code>. Leer bedeutet: Akzentfarbe des Designs auf Weiß.</p>
|
||
<div class="section-head"><h2>Spamschutz für Formulare</h2><span class="muted">Optional, von Cloudflare</span></div>
|
||
<p class="field-help">Eingebaut sind bereits drei Prüfungen ohne Dritte: ein verstecktes Feld, ein signierter Zeitstempel und höchstens fünf Absendungen je Absender und Stunde. Das hält automatisch ausgefüllte Formulare ab. Gegen jemanden, der einen echten Browser fernsteuert, hilft <strong>Cloudflare Turnstile</strong> – die Prüfung ohne Bilderrätsel. Der Preis: das Widget braucht JavaScript (ohne Skript ist das Formular dann nicht absendbar), und die IP-Adresse des Absenders geht an Cloudflare, was in die Datenschutzerklärung gehört. Schlüssel gibt es kostenlos im Cloudflare-Konto unter „Turnstile“.</p>
|
||
<label class="check-label" for="turnstile"><input id="turnstile" name="turnstile" type="checkbox" value="1"{{ settings.turnstile ? ' checked' : '' }}> Turnstile-Prüfung in Formularen anzeigen</label>
|
||
<label for="turnstileSiteKey">Öffentlicher Schlüssel (Site Key)</label>
|
||
<input id="turnstileSiteKey" name="turnstileSiteKey" maxlength="100" value="{{ settings.turnstileSiteKey }}" placeholder="0x4AAAAAAA…">
|
||
<label for="turnstileSecret">Geheimer Schlüssel (Secret Key)</label>
|
||
<input id="turnstileSecret" name="turnstileSecret" maxlength="100" value="{{ settings.turnstileSecret }}" placeholder="0x4AAAAAAA…" autocomplete="off">
|
||
<p class="field-help">Antwortet Cloudflare einmal nicht, wird die Anfrage trotzdem angenommen: ein Ausfall dort darf keine Kundenanfrage verschlucken.</p>
|
||
<label for="robotsExtra">Zusätzliche Zeilen für robots.txt</label>
|
||
<textarea id="robotsExtra" name="robotsExtra" rows="8">{{ settings.robotsExtra }}</textarea>
|
||
<p class="field-help">Nur Kommentarzeilen und einfache Anweisungen wie <code>Disallow: /pfad</code>, <code>Allow:</code>, <code>User-agent:</code> und <code>Crawl-delay:</code>. Leerzeilen trennen Blöcke, höchstens 200 Zeilen. Hier stehen zum Beispiel eigene Regeln je Bot – etwa welche KI-Crawler die Website lesen dürfen und welche nicht.</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="headerOverlay"><input id="headerOverlay" name="headerOverlay" type="checkbox" value="1"{{ settings.headerOverlay ? ' checked' : '' }}> Kopfbereich über dem Inhalt schweben lassen</label>
|
||
<p class="field-help">Der Kopfbereich liegt dann über der ersten Sektion, wie der transparente Header in Flatsome. Seine Farben für den gescrollten Zustand stellst du im Editor des Kopfbereichs unter Design → Hintergrund „beim Scrollen“ ein; zusammen mit „oben halten“ bleibt er beim Scrollen sichtbar.</p>
|
||
<label for="motion">Einblendungen und Hover-Bewegungen</label>
|
||
<select id="motion" name="motion">
|
||
<option value="always"{{ settings.motion == 'always' ? ' selected' : '' }}>Immer abspielen (wie Flatsome)</option>
|
||
<option value="respect"{{ settings.motion == 'respect' ? ' selected' : '' }}>Browservorgabe „Bewegung reduzieren“ beachten</option>
|
||
</select>
|
||
<p class="field-help">Manche Browser melden „Bewegung reduzieren“, ohne dass jemand es eingestellt hat – dann bleibt bei „beachten“ jede Einblendung aus. „Immer abspielen“ verhält sich wie andere Websites; die Slider-Automatik hält an, sobald jemand sie berührt.</p>
|
||
<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>
|
||
<fieldset class="field-set">
|
||
<legend>Aussehen des Hinweises</legend>
|
||
<div class="fields">
|
||
<div>
|
||
<label for="consentLayout">Anordnung</label>
|
||
<select id="consentLayout" name="consentLayout">
|
||
<option value="karte"{{ settings.consentLayout == 'karte' ? ' selected' : '' }}>Karte unten in der Mitte</option>
|
||
<option value="leiste"{{ settings.consentLayout == 'leiste' ? ' selected' : '' }}>Leiste über die volle Breite</option>
|
||
<option value="ecke"{{ settings.consentLayout == 'ecke' ? ' selected' : '' }}>Kleine Karte rechts unten</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="consentShape">Form</label>
|
||
<select id="consentShape" name="consentShape">
|
||
<option value="weich"{{ settings.consentShape == 'weich' ? ' selected' : '' }}>Abgerundet</option>
|
||
<option value="rund"{{ settings.consentShape == 'rund' ? ' selected' : '' }}>Rund</option>
|
||
<option value="eckig"{{ settings.consentShape == 'eckig' ? ' selected' : '' }}>Eckig</option>
|
||
</select>
|
||
<p class="field-help">Gilt für die Fläche und ihre Knöpfe zusammen.</p>
|
||
</div>
|
||
<div>
|
||
<label for="consentTitle">Überschrift</label>
|
||
<input id="consentTitle" name="consentTitle" maxlength="80" value="{{ settings.consentTitle }}" placeholder="Externe Inhalte und Einwilligung">
|
||
</div>
|
||
<div>
|
||
<label for="consentColor">Farbe der Fläche</label>
|
||
<input id="consentColor" name="consentColor" value="{{ settings.consentColor }}" placeholder="@paper" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||
</div>
|
||
<div>
|
||
<label for="consentInk">Schriftfarbe</label>
|
||
<input id="consentInk" name="consentInk" value="{{ settings.consentInk }}" placeholder="@ink" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||
</div>
|
||
<div>
|
||
<label for="consentAccent">Farbe des Knopfes „Alle akzeptieren“</label>
|
||
<input id="consentAccent" name="consentAccent" value="{{ settings.consentAccent }}" placeholder="@accent" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||
<p class="field-help">Färbt auch den Streifen oben, das Symbol und die Kästchen.</p>
|
||
</div>
|
||
<div>
|
||
<label for="consentAccentInk">Schrift auf dem Knopf</label>
|
||
<input id="consentAccentInk" name="consentAccentInk" value="{{ settings.consentAccentInk }}" placeholder="#ffffff" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||
</div>
|
||
</div>
|
||
<p class="field-help">Leer heißt: Papier, Tinte und Akzent aus dem Design. „Nur notwendige“ bleibt bewusst ein Rahmenknopf in der Schriftfarbe – beide Antworten sind gleich groß, keine wird versteckt.</p>
|
||
</fieldset>
|
||
<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 %}
|