// lazyflat — live time helpers. // [data-rel-utc=""] → "vor 3 min" (relative-past, updated every 5 s) // [data-counter-up-utc=""] → "vor X s" counting up each second (used for // "aktualisiert vor X s"; resets when the // server ships a newer timestamp in the swap) function fmtRelative(iso) { const ts = Date.parse(iso); if (!iso || Number.isNaN(ts)) return "—"; const diff = Math.max(0, Math.floor((Date.now() - ts) / 1000)); if (diff < 5) return "gerade eben"; if (diff < 60) return `vor ${diff} s`; if (diff < 3600) return `vor ${Math.floor(diff / 60)} min`; if (diff < 86400) return `vor ${Math.floor(diff / 3600)} h`; return `vor ${Math.floor(diff / 86400)} Tagen`; } function fmtCountUp(iso) { const ts = Date.parse(iso); if (!iso || Number.isNaN(ts)) return "—"; const diff = Math.max(0, Math.floor((Date.now() - ts) / 1000)); return `vor ${diff} s`; } function updateRelativeTimes() { document.querySelectorAll("[data-rel-utc]").forEach((el) => { el.textContent = fmtRelative(el.dataset.relUtc); }); } function updateCounterUps() { document.querySelectorAll("[data-counter-up-utc]").forEach((el) => { el.textContent = fmtCountUp(el.dataset.counterUpUtc); }); } // After the HTMX swap rebuilds the list, the open-chevron class is gone even // though the corresponding .flat-detail pane is preserved. Re-sync by looking // at pane visibility. function syncFlatExpandState() { document.querySelectorAll(".flat-detail").forEach((pane) => { const row = pane.closest(".flat-row"); if (!row) return; const btn = row.querySelector(".flat-expand-btn"); if (!btn) return; const open = pane.dataset.loaded === "1" && pane.style.display !== "none"; btn.classList.toggle("open", open); }); } function tick() { updateRelativeTimes(); updateCounterUps(); } document.addEventListener("DOMContentLoaded", tick); document.addEventListener("DOMContentLoaded", syncFlatExpandState); if (document.body) { document.body.addEventListener("htmx:afterSwap", tick); document.body.addEventListener("htmx:afterSwap", syncFlatExpandState); } setInterval(updateCounterUps, 1000); setInterval(updateRelativeTimes, 5000); // Flat detail expand — lazily fetches /partials/wohnung/ into the sibling // .flat-detail container on first open, toggles visibility on subsequent clicks. // Event delegation survives HTMX swaps without re-binding on each poll. document.addEventListener("click", (ev) => { const btn = ev.target.closest(".flat-expand-btn"); if (!btn) return; const row = btn.closest(".flat-row"); if (!row) return; const pane = row.querySelector(".flat-detail"); if (!pane) return; if (btn.classList.contains("open")) { pane.style.display = "none"; btn.classList.remove("open"); return; } btn.classList.add("open"); pane.style.display = "block"; if (pane.dataset.loaded) return; pane.innerHTML = '
lädt…
'; const flatId = btn.dataset.flatId || ""; fetch("/partials/wohnung/" + encodeURIComponent(flatId), { headers: { "HX-Request": "true" } }) .then((r) => r.text()) .then((html) => { pane.innerHTML = html; pane.dataset.loaded = "1"; // DEBUG(lightbox): confirm fetched partial actually contains