Forms are an editor now: one card per field with add and remove, the technical name derived from the label, options only for a choice, and a new "Aussehen" section with a button label and four colours (field border and background, button and button text) that travel to the page as one rule per form inside the hashed style block. A live preview draws the form with the site's default button and names the text contrast. Statistics open with six figures (views against the same span before, average per day, busiest day, pages, referred views, missing addresses), a chart with grid lines and a title per bar, a weekday chart and ranked lists with share bars. The design screen shows the palette as colour cards with seven ready-made palettes, picks the base colours from the palette, previews every font in itself and the typography as a sample. The sidebar ends in an account card with role and a logout icon. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
64 lines
4.7 KiB
Twig
64 lines
4.7 KiB
Twig
{# Server rendered, no JavaScript. Errors are announced and each message sits at its field. #}
|
|
{% if form is null %}
|
|
<p class="form-missing">Dieses Formular ist nicht mehr vorhanden.</p>
|
|
{% elseif sent %}
|
|
<p class="form-success" role="status">{{ form.settings.success }}</p>
|
|
{% else %}
|
|
<form class="block-form" data-form="{{ form.slug }}" method="post" action="{{ path('form_submit', {slug: form.slug}) }}" novalidate>
|
|
<input type="hidden" name="page" value="{{ page.path }}">
|
|
<input type="hidden" name="block" value="{{ blockId }}">
|
|
<input type="hidden" name="issued" value="{{ issued }}">
|
|
<input type="hidden" name="issuedSignature" value="{{ issuedSignature }}">
|
|
{# Bait for automated senders. Hidden from view, from assistive technology and from tab order. #}
|
|
<div class="form-trap" aria-hidden="true"><label for="website-{{ blockId }}">Website</label><input id="website-{{ blockId }}" name="website" type="text" tabindex="-1" autocomplete="off"></div>
|
|
|
|
{% if errors is not empty %}
|
|
<p class="form-error-summary" role="alert">Bitte prüfe {{ errors|length }} {{ errors|length == 1 ? 'Eingabe' : 'Eingaben' }}. Die betroffenen Felder sind unten gekennzeichnet.</p>
|
|
{% endif %}
|
|
|
|
{% for field in form.fields %}
|
|
{% set id = blockId ~ '-' ~ field.name %}
|
|
{% set error = errors[field.name]|default(null) %}
|
|
{% set value = values[field.name]|default('') %}
|
|
{% set described = (field.help ? id ~ '-help ' : '') ~ (error ? id ~ '-error' : '') %}
|
|
<div class="form-field{{ error ? ' has-error' : '' }}">
|
|
{% if field.type == 'consent' %}
|
|
<div class="check-row">
|
|
<input id="{{ id }}" name="{{ field.name }}" type="checkbox" value="ja"{{ value ? ' checked' : '' }} required
|
|
{% if described|trim %}aria-describedby="{{ described|trim }}"{% endif %}{{ error ? ' aria-invalid="true"' : '' }}>
|
|
<label for="{{ id }}">{{ field.label }} <span class="required-mark" aria-hidden="true">*</span></label>
|
|
</div>
|
|
{% else %}
|
|
<label for="{{ id }}">{{ field.label }}{% if field.required %} <span class="required-mark" aria-hidden="true">*</span><span class="visually-hidden"> (Pflichtfeld)</span>{% endif %}</label>
|
|
{% if field.type == 'textarea' %}
|
|
<textarea id="{{ id }}" name="{{ field.name }}" rows="6" maxlength="5000"{{ field.required ? ' required' : '' }}
|
|
{% if described|trim %}aria-describedby="{{ described|trim }}"{% endif %}{{ error ? ' aria-invalid="true"' : '' }}>{{ value }}</textarea>
|
|
{% elseif field.type == 'select' %}
|
|
<select id="{{ id }}" name="{{ field.name }}"{{ field.required ? ' required' : '' }}
|
|
{% if described|trim %}aria-describedby="{{ described|trim }}"{% endif %}{{ error ? ' aria-invalid="true"' : '' }}>
|
|
<option value="">— bitte wählen —</option>
|
|
{% for option in field.options %}<option value="{{ option }}"{{ value == option ? ' selected' : '' }}>{{ option }}</option>{% endfor %}
|
|
</select>
|
|
{% else %}
|
|
<input id="{{ id }}" name="{{ field.name }}" value="{{ value }}" maxlength="200"
|
|
type="{{ {email: 'email', phone: 'tel'}[field.type]|default('text') }}"
|
|
autocomplete="{{ {email: 'email', phone: 'tel'}[field.type]|default('on') }}"
|
|
{{ field.required ? ' required' : '' }}
|
|
{% if described|trim %}aria-describedby="{{ described|trim }}"{% endif %}{{ error ? ' aria-invalid="true"' : '' }}>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if field.help %}<p class="field-note" id="{{ id }}-help">{{ field.help }}</p>{% endif %}
|
|
{% if error %}<p class="field-error" id="{{ id }}-error">{{ error }}</p>{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% if turnstileKey|default('') %}
|
|
{# Cloudflare Turnstile. Braucht JavaScript; ohne Skript bleibt das Formular unabsendbar, und
|
|
genau deshalb ist die Prüfung eine Entscheidung und keine Voreinstellung. #}
|
|
<div class="cf-turnstile" data-sitekey="{{ turnstileKey }}" data-language="de"></div>
|
|
<noscript><p class="field-error">Zum Absenden dieses Formulars wird JavaScript benötigt. Schreib uns sonst gern direkt eine E-Mail.</p></noscript>
|
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
|
{% endif %}
|
|
<button class="button cms-button" type="submit">{{ form.settings.buttonLabel|default('Absenden') }}</button>
|
|
</form>
|
|
{% endif %}
|