Viele neue Features

This commit is contained in:
2026-03-01 13:01:46 +00:00
parent 04a0d2b54d
commit 832199a44d
13 changed files with 903 additions and 210 deletions

View File

@@ -5,14 +5,191 @@
{% if images %}
<div class="gallery-grid">
{% for image in images %}
<figure class="gallery-item">
<a href="{{ url_for('serve_upload', filename=image['filename']) }}" target="_blank" rel="noopener">
<img src="{{ url_for('serve_upload', filename=image['filename']) }}" alt="{{ t('gallery_image_alt').format(name=image['uploaded_by']) }}" loading="lazy" />
</a>
<figcaption>{{ t('gallery_uploaded_by').format(name=image['uploaded_by']) }}</figcaption>
<figure class="gallery-item gallery-card">
<div class="gallery-media">
<a
href="{{ url_for('serve_upload', filename=image['filename']) }}"
class="gallery-open"
data-image-index="{{ loop.index0 }}"
>
<img src="{{ url_for('serve_upload', filename=image['filename']) }}" alt="{{ t('gallery_image_alt').format(name=image['uploaded_by_name']) }}" loading="lazy" />
</a>
{% if is_host or guest_id == image['uploaded_by'] %}
<form class="gallery-delete-form" method="post" action="{{ url_for('delete_image', image_id=image['id']) }}">
<button class="gallery-delete-btn" type="submit" aria-label="{{ t('delete') }}" title="{{ t('delete') }}">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M9 3h6l1 2h4v2H4V5h4l1-2zm1 6h2v8h-2V9zm4 0h2v8h-2V9zM7 9h2v8H7V9z" />
</svg>
</button>
</form>
{% endif %}
</div>
<figcaption>{{ t('gallery_uploaded_by').format(name=image['uploaded_by_name']) }}</figcaption>
</figure>
{% endfor %}
</div>
<div class="lightbox" id="gallery-lightbox" aria-hidden="true">
<button class="lightbox-close" type="button" aria-label="Close">×</button>
<div class="lightbox-counter" id="lightbox-counter">1 / 1</div>
<a class="lightbox-download" id="lightbox-download" href="#" aria-label="{{ t('download') }}" title="{{ t('download') }}">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M12 3a1 1 0 0 1 1 1v8.59l2.3-2.29a1 1 0 1 1 1.4 1.41l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.41L11 12.59V4a1 1 0 0 1 1-1zm-7 14a1 1 0 0 1 1 1v1h12v-1a1 1 0 1 1 2 0v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
</svg>
</a>
<button class="lightbox-nav lightbox-prev" type="button" aria-label="Previous image"></button>
<img class="lightbox-image" id="lightbox-image" alt="" />
<button class="lightbox-nav lightbox-next" type="button" aria-label="Next image"></button>
</div>
<script>
(() => {
const lightbox = document.getElementById("gallery-lightbox");
const lightboxImage = document.getElementById("lightbox-image");
const lightboxDownload = document.getElementById("lightbox-download");
const lightboxCounter = document.getElementById("lightbox-counter");
const closeBtn = lightbox.querySelector(".lightbox-close");
const prevBtn = lightbox.querySelector(".lightbox-prev");
const nextBtn = lightbox.querySelector(".lightbox-next");
const openButtons = Array.from(document.querySelectorAll(".gallery-open"));
const images = [
{% for image in images %}
{
src: {{ url_for('serve_upload', filename=image['filename'])|tojson }},
download: {{ url_for('serve_upload', filename=image['filename'], download=1)|tojson }},
alt: {{ t('gallery_image_alt').format(name=image['uploaded_by_name'])|tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
];
let currentIndex = 0;
let touchStartX = 0;
let touchStartY = 0;
let didSwipe = false;
let controlsTimer = null;
const scheduleHideControls = () => {
clearTimeout(controlsTimer);
controlsTimer = window.setTimeout(() => {
lightbox.classList.add("lightbox-controls-hidden");
}, 3000);
};
const showControlsTemporarily = () => {
lightbox.classList.remove("lightbox-controls-hidden");
scheduleHideControls();
};
const setImage = (index, options = {}) => {
const { revealControls = true } = options;
if (!images.length) {
return;
}
currentIndex = (index + images.length) % images.length;
const item = images[currentIndex];
lightboxImage.classList.remove("is-fading");
void lightboxImage.offsetWidth;
lightboxImage.classList.add("is-fading");
lightboxImage.src = item.src;
lightboxImage.alt = item.alt;
lightboxDownload.href = item.download;
lightboxCounter.textContent = `${currentIndex + 1} / ${images.length}`;
if (revealControls) {
showControlsTemporarily();
}
};
const openLightbox = (index) => {
setImage(index);
lightbox.classList.add("is-open");
lightbox.setAttribute("aria-hidden", "false");
document.body.classList.add("no-scroll");
showControlsTemporarily();
};
const closeLightbox = () => {
lightbox.classList.remove("is-open");
lightbox.setAttribute("aria-hidden", "true");
document.body.classList.remove("no-scroll");
lightbox.classList.remove("lightbox-controls-hidden");
clearTimeout(controlsTimer);
};
openButtons.forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
openLightbox(Number(link.dataset.imageIndex || 0));
});
});
prevBtn.addEventListener("click", () => setImage(currentIndex - 1, { revealControls: true }));
nextBtn.addEventListener("click", () => setImage(currentIndex + 1, { revealControls: true }));
closeBtn.addEventListener("click", closeLightbox);
lightbox.addEventListener("mousemove", () => {
if (lightbox.classList.contains("is-open")) {
showControlsTemporarily();
}
});
lightbox.addEventListener("click", (event) => {
if (event.target === lightbox) {
closeLightbox();
} else if (
lightbox.classList.contains("is-open") &&
event.target.closest(".lightbox-close, .lightbox-download, .lightbox-nav")
) {
showControlsTemporarily();
}
});
lightboxImage.addEventListener("touchstart", (event) => {
const point = event.changedTouches[0];
touchStartX = point.clientX;
touchStartY = point.clientY;
didSwipe = false;
}, { passive: true });
lightboxImage.addEventListener("touchend", (event) => {
const point = event.changedTouches[0];
const deltaX = point.clientX - touchStartX;
const deltaY = point.clientY - touchStartY;
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
if (absX < 40 || absX <= absY) {
return;
}
didSwipe = true;
if (deltaX < 0) {
setImage(currentIndex + 1, { revealControls: false });
} else {
setImage(currentIndex - 1, { revealControls: false });
}
}, { passive: true });
lightboxImage.addEventListener("click", (event) => {
if (didSwipe) {
event.preventDefault();
event.stopPropagation();
didSwipe = false;
}
});
document.addEventListener("keydown", (event) => {
if (!lightbox.classList.contains("is-open")) {
return;
}
showControlsTemporarily();
if (event.key === "Escape") {
closeLightbox();
} else if (event.key === "ArrowLeft") {
setImage(currentIndex - 1, { revealControls: true });
} else if (event.key === "ArrowRight") {
setImage(currentIndex + 1, { revealControls: true });
}
});
})();
</script>
{% else %}
<p>{{ t('gallery_empty') }}</p>
{% endif %}