The icon set had no drawing for people, music or a house - the three pictures
the Tagespflege Moldovan template asks for. They join as `people`, `music` and
`home`; a test keeps the list and the drawings together, because a missing path
renders an empty `<path d="">`: invisible, without an error.
An icon block's own colour never reached its drawing: the embedded SVG carries
the same `block-icon` class as its wrapper and set the accent colour again.
`.block-icon .block-icon{color:inherit}` fixes it at the root.
The menu button becomes a thing you can shape. `burgerLabel` chooses symbol and
text, text only or symbol only (which keeps the name for screen readers), and
the new style group `menu` targets the button itself, the way `img` targets an
image and `controls` the slider. Inside the header the opened menu now lies
under the header across the full width instead of pushing the header apart -
on a phone that looked like a missing navigation.
The back-to-top button no longer depends on `animation-timeline: scroll()`. A
browser that parses the property but does not run the timeline left it in the
first frame of the animation: permanently invisible. Two classes from
scrolled.js decide now, and without a script the button simply stays visible.
Also: the Tagespflege Moldovan rebuild as its own installation, built only by
`scripts/seed-tagespflege-moldovan.php`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
276 lines
25 KiB
Twig
276 lines
25 KiB
Twig
{% set type = block.type %}
|
|
{% set props = block.props|default({}) %}
|
|
{% set layout = block.layout|default({}) %}
|
|
{% set designClasses = layout.visibility|default('all') != 'all' ? ' visibility-' ~ layout.visibility : '' %}
|
|
{% for device, settings in layout.design|default({}) %}
|
|
{% for key, value in settings %}{% set designClasses = designClasses ~ ' d-' ~ device ~ '-' ~ key ~ '-' ~ value %}{% endfor %}
|
|
{% endfor %}
|
|
{% for key, value in layout.effects|default({}) %}{% set designClasses = designClasses ~ ' fx-' ~ key ~ '-' ~ value %}{% endfor %}
|
|
{% if type in ['section', 'row', 'column'] %}
|
|
{% set tag = type == 'section' ? 'section' : 'div' %}
|
|
<{{ tag }} id="{{ block.id }}" class="block-{{ type }} tone-{{ layout.tone|default('paper') }} width-{{ layout.width|default('normal') }}{{ designClasses }}{{ block.anchor|default('') ? ' has-anchor' : '' }}">
|
|
{% if block.anchor|default('') %}<span id="{{ block.anchor }}" class="block-anchor" aria-hidden="true"></span>{% endif %}
|
|
{% if type == 'section' %}<div class="shell section-inner">{% endif %}
|
|
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
{% if type == 'section' %}</div>{% endif %}
|
|
</{{ tag }}>
|
|
{% elseif type == 'heading' %}
|
|
<h{{ props.level }} id="{{ block.id }}" class="{{ designClasses|trim }}">{% if richHeadings|default(false) %}{{ props.text|raw }}{% else %}{{ props.text }}{% endif %}</h{{ props.level }}>
|
|
{% elseif type == 'text' %}
|
|
{% if richText|default(false) %}
|
|
{# Already filtered by RichText when it was stored, so printing it unescaped is safe.
|
|
A div, not a p: the allowlist includes lists and paragraphs, which may not nest in one. #}
|
|
<div id="{{ block.id }}" class="block-text{{ designClasses }}">{{ props.text|raw }}</div>
|
|
{% else %}
|
|
<p id="{{ block.id }}" class="block-text{{ designClasses }}">{{ props.text }}</p>
|
|
{% endif %}
|
|
{% elseif type == 'button' %}
|
|
{% for device, settings in layout.button|default({}) %}{% for key, value in settings %}{% set designClasses = designClasses ~ ' b-' ~ device ~ '-' ~ key ~ '-' ~ value %}{% endfor %}{% endfor %}
|
|
<a id="{{ block.id }}" class="button cms-button{{ designClasses }}" href="{{ props.href }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab|default(false)} only %}>{{ props.text }} <span aria-hidden="true">↗</span></a>
|
|
{% elseif type == 'image' %}
|
|
{# Wie breit das Bild tatsaechlich erscheint. Steht im Block eine feste Breite - ein Logo mit
|
|
190px, ein Symbol mit 72px -, dann ist das die Antwort, und der Browser laedt die kleine
|
|
Variante statt der grossen. Ohne feste Breite bleibt es bei der Annahme nach Spaltenbreite. #}
|
|
{% set festeBreite = block.style.desktop.maxWidth|default('') %}
|
|
{% set eager = eagerImage|default(null) == block.id %}
|
|
{# „auto“ heißt: der Browser misst selbst nach, wie breit das Bild tatsächlich erscheint, und
|
|
wählt genau dazu die Variante. Das geht nur bei verzögert geladenen Bildern - beim
|
|
vorgezogenen Bild steht die Größe noch nicht fest. Die Angaben dahinter sind der Rückfall
|
|
für Browser, die „auto“ noch nicht kennen (heute Safari). #}
|
|
{% set annahme = '(max-width: 650px) 100vw, (max-width: 1024px) 50vw, 800px' %}
|
|
{% set imageSizes = festeBreite ends with 'px' ? festeBreite : (eager or inHeader|default(false) ? annahme : 'auto, ' ~ annahme) %}
|
|
{% set klick = props.click|default(props.href|default('') ? 'link' : 'keine') %}
|
|
{% set imageLinked = klick == 'link' and props.href|default('') %}
|
|
{% set grossesBild = props.sources|default([]) ? props.sources|last.src : props.src %}
|
|
<figure id="{{ block.id }}" class="block-image{{ designClasses }}">
|
|
{%- if klick == 'vergroessern' -%}
|
|
{# Ohne Skript öffnet dieser Verweis schlicht das große Bild. Mit Skript fängt der
|
|
Betrachter den Klick ab - die Seite bleibt also in jedem Fall benutzbar. #}
|
|
<a class="image-link image-zoom" href="{{ grossesBild }}" data-zoom data-caption="{{ props.caption|default('') }}" aria-label="{{ props.alt|default('Bild') }} vergrößern">
|
|
{%- elseif imageLinked -%}
|
|
<a class="image-link" href="{{ props.href }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab|default(false)} only %}>
|
|
{%- endif -%}<img src="{{ props.src }}" alt="{{ props.alt }}" width="{{ props.width }}" height="{{ props.height }}" loading="{{ eager or inHeader|default(false) ? 'eager' : 'lazy' }}"{{ eager ? ' fetchpriority="high"' : '' }} decoding="async"{% if props.sources|default([]) %} srcset="{% for source in props.sources %}{{ source.src }} {{ source.width }}w{% if not loop.last %}, {% endif %}{% endfor %}" sizes="{{ imageSizes }}"{% endif %}>{% if klick == 'vergroessern' or imageLinked %}</a>{% endif %}{% if props.caption|default('') %}<figcaption>{{ props.caption }}</figcaption>{% endif %}</figure>
|
|
{% elseif type == 'iconcard' %}
|
|
<div id="{{ block.id }}" class="block-iconcard{{ designClasses }}">
|
|
{% include 'blocks/icon.html.twig' with {name: props.icon} only %}
|
|
<h3>{% if props.href|default('') %}<a href="{{ props.href }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab|default(false)} only %}>{{ props.title }}</a>{% else %}{{ props.title }}{% endif %}</h3>
|
|
{% if props.text|default('') %}<p>{{ props.text }}</p>{% endif %}
|
|
</div>
|
|
{% elseif type == 'stat' %}
|
|
<p id="{{ block.id }}" class="block-stat{{ designClasses }}"><strong>{{ props.value }}</strong><span>{{ props.label }}</span></p>
|
|
{% elseif type == 'quote' %}
|
|
<figure id="{{ block.id }}" class="block-quote{{ designClasses }}">
|
|
{% if props.rating|default(0) > 0 %}
|
|
<p class="quote-rating"><span class="visually-hidden">Bewertung: {{ props.rating }} von 5</span><span aria-hidden="true">{% for i in 1..5 %}{{ i <= props.rating ? '★' : '☆' }}{% endfor %}</span></p>
|
|
{% endif %}
|
|
<blockquote><p>{{ props.text }}</p></blockquote>
|
|
<figcaption>{{ props.author }}{% if props.role|default('') %} <span>{{ props.role }}</span>{% endif %}</figcaption>
|
|
</figure>
|
|
{% elseif type == 'logos' %}
|
|
<ul id="{{ block.id }}" class="block-logos{{ designClasses }}">
|
|
{% for child in block.children|default([]) %}<li class="visibility-{{ child.layout.visibility|default('all') }}">{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}</li>{% endfor %}
|
|
</ul>
|
|
{% elseif type == 'cta' %}
|
|
<div id="{{ block.id }}" class="block-cta{{ designClasses }}">
|
|
<div>
|
|
<h2>{{ props.title }}</h2>
|
|
{% if props.text|default('') %}<p>{{ props.text }}</p>{% endif %}
|
|
</div>
|
|
<a class="button" href="{{ props.href }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab|default(false)} only %}>{{ props.buttonText }}</a>
|
|
</div>
|
|
{% elseif type == 'contact' %}
|
|
<address id="{{ block.id }}" class="block-contact{{ designClasses }}">
|
|
{% if props.phone|default('') %}<span>{% include 'blocks/icon.html.twig' with {name: 'phone'} only %}<a href="tel:{{ props.phone|replace({' ': '', '(': '', ')': '', '/': '', '-': ''}) }}">{{ props.phone }}</a></span>{% endif %}
|
|
{% if props.email|default('') %}<span>{% include 'blocks/icon.html.twig' with {name: 'mail'} only %}<a href="mailto:{{ props.email }}">{{ props.email }}</a></span>{% endif %}
|
|
{% if props.address|default('') %}<span>{% include 'blocks/icon.html.twig' with {name: 'pin'} only %}{{ props.address }}</span>{% endif %}
|
|
</address>
|
|
{% elseif type == 'divider' %}
|
|
<hr id="{{ block.id }}" class="block-divider{{ designClasses }}">
|
|
{% elseif type == 'spacer' %}
|
|
<div id="{{ block.id }}" class="block-spacer spacer-{{ props.size }}{{ designClasses }}" aria-hidden="true"></div>
|
|
{% elseif type == 'menu' %}
|
|
{% set links = navigation[props.slot]|default([]) %}
|
|
{% if links is not empty %}
|
|
{# The layout is a class, and the burger variant is a details element: a menu that needs a
|
|
script to open would be a menu that does not open on a page without one. #}
|
|
{% set menuLayout = props.layout|default('horizontal') %}
|
|
{% set collapse = menuLayout == 'horizontal' ? props.collapse|default('tablet') : 'never' %}
|
|
<nav id="{{ block.id }}" class="block-menu menu-{{ menuLayout }} menu-collapse-{{ collapse }}{{ designClasses }}"
|
|
aria-label="{{ {header: 'Hauptnavigation', secondary: 'Zweite Navigation', footer: 'Fußzeile', legal: 'Rechtliches'}[props.slot]|default('Navigation') }}">
|
|
{% if menuLayout == 'burger' or collapse != 'never' %}
|
|
<details class="menu-burger">
|
|
{# Nur Symbol behält den Namen für Vorlesesoftware; sichtbar ist dann nur der Strich. #}
|
|
{% set burger = props.burgerLabel|default('both') %}
|
|
<summary>{% if burger != 'text' %}<span class="burger-lines" aria-hidden="true"></span>{% endif %}<span class="burger-text{{ burger == 'icon' ? ' visually-hidden' : '' }}">Menü</span></summary>
|
|
<div class="menu-burger-body">{% include 'nav.html.twig' with {links: links} only %}</div>
|
|
</details>
|
|
{% endif %}
|
|
{% if menuLayout != 'burger' %}
|
|
{% include 'nav.html.twig' with {links: links} only %}
|
|
{% endif %}
|
|
</nav>
|
|
{% endif %}
|
|
{% elseif type == 'partialref' %}
|
|
{# A linked section: its blocks come from the partial, resolved by the controller. One change
|
|
there reaches every page that links it, which is the difference to a copied section. #}
|
|
{% set section = sections[props.partial]|default(null) %}
|
|
{% if section %}
|
|
<div id="{{ block.id }}" class="block-partialref{{ designClasses }}">
|
|
{% for child in section.blocks %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
</div>
|
|
{% elseif editing|default(false) %}
|
|
<p id="{{ block.id }}" class="block-unavailable{{ designClasses }}" role="note">Die verknüpfte Sektion „{{ props.partial }}“ gibt es nicht mehr. Der Verweis bleibt gespeichert.</p>
|
|
{% else %}
|
|
<!-- Verknüpfte Sektion {{ props.partial }} ist nicht vorhanden. -->
|
|
{% endif %}
|
|
{% elseif type == 'breadcrumb' %}
|
|
{% if breadcrumbs|default([])|length > 1 %}
|
|
<nav id="{{ block.id }}" class="block-breadcrumb{{ designClasses }}" aria-label="Brotkrumen">
|
|
<ol>
|
|
<li><a href="/">Start</a></li>
|
|
{% for crumb in breadcrumbs %}
|
|
<li>{% if loop.last %}<span aria-current="page">{{ crumb.name }}</span>{% else %}<a href="{{ crumb.href }}">{{ crumb.name }}</a>{% endif %}</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</nav>
|
|
{% endif %}
|
|
{% elseif type == 'form' %}
|
|
<div id="{{ block.id }}" class="block-formwrap{{ designClasses }}">
|
|
{% include 'blocks/form.html.twig' with (forms[block.id]|default({form: null, sent: false, errors: {}, values: {}, blockId: block.id, page: {path: ''}, issued: 0, issuedSignature: ''})) only %}
|
|
</div>
|
|
{% elseif type == 'tabs' %}
|
|
{# Without the script this is an anchor list followed by every panel: complete and readable.
|
|
tabs.js turns it into a tab widget; it only changes presentation, never content. #}
|
|
<div id="{{ block.id }}" class="block-tabs{{ designClasses }}" data-tabs>
|
|
<ul class="tabs-nav">
|
|
{% for child in block.children|default([]) %}<li class="visibility-{{ child.layout.visibility|default('all') }}"><a href="#{{ child.id }}">{{ child.props.title }}</a></li>{% endfor %}
|
|
</ul>
|
|
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
</div>
|
|
{% elseif type == 'tabitem' %}
|
|
<section id="{{ block.id }}" class="block-tabitem{{ designClasses }}">
|
|
<h3 class="tabs-heading">{{ props.title }}</h3>
|
|
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
</section>
|
|
{% elseif type == 'accordion' %}
|
|
<div id="{{ block.id }}" class="block-accordion{{ designClasses }}">
|
|
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
</div>
|
|
{% elseif type == 'accordionitem' %}
|
|
<details id="{{ block.id }}" class="block-accordionitem{{ designClasses }}"{% if props.open|default(false) %} open{% endif %}>
|
|
<summary>{{ props.title }}</summary>
|
|
<div class="accordion-body">
|
|
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
</div>
|
|
</details>
|
|
{% elseif type == 'icon' %}
|
|
{# A single icon. Decorative ones are hidden from assistive technology; one that means
|
|
something carries its label as text for screen readers. #}
|
|
{% set iconBody %}{% include 'blocks/icon.html.twig' with {name: props.icon} only %}{% if not props.decorative %}<span class="visually-hidden">{{ props.label }}</span>{% endif %}{% endset %}
|
|
{% if props.href|default('') %}
|
|
<a id="{{ block.id }}" class="block-icon icon-size-{{ props.size }}{{ designClasses }}" href="{{ props.href }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab|default(false)} only %}{{ props.decorative ? ' aria-hidden="true" tabindex="-1"' : '' }}>{{ iconBody }}</a>
|
|
{% else %}
|
|
<span id="{{ block.id }}" class="block-icon icon-size-{{ props.size }}{{ designClasses }}"{{ props.decorative ? ' aria-hidden="true"' : '' }}>{{ iconBody }}</span>
|
|
{% endif %}
|
|
{% elseif type == 'social' %}
|
|
{# Die Profile kommen aus den Einstellungen, der Block sagt nur, wie sie aussehen. Eine
|
|
Liste, weil es eine ist; ohne Beschriftung trägt jeder Link seinen Namen unsichtbar. #}
|
|
{% set profiles = siteSettings.load().social|default({}) %}
|
|
{% set chosen = props.networks|default([]) %}
|
|
{% set shown = [] %}
|
|
{% for key, name in constant('App\\Content\\SocialProfiles::NETWORKS') %}
|
|
{% if profiles[key]|default('') and (chosen is empty or key in chosen) %}{% set shown = shown|merge([{key: key, name: name, url: profiles[key]}]) %}{% endif %}
|
|
{% endfor %}
|
|
{% if shown is not empty %}
|
|
<ul id="{{ block.id }}" class="block-social social-{{ props.layout|default('horizontal') }} social-shape-{{ props.shape|default('plain') }} social-size-{{ props.size|default(24) }}{{ props.labels|default(false) ? ' social-labels' : '' }}{{ designClasses }}" aria-label="Soziale Profile">
|
|
{% for profile in shown %}
|
|
<li><a href="{{ profile.url }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab is defined ? props.newTab : true} only %}>{% include 'blocks/social-icon.html.twig' with {network: profile.key} only %}<span{{ props.labels|default(false) ? '' : ' class="visually-hidden"' }}>{{ profile.name }}</span></a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% elseif editing|default(false) %}
|
|
<p id="{{ block.id }}" class="block-unavailable{{ designClasses }}" role="note">Noch keine sozialen Profile eingetragen. Sie stehen unter Einstellungen → Unternehmensdaten → Soziale Profile.</p>
|
|
{% else %}
|
|
<!-- Block {{ block.id }} (social): keine Profile eingetragen. -->
|
|
{% endif %}
|
|
{% elseif type == 'progress' %}
|
|
{# A progress bar as a real meter: the number is in the markup, not only in the width of a
|
|
coloured div, so it is readable without seeing it. #}
|
|
<div id="{{ block.id }}" class="block-progress{{ designClasses }}">
|
|
<p class="progress-label">{{ props.label }}{% if props.showValue %} <span class="progress-value">{{ props.value }} %</span>{% endif %}</p>
|
|
<div class="progress-track" role="meter" aria-valuenow="{{ props.value }}" aria-valuemin="0" aria-valuemax="100" aria-label="{{ props.label }}">
|
|
<div class="progress-fill progress-fill-{{ props.value }}"></div>
|
|
</div>
|
|
</div>
|
|
{% elseif type == 'slider' %}
|
|
{# A slider without a script: a scroll container with scroll snapping. It works with a finger,
|
|
a trackpad, a mouse wheel and the keyboard, and it degrades to a scrollable strip on a
|
|
browser that ignores snapping. No autoplay - a moving carousel is something people ask to
|
|
stop, not something a page should start on its own. #}
|
|
<div id="{{ block.id }}" class="block-slider{{ designClasses }} slide-desktop-{{ props.desktopVisible }} slide-tablet-{{ props.tabletVisible }} slide-mobile-{{ props.mobileVisible }}{{ props.snap ? ' slider-snap' : '' }}{{ props.dots ?? false ? ' slider-dots' : '' }}"{% if props.autoplay|default(0) >= 3 %} data-autoplay="{{ props.autoplay }}"{% endif %}
|
|
tabindex="0" role="group" aria-label="Zum Blättern seitwärts scrollen">
|
|
{% for child in block.children|default([]) %}
|
|
<div class="slider-slide visibility-{{ child.layout.visibility|default('all') }}">{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% elseif type == 'embed' %}
|
|
{# Content from a third party. Until the group is granted, nothing of it is requested - no
|
|
iframe, no image, no preconnect. That is the whole point of the consent question: the
|
|
placeholder is not a nicety, it is what keeps the promise.
|
|
In the editor it always stays a placeholder: the administration's policy forbids foreign
|
|
frames, and a preview must not load anything from outside either. #}
|
|
{% if not editing|default(false) and consentGate.granted(app.request, props.group) %}
|
|
<div id="{{ block.id }}" class="block-embed{{ designClasses }}">
|
|
<iframe src="{{ props.url }}" title="{{ props.title }}" height="{{ props.height }}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin"></iframe>
|
|
</div>
|
|
{% else %}
|
|
<div id="{{ block.id }}" class="block-embed embed-blocked{{ designClasses }}">
|
|
<p class="embed-title">{{ props.title }}</p>
|
|
<p class="embed-hint">{{ props.hint ?: 'Dieser Inhalt wird von einem anderen Anbieter geladen. Dabei erfährt er deine IP-Adresse und welche Seite du ansiehst.' }}</p>
|
|
<p class="embed-provider">Anbieter: <strong>{{ props.provider ?: props.url|split('/')[2] }}</strong></p>
|
|
{% if editing|default(false) %}
|
|
<p class="embed-hint">Wird erst auf der veröffentlichten Seite geladen, und dort erst nach Einwilligung.</p>
|
|
{% else %}
|
|
<form method="post" action="{{ path('consent_save') }}">
|
|
<input type="hidden" name="back" value="{{ app.request.pathInfo }}">
|
|
<input type="hidden" name="choice" value="some">
|
|
{# The groups already granted stay granted; this button adds this one. #}
|
|
{% for granted in consentGate.state(app.request).groups %}{% if granted.granted %}<input type="hidden" name="groups[]" value="{{ granted.key }}">{% endif %}{% endfor %}
|
|
<input type="hidden" name="groups[]" value="{{ props.group }}">
|
|
<button class="consent-button" type="submit">Inhalt laden und einwilligen</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% elseif type == 'postlist' %}
|
|
<div id="{{ block.id }}" class="block-postlist{{ designClasses }}">
|
|
{% set feed = postLists[block.id]|default([]) %}
|
|
{% include 'blocks/post-list.html.twig' with {editing: editing|default(false), posts: feed.posts|default(feed), pages: feed.pages|default(1), page: feed.page|default(1), total: feed.total|default(null), layout: block.props.layout|default('karten'), showMeta: block.props.showMeta ?? true, showExcerpt: block.props.showExcerpt ?? true, showTerms: block.props.showTerms ?? true} only %}
|
|
</div>
|
|
{% elseif type == 'postnav' %}
|
|
<div id="{{ block.id }}" class="block-postnav{{ designClasses }}">
|
|
{% include 'blocks/post-nav.html.twig' with {previous: neighbours.previous|default(null), next: neighbours.next|default(null), previousLabel: props.previousLabel|default('Vorheriger Beitrag'), nextLabel: props.nextLabel|default('Nächster Beitrag')} only %}
|
|
</div>
|
|
{% elseif type == 'legal' %}
|
|
{# Impressum, Datenschutz und - sofern schon gefragt wurde - der Weg zurück zur Einwilligung.
|
|
Als Baustein, damit die Zeile dort steht, wo sie hingehört, und die Farben ihres Bereichs
|
|
erbt statt als heller Streifen unter einem dunklen Fuß zu kleben. #}
|
|
{% set rechtliches = consentGate.legalLinks() %}
|
|
{% set widerruflich = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %}
|
|
<nav id="{{ block.id }}" class="block-legal{{ designClasses }}" aria-label="{{ props.label|default('Rechtliches') }}">
|
|
{% for path, label in rechtliches %}{% if not loop.first %}<span aria-hidden="true"> · </span>{% endif %}<a href="/{{ path }}">{{ label }}</a>{% endfor %}
|
|
{% if widerruflich %}{% if rechtliches is not empty %}<span aria-hidden="true"> · </span>{% endif %}<a href="/einwilligung">Einwilligung ändern</a>{% endif %}
|
|
</nav>
|
|
{% elseif type == 'gallery' %}
|
|
<div id="{{ block.id }}" class="block-gallery{{ designClasses }}" role="group" aria-label="Bildergalerie"{% if props.lightbox|default(false) %} data-zoom-group{% endif %}>
|
|
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
{#
|
|
A block whose plugin is not available. Its stored content stays in the document; the core
|
|
renders nothing from its unvalidated props. Visitors see no broken fragment, while the
|
|
editor preview shows a placeholder so the block can still be found and moved.
|
|
#}
|
|
<!-- Block {{ block.id }} ({{ type }}) wird von dieser Installation nicht dargestellt. -->
|
|
{% if editing|default(false) %}<p id="{{ block.id }}" class="block-unavailable{{ designClasses }}" role="note">Baustein „{{ type }}“ ist in dieser Installation nicht verfügbar. Der Inhalt bleibt gespeichert.</p>{% endif %}
|
|
{% endif %}
|