Make every image of a zoomable gallery zoomable unless it chose otherwise

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-22 08:38:02 +02:00
1 parent e47f278a13
commit 24f98633d0
4 files changed
+49 -4

No files matched your search

+10
View File
@@ -1,5 +1,15 @@
# Projektstatus # Projektstatus
## Fassung 1.22.1: vergrößerbare Galerie mit einem Haken (22.09.2026)
**Anlass:** Umut: „Bei Klick vergrößern bei Galerie muss man da alles einzeln machen? kann man bei Galerie das mit einem Klick für alle elemente machen?“
- Der Haken „Bilder vergrößerbar“ an der Galerie gilt jetzt für jedes Bild darin, das unter „Beim Klick“ nichts selbst gewählt hat. Das gilt auch für später hinzugefügte Bilder. Bisher schaltete er nur das Blättern im Popup ein, und jedes Bild musste einzeln auf „Bild vergrößern“ gestellt werden.
- Eine eigene Wahl geht vor: „Nichts tun“ bleibt still, ein Link bleibt ein Link. „Nie eingestellt“ und „Nichts tun“ sind im Dokument unterscheidbar, weil der Editor `click` erst beim Ändern speichert. Bestehende Galerien ändern sich deshalb nur dort, wo der Haken gesetzt ist und ein Bild noch nie eingestellt wurde.
- Der Editor zeigt bei einem solchen Bild „Bild vergrößern“ als aktuelle Wahl, nach derselben Regel wie die Vorlage (`galleryZoom` in `blocks/block.html.twig`).
**Geprüft:** neuer Test `CmsTest::testAZoomableGalleryMakesEveryImageWithoutItsOwnChoiceZoomable` (ohne Wahl, „Nichts tun“, Link, Galerie ohne Haken), volle Kette vor dem Release.
## PDFs in der Mediathek, als Linkziel und im Betrachter (22.09.2026) ## PDFs in der Mediathek, als Linkziel und im Betrachter (22.09.2026)
**Anlass:** Umut: „kann man aktuell PDFs hochladen und als Button verlinken? … und ein Inline PDF Viewer wie Bild-Popup“, danach: „ja setz um, einfacher Browser-Betrachter“. **Anlass:** Umut: „kann man aktuell PDFs hochladen und als Button verlinken? … und ein Inline PDF Viewer wie Bild-Popup“, danach: „ja setz um, einfacher Browser-Betrachter“.
+5 -2
View File
@@ -541,7 +541,10 @@ function renderContent(block, panel) {
panel.append(imagePreview.element); panel.append(imagePreview.element);
const linkOptions = document.createElement('div'); linkOptions.className = 'image-link-options'; const linkOptions = document.createElement('div'); linkOptions.className = 'image-link-options';
const showLinkOptions = value => { linkOptions.hidden = value !== 'link'; }; const showLinkOptions = value => { linkOptions.hidden = value !== 'link'; };
const clickAction = block.props?.click || (block.props?.href ? 'link' : 'keine'); // Ohne eigene Wahl folgt das Bild seiner Galerie - dieselbe Regel wie in der Vorlage.
const holder = locate(history.document.blocks, block.id)?.parent;
const galleryZoom = holder?.type === 'gallery' && holder.props?.lightbox === true;
const clickAction = block.props?.click || (block.props?.href ? 'link' : (galleryZoom ? 'vergroessern' : 'keine'));
field('Beim Klick', 'click', clickAction, { choices: { keine: 'Nichts tun', vergroessern: 'Bild vergrößern', link: 'Zu einer Adresse führen' }, onInput: showLinkOptions }); field('Beim Klick', 'click', clickAction, { choices: { keine: 'Nichts tun', vergroessern: 'Bild vergrößern', link: 'Zu einer Adresse führen' }, onInput: showLinkOptions });
field('Linkziel', 'href', block.props.href || '', { container: linkOptions, help: 'Lokaler Pfad (/kontakt), Sprungmarke (#abschnitt) oder HTTPS-Adresse – oder ein PDF. Für ein Logo im Kopfbereich meist /.' }); field('Linkziel', 'href', block.props.href || '', { container: linkOptions, help: 'Lokaler Pfad (/kontakt), Sprungmarke (#abschnitt) oder HTTPS-Adresse – oder ein PDF. Für ein Logo im Kopfbereich meist /.' });
showLinkOptions(clickAction); panel.append(linkOptions); showLinkOptions(clickAction); panel.append(linkOptions);
@@ -558,7 +561,7 @@ function renderContent(block, panel) {
field('Bildunterschrift', 'caption', block.props.caption || '', { multiline: true, maxLength: 500 }); field('Bildunterschrift', 'caption', block.props.caption || '', { multiline: true, maxLength: 500 });
} }
if (block.type === 'gallery') { if (block.type === 'gallery') {
field('Bilder vergrößerbar', 'lightbox', block.props?.lightbox === true, { checkbox: true, help: 'Ein Klick öffnet das Bild über der Seite, mit Blättern durch die Galerie. Die einzelnen Bilder brauchen dafür „Beim Klick: Bild vergrößern".' }); field('Bilder vergrößerbar', 'lightbox', block.props?.lightbox === true, { checkbox: true, help: 'Gilt für alle Bilder der Galerie: ein Klick öffnet das Bild über der Seite, mit Blättern durch die Galerie. Ein Bild mit eigener Einstellung unter „Beim Klick“ behält sie.' });
const hint = document.createElement('p'); hint.className = 'panel-hint'; hint.textContent = `${block.children?.length || 0} Bilder · Bilder im Strukturbaum auswählen, beschriften und mit den Pfeilen sortieren. Spalten und Abstände stehen unter Design.`; const hint = document.createElement('p'); hint.className = 'panel-hint'; hint.textContent = `${block.children?.length || 0} Bilder · Bilder im Strukturbaum auswählen, beschriften und mit den Pfeilen sortieren. Spalten und Abstände stehen unter Design.`;
const picker = document.createElement('button'); picker.className = 'primary'; picker.textContent = 'Galeriebilder auswählen'; const picker = document.createElement('button'); picker.className = 'primary'; picker.textContent = 'Galeriebilder auswählen';
picker.addEventListener('click', () => chooseMedia(true)); panel.append(hint, picker); picker.addEventListener('click', () => chooseMedia(true)); panel.append(hint, picker);
+4 -2
View File
@@ -39,7 +39,9 @@
für Browser, die „auto“ noch nicht kennen (heute Safari). #} für Browser, die „auto“ noch nicht kennen (heute Safari). #}
{% set annahme = '(max-width: 650px) 100vw, (max-width: 1024px) 50vw, 800px' %} {% 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 imageSizes = festeBreite ends with 'px' ? festeBreite : (eager or inHeader|default(false) ? annahme : 'auto, ' ~ annahme) %}
{% set klick = props.click|default(props.href|default('') ? 'link' : 'keine') %} {# Ohne eigene Wahl folgt ein Bild seiner Galerie: ist sie vergrößerbar, ist es das Bild auch.
Eine eigene Wahl - auch „Nichts tun“ - geht vor, ebenso ein Link. #}
{% set klick = props.click|default(props.href|default('') ? 'link' : (galleryZoom|default(false) ? 'vergroessern' : 'keine')) %}
{% set imageLinked = klick == 'link' and props.href|default('') %} {% set imageLinked = klick == 'link' and props.href|default('') %}
{% set grossesBild = props.sources|default([]) ? props.sources|last.src : props.src %} {% set grossesBild = props.sources|default([]) ? props.sources|last.src : props.src %}
<figure id="{{ block.id }}" class="block-image{{ designClasses }}"> <figure id="{{ block.id }}" class="block-image{{ designClasses }}">
@@ -284,7 +286,7 @@
</nav> </nav>
{% elseif type == 'gallery' %} {% 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 %}> <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 %} {% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, galleryZoom: props.lightbox|default(false), 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> </div>
{% elseif kant_plugin_block(type) %} {% elseif kant_plugin_block(type) %}
{# Ein Baustein eines aktiven Plugins. Die Vorlage bekommt nur, was der Kern geprüft hat: {# Ein Baustein eines aktiven Plugins. Die Vorlage bekommt nur, was der Kern geprüft hat:
+30
View File
@@ -1154,6 +1154,36 @@ final class CmsTest extends TestCase
self::assertStringContainsString("script-src 'none'", (string) $zweite->headers->get('Content-Security-Policy')); self::assertStringContainsString("script-src 'none'", (string) $zweite->headers->get('Content-Security-Policy'));
} }
public function testAZoomableGalleryMakesEveryImageWithoutItsOwnChoiceZoomable(): void
{
// Umut: „Bei Galerie muss man da alles einzeln machen? kann man das mit einem Klick für
// alle Elemente machen?" Der Haken an der Galerie gilt jetzt für jedes Bild darin, das
// selbst nichts gewählt hat. Eine eigene Wahl und ein Link gehen vor.
$bild = static fn (string $id, array $props): array => ['id' => $id, 'type' => 'image', 'props' => [
'src' => '/media/'.str_repeat('a', 32).'/480', 'alt' => 'Motiv '.$id, 'decorative' => false, 'width' => 480, 'height' => 270, ...$props,
]];
$document = DemoContent::starter('Startseite');
$document['schemaVersion'] = 19;
$document['blocks'][] = ['id' => 'galerie', 'type' => 'gallery', 'props' => ['lightbox' => true], 'children' => [
$bild('frei1', []),
$bild('frei2', []),
$bild('still', ['click' => 'keine']),
$bild('verlinkt', ['click' => 'link', 'href' => '/kontakt']),
]];
$document['blocks'][] = ['id' => 'ohne', 'type' => 'gallery', 'props' => [], 'children' => [$bild('ruhig', [])]];
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1);
$html = $this->request('/')->getContent();
self::assertMatchesRegularExpression('#<figure id="frei1"[^>]*>\s*<a class="image-link image-zoom"#', $html);
self::assertMatchesRegularExpression('#<figure id="frei2"[^>]*>\s*<a class="image-link image-zoom"#', $html);
self::assertMatchesRegularExpression('#<figure id="still"[^>]*><img#', $html);
self::assertMatchesRegularExpression('#<figure id="verlinkt"[^>]*>\s*<a class="image-link" href="/kontakt"#', $html);
// Eine Galerie ohne Haken bleibt, wie sie war.
self::assertMatchesRegularExpression('#<figure id="ruhig"[^>]*><img#', $html);
self::assertSame(2, substr_count($html, 'image-zoom'));
self::assertStringContainsString('<script src="/assets/lightbox.js?v=', $html);
}
public function testAPdfFromTheLibraryIsALinkTargetAndOpensInTheViewer(): void public function testAPdfFromTheLibraryIsALinkTargetAndOpensInTheViewer(): void
{ {
// Umut: „PDFs hochladen und als Button verlinken - und ein Inline-PDF-Viewer wie das // Umut: „PDFs hochladen und als Button verlinken - und ein Inline-PDF-Viewer wie das