diff --git a/src/lib/block-types.ts b/src/lib/block-types.ts index 5f8667b..c2f2b53 100644 --- a/src/lib/block-types.ts +++ b/src/lib/block-types.ts @@ -104,6 +104,10 @@ export interface ImageGalleryImage { _slug?: string; src?: string; description?: string; + /** Optionaler Hover-Titel (image-Type: alt/caption/title). */ + alt?: string; + caption?: string; + title?: string; file?: { url?: string }; /** Nach resolveContentImages: Proxy-URL zum transformierten Bild (16:9). */ resolvedSrc?: string; @@ -115,6 +119,10 @@ export interface ImageGalleryBlockData { _slug?: string; /** Optionaler Einleitungstext (Markdown). */ description?: string; + /** Optional: oberhalb des Galerie-Modals als Header. */ + title?: string; + /** "standard" = Carousel (Default), "grid" = Thumbnail-Grid mit Modal. */ + variant?: "standard" | "grid"; images?: ImageGalleryImage[]; layout?: BlockLayout; } diff --git a/src/lib/components/blocks/ImageGalleryBlock.svelte b/src/lib/components/blocks/ImageGalleryBlock.svelte index 2ae2e6f..3f0f281 100644 --- a/src/lib/components/blocks/ImageGalleryBlock.svelte +++ b/src/lib/components/blocks/ImageGalleryBlock.svelte @@ -13,6 +13,22 @@ const t = useTranslate(); let { block }: { block: ImageGalleryBlockData } = $props(); + /** Hover-Label aus image-Field — title|alt|caption|description, in dieser Reihenfolge. */ + function imageLabel(img: ImageGalleryImage): string { + return (img.title ?? img.alt ?? img.caption ?? img.description ?? "").trim(); + } + + /** Dateiname für Download aus Asset-URL ableiten. */ + function downloadFilename(url: string): string { + try { + const u = new URL(url); + const last = u.pathname.split("/").filter(Boolean).pop(); + return last ? decodeURIComponent(last) : "image"; + } catch { + return "image"; + } + } + marked.setOptions({ gfm: true }); const layoutClasses = $derived(getBlockLayoutClasses(block.layout)); @@ -45,6 +61,36 @@ let currentIndex = $state(0); + // Grid-Modal-State: -1 = closed, 0..n-1 = open at index. + let modalIndex = $state(-1); + const modalOpen = $derived(modalIndex >= 0); + + function openModal(i: number) { + modalIndex = i; + } + function closeModal() { + modalIndex = -1; + } + function modalPrev() { + if (modalIndex < 0) return; + modalIndex = (modalIndex - 1 + withUrl.length) % withUrl.length; + } + function modalNext() { + if (modalIndex < 0) return; + modalIndex = (modalIndex + 1) % withUrl.length; + } + function onModalKey(e: KeyboardEvent) { + if (!modalOpen) return; + if (e.key === "Escape") { + e.preventDefault(); + closeModal(); + } else if (e.key === "ArrowLeft") { + modalPrev(); + } else if (e.key === "ArrowRight") { + modalNext(); + } + } + function prev() { currentIndex = (currentIndex - 1 + withUrl.length) % withUrl.length; } @@ -109,6 +155,8 @@ } + +
{#if descriptionHtml}
@@ -118,6 +166,110 @@ {#if withUrl.length === 0}

{t(T.image_gallery_empty)}

+ {:else if block.variant === "grid"} +
    + {#each withUrl as item, i} + {@const label = imageLabel(item.img)} +
  • + +
  • + {/each} +
+ + {#if modalOpen} + {@const cur = withUrl[modalIndex]} + {@const curLabel = imageLabel(cur.img)} + + {/if} {:else if withUrl.length === 1}