Files
havelseiten/generator/static/external-content.js
2026-05-16 14:16:54 +02:00

326 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const CONSENT_PREFIX = "havelseiten:external:";
function storageGet(key) {
try {
return localStorage.getItem(key);
} catch (error) {
return null;
}
}
function storageSet(key, value) {
try {
localStorage.setItem(key, value);
} catch (error) {
// If storage is unavailable, the current in-page load still works.
}
}
function consentKey(button) {
if (button.dataset.consentKey) {
return `${CONSENT_PREFIX}${button.dataset.consentKey}`;
}
if (button.dataset.mapAddress) {
return `${CONSENT_PREFIX}map:${button.dataset.mapAddress}`;
}
if (button.dataset.embedSrc) {
return `${CONSENT_PREFIX}embed:${button.dataset.embedSrc}`;
}
return "";
}
function mapEmbedUrl(lat, lon) {
const latitude = Number(lat);
const longitude = Number(lon);
const bbox = [
longitude - 0.01,
latitude - 0.006,
longitude + 0.01,
latitude + 0.006
].join(",");
return (
"https://www.openstreetmap.org/export/embed.html"
+ `?bbox=${encodeURIComponent(bbox)}`
+ "&layer=mapnik"
+ `&marker=${encodeURIComponent(`${latitude},${longitude}`)}`
);
}
async function geocodeAddress(address) {
const url = (
"https://nominatim.openstreetmap.org/search"
+ `?format=json&limit=1&q=${encodeURIComponent(address)}`
);
const response = await fetch(url, {
headers: {
Accept: "application/json"
}
});
const results = await response.json();
if (!results.length) {
throw new Error("Adresse nicht gefunden");
}
return results[0];
}
async function loadEmbed(button, remember = true) {
const iframe = document.createElement("iframe");
const key = consentKey(button);
button.disabled = true;
button.textContent = "Wird geladen...";
if (button.dataset.mapAddress) {
const cachedMapUrl = storageGet(`${key}:src`);
if (cachedMapUrl) {
iframe.src = cachedMapUrl;
} else {
try {
const place = await geocodeAddress(button.dataset.mapAddress);
iframe.src = mapEmbedUrl(place.lat, place.lon);
storageSet(`${key}:src`, iframe.src);
} catch (error) {
button.disabled = false;
button.textContent = "Karte konnte nicht geladen werden";
return;
}
}
} else {
iframe.src = button.dataset.embedSrc;
}
iframe.className = button.dataset.embedClass || "";
iframe.title = button.dataset.embedTitle || "Externer Inhalt";
iframe.loading = "lazy";
iframe.allow = button.dataset.embedAllow || "";
iframe.allowFullscreen = true;
if (remember && key) {
storageSet(key, "1");
}
button.closest(".external-placeholder, .location-placeholder").replaceWith(iframe);
}
function galleryItems() {
return [...document.querySelectorAll(".gallery-grid a")].map((link) => ({
alt: link.querySelector("img")?.alt || "",
height: link.dataset.pswpHeight,
src: link.href,
width: link.dataset.pswpWidth
}));
}
function createLightbox() {
const lightbox = document.createElement("div");
lightbox.className = "site-lightbox";
lightbox.hidden = true;
lightbox.innerHTML = `
<button class="site-lightbox-close" type="button" aria-label="Schliessen">×</button>
<button class="site-lightbox-prev" type="button" aria-label="Vorheriges Bild"></button>
<figure>
<img alt="">
<figcaption></figcaption>
</figure>
<button class="site-lightbox-next" type="button" aria-label="Naechstes Bild"></button>
`;
document.body.append(lightbox);
return lightbox;
}
function initGallery() {
const items = galleryItems();
if (!items.length) {
return;
}
const lightbox = createLightbox();
const image = lightbox.querySelector("img");
const caption = lightbox.querySelector("figcaption");
let currentIndex = 0;
let touchStartX = 0;
function show(index) {
currentIndex = (index + items.length) % items.length;
const item = items[currentIndex];
image.src = item.src;
image.alt = item.alt;
caption.textContent = item.alt;
caption.hidden = !item.alt;
lightbox.hidden = false;
document.body.classList.add("lightbox-open");
}
function close() {
lightbox.hidden = true;
image.removeAttribute("src");
document.body.classList.remove("lightbox-open");
}
function next() {
show(currentIndex + 1);
}
function prev() {
show(currentIndex - 1);
}
document.querySelectorAll(".gallery-grid a").forEach((link, index) => {
link.addEventListener("click", (event) => {
event.preventDefault();
show(index);
});
});
lightbox.querySelector(".site-lightbox-close").addEventListener("click", close);
lightbox.querySelector(".site-lightbox-next").addEventListener("click", next);
lightbox.querySelector(".site-lightbox-prev").addEventListener("click", prev);
lightbox.addEventListener("click", (event) => {
if (event.target === lightbox) {
close();
}
});
lightbox.addEventListener("touchstart", (event) => {
touchStartX = event.changedTouches[0].clientX;
}, { passive: true });
lightbox.addEventListener("touchend", (event) => {
const delta = event.changedTouches[0].clientX - touchStartX;
if (Math.abs(delta) < 40) {
return;
}
if (delta < 0) {
next();
} else {
prev();
}
}, { passive: true });
document.addEventListener("keydown", (event) => {
if (lightbox.hidden) {
return;
}
if (event.key === "Escape") {
close();
} else if (event.key === "ArrowRight") {
next();
} else if (event.key === "ArrowLeft") {
prev();
}
});
}
function initSubmenus() {
const mobileQuery = window.matchMedia("(max-width: 800px)");
document.querySelectorAll(".dropdown").forEach((dropdown) => {
const button = dropdown.querySelector(".dropdown-label");
if (!button) {
return;
}
const startsOpen = (
document.body.classList.contains("mobile-submenus-open")
&& mobileQuery.matches
);
dropdown.classList.toggle("is-open", startsOpen);
button.setAttribute("aria-expanded", startsOpen ? "true" : "false");
button.addEventListener("click", (event) => {
if (!mobileQuery.matches) {
return;
}
event.preventDefault();
const isOpen = dropdown.classList.toggle("is-open");
button.setAttribute("aria-expanded", isOpen ? "true" : "false");
});
dropdown.addEventListener("mouseenter", () => {
if (mobileQuery.matches) {
return;
}
dropdown.classList.add("is-open");
button.setAttribute("aria-expanded", "true");
});
dropdown.addEventListener("mouseleave", () => {
if (mobileQuery.matches) {
return;
}
dropdown.classList.remove("is-open");
button.setAttribute("aria-expanded", "false");
});
dropdown.addEventListener("focusin", () => {
if (mobileQuery.matches) {
return;
}
dropdown.classList.add("is-open");
button.setAttribute("aria-expanded", "true");
});
dropdown.addEventListener("focusout", (event) => {
if (
mobileQuery.matches
|| dropdown.contains(event.relatedTarget)
) {
return;
}
dropdown.classList.remove("is-open");
button.setAttribute("aria-expanded", "false");
});
});
}
document.querySelectorAll(".external-load-button").forEach((button) => {
const key = consentKey(button);
if (button.dataset.autoLoad === "true" || (key && storageGet(key) === "1")) {
loadEmbed(button, false);
}
});
document.addEventListener("click", (event) => {
const externalButton = event.target.closest(".external-load-button");
if (externalButton) {
loadEmbed(externalButton);
}
const galleryButton = event.target.closest(".gallery-load-button");
if (galleryButton) {
const section = galleryButton.closest(".gallery-section");
const grid = section?.querySelector(".gallery-grid");
if (grid) {
grid.hidden = false;
}
galleryButton.closest(".gallery-placeholder")?.remove();
}
});
initSubmenus();
initGallery();