// 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"; }) .catch(() => { pane.innerHTML = '
Detail konnte nicht geladen werden.
'; }); }); // Deep-link landing: ?flat= on first load expands + highlights that row. // Used from Telegram match notifications so "Zur lazyflat Seite" jumps // straight to the relevant card. function openDeepLinkedFlat() { const params = new URLSearchParams(location.search); const targetId = params.get("flat"); if (!targetId) return; let found = null; for (const btn of document.querySelectorAll(".flat-expand-btn")) { if (btn.dataset.flatId === targetId) { found = btn; break; } } if (!found) { // Flat not in the visible list (filter/age/rejected). Drop the param so // a reload doesn't keep failing silently. params.delete("flat"); const qs = params.toString(); history.replaceState(null, "", location.pathname + (qs ? "?" + qs : "") + location.hash); return; } const row = found.closest(".flat-row"); if (!found.classList.contains("open")) found.click(); if (row) { row.scrollIntoView({ behavior: "smooth", block: "center" }); row.classList.add("flat-highlight"); setTimeout(() => row.classList.remove("flat-highlight"), 3000); } params.delete("flat"); const qs = params.toString(); history.replaceState(null, "", location.pathname + (qs ? "?" + qs : "") + location.hash); } document.addEventListener("DOMContentLoaded", openDeepLinkedFlat);