(function () { const dialog = document.getElementById("completeDialog"); const dialogForm = document.getElementById("completeDialogForm"); const dialogChoice = document.getElementById("completeDialogChoice"); const dialogText = document.getElementById("completeDialogText"); const dialogChoices = document.getElementById("completeDialogChoices"); const closeButton = document.getElementById("completeDialogClose"); const quickTaskDialog = document.getElementById("quickTaskDialog"); const quickTaskOpen = document.getElementById("quickTaskOpen"); const quickTaskClose = document.getElementById("quickTaskClose"); const quickWinsSubmit = document.getElementById("quickWinsSubmit"); const quickWinInputs = document.querySelectorAll("[data-quick-win-input]"); const quickWinCustomToggle = document.querySelector("[data-quick-win-custom-toggle]"); const quickWinCustomFields = document.getElementById("quickWinCustomFields"); const quickWinTitle = document.getElementById("quick-title"); const quickWinEffort = document.getElementById("quick-effort"); const currentUserId = document.body.dataset.currentUserId; const currentUserName = document.body.dataset.currentUserName; const quickWinSortList = document.querySelector("[data-quick-win-sort-list]"); const quickWinSortIds = document.getElementById("quickWinSortIds"); const quickWinSortSave = document.getElementById("quickWinSortSave"); const quickWinToggleButtons = document.querySelectorAll("[data-quick-win-toggle]"); const celebrationLayer = document.getElementById("celebrationLayer"); const celebratePoints = Number.parseInt(document.body.dataset.celebratePoints || "", 10); const scrollRestoreKey = "putzliga:scroll-restore"; let draggedQuickWin = null; let quickWinSortDirty = false; function rememberScrollPosition() { try { window.sessionStorage.setItem( scrollRestoreKey, JSON.stringify({ path: window.location.pathname, y: window.scrollY, }), ); } catch (_) { // Ignore storage errors and continue normally. } } function restoreScrollPosition() { try { const rawValue = window.sessionStorage.getItem(scrollRestoreKey); if (!rawValue) { return; } const saved = JSON.parse(rawValue); window.sessionStorage.removeItem(scrollRestoreKey); if (!saved || saved.path !== window.location.pathname || typeof saved.y !== "number") { return; } window.requestAnimationFrame(() => { window.requestAnimationFrame(() => { window.scrollTo({ top: saved.y, behavior: "auto" }); }); }); } catch (_) { // Ignore malformed storage data. } } function buildCompletionOptions(button) { const options = []; const assignedPairs = [ [button.dataset.assignedPrimaryId, button.dataset.assignedPrimaryName], [button.dataset.assignedSecondaryId, button.dataset.assignedSecondaryName], ]; assignedPairs.forEach(([id, label]) => { if (id && label && !options.some((option) => option.value === id)) { options.push({ value: id, label }); } }); if (currentUserId && currentUserName && !options.some((option) => option.value === currentUserId)) { options.push({ value: currentUserId, label: "Ich" }); } return options; } document.querySelectorAll("[data-complete-action]").forEach((button) => { button.addEventListener("click", () => { if (!dialog || !dialogForm || !dialogChoice || !dialogText || !dialogChoices) { return; } dialogForm.action = button.dataset.completeAction; dialogText.textContent = `Die Aufgabe "${button.dataset.completeTitle}" war ${button.dataset.completeAssigned} zugewiesen. Wer hat sie erledigt?`; dialogChoices.innerHTML = ""; buildCompletionOptions(button).forEach((option, index) => { const choiceButton = document.createElement("button"); choiceButton.type = "button"; choiceButton.className = index === 0 ? "button button--secondary" : "button"; choiceButton.dataset.completeChoice = option.value; choiceButton.textContent = option.label; choiceButton.addEventListener("click", () => { dialogChoice.value = option.value; rememberScrollPosition(); dialog.close(); dialogForm.submit(); }); dialogChoices.appendChild(choiceButton); }); dialog.showModal(); }); }); if (closeButton && dialog) { closeButton.addEventListener("click", () => dialog.close()); } if (quickTaskOpen && quickTaskDialog) { quickTaskOpen.addEventListener("click", () => { if (!quickTaskDialog.open) { quickTaskDialog.showModal(); } }); } if (quickTaskClose && quickTaskDialog) { quickTaskClose.addEventListener("click", () => { if (quickTaskDialog.open) { quickTaskDialog.close(); } }); } [dialog, quickTaskDialog].forEach((activeDialog) => { if (!activeDialog) { return; } activeDialog.addEventListener("click", (event) => { if (event.target === activeDialog && activeDialog.open) { activeDialog.close(); } }); }); function updateQuickWinsState() { const selectedPresetCount = [...quickWinInputs].filter((input) => input.checked).length; const customSelected = quickWinCustomToggle?.checked === true; const totalCount = selectedPresetCount + (customSelected ? 1 : 0); if (quickWinCustomFields) { quickWinCustomFields.hidden = !customSelected; } if (quickWinTitle) { quickWinTitle.disabled = !customSelected; quickWinTitle.required = customSelected; } if (quickWinEffort) { quickWinEffort.disabled = !customSelected; quickWinEffort.required = customSelected; } if (quickWinsSubmit) { quickWinsSubmit.disabled = totalCount === 0; quickWinsSubmit.textContent = totalCount <= 1 ? "Quick-Win sichern" : "Quick Wins sichern"; } } quickWinInputs.forEach((input) => input.addEventListener("change", updateQuickWinsState)); if (quickWinCustomToggle) { quickWinCustomToggle.addEventListener("change", updateQuickWinsState); } updateQuickWinsState(); if (quickTaskDialog) { quickTaskDialog.addEventListener("close", () => { const quickWinsForm = document.getElementById("quickWinsForm"); if (!quickWinsForm) { return; } quickWinsForm.reset(); updateQuickWinsState(); }); } if (dialogForm) { dialogForm.addEventListener("submit", () => { rememberScrollPosition(); }); } document.querySelectorAll('form[action*="/complete"]').forEach((form) => { form.addEventListener("submit", () => { rememberScrollPosition(); }); }); const quickWinsForm = document.getElementById("quickWinsForm"); if (quickWinsForm) { quickWinsForm.addEventListener("submit", () => { rememberScrollPosition(); }); } function syncQuickWinSortIds() { if (!quickWinSortList || !quickWinSortIds) { return; } const ids = [...quickWinSortList.querySelectorAll("[data-quick-win-sort-item]")] .map((item) => item.dataset.quickWinSortItem) .filter(Boolean); quickWinSortIds.value = ids.join(","); } function setQuickWinSortDirty(isDirty) { quickWinSortDirty = isDirty; if (quickWinSortSave) { quickWinSortSave.disabled = !isDirty; } } function clearCelebrationQuery() { if (!window.history.replaceState) { return; } const url = new URL(window.location.href); if (!url.searchParams.has("celebrate_points")) { return; } url.searchParams.delete("celebrate_points"); const nextUrl = `${url.pathname}${url.search}${url.hash}`; window.history.replaceState({}, document.title, nextUrl); } function celebrateCompletion(points) { if (!celebrationLayer || !Number.isFinite(points) || points <= 0) { return; } const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; celebrationLayer.hidden = false; celebrationLayer.setAttribute("aria-hidden", "false"); celebrationLayer.innerHTML = ""; const score = document.createElement("div"); score.className = "celebration-score"; score.textContent = points; celebrationLayer.appendChild(score); const glow = document.createElement("div"); glow.className = "celebration-glow"; celebrationLayer.appendChild(glow); if (!prefersReducedMotion) { const colors = [ "rgba(94, 168, 255, 0.96)", "rgba(52, 211, 153, 0.95)", "rgba(250, 204, 21, 0.92)", "rgba(191, 219, 254, 0.96)", ]; Array.from({ length: 14 }).forEach((_, index) => { const particle = document.createElement("span"); particle.className = "celebration-particle"; particle.style.setProperty("--angle", `${Math.round((360 / 14) * index + Math.random() * 18)}deg`); particle.style.setProperty("--distance", `${72 + Math.round(Math.random() * 44)}px`); particle.style.setProperty("--delay", `${(Math.random() * 0.08).toFixed(2)}s`); particle.style.setProperty("--size", `${7 + Math.round(Math.random() * 7)}px`); particle.style.background = colors[index % colors.length]; celebrationLayer.appendChild(particle); }); } window.setTimeout(() => { celebrationLayer.hidden = true; celebrationLayer.setAttribute("aria-hidden", "true"); celebrationLayer.innerHTML = ""; }, prefersReducedMotion ? 520 : 1500); } if (quickWinSortList) { syncQuickWinSortIds(); setQuickWinSortDirty(false); quickWinSortList.querySelectorAll("[data-quick-win-sort-item]").forEach((item) => { item.addEventListener("dragstart", () => { draggedQuickWin = item; item.classList.add("is-dragging"); }); item.addEventListener("dragend", () => { item.classList.remove("is-dragging"); draggedQuickWin = null; }); item.addEventListener("dragover", (event) => { event.preventDefault(); }); item.addEventListener("drop", async (event) => { event.preventDefault(); if (!draggedQuickWin || draggedQuickWin === item) { return; } const items = [...quickWinSortList.querySelectorAll("[data-quick-win-sort-item]")]; const draggedIndex = items.indexOf(draggedQuickWin); const targetIndex = items.indexOf(item); if (draggedIndex < targetIndex) { item.after(draggedQuickWin); } else { item.before(draggedQuickWin); } syncQuickWinSortIds(); setQuickWinSortDirty(true); }); }); } quickWinToggleButtons.forEach((button) => { button.addEventListener("click", () => { const targetId = button.dataset.target; if (!targetId) { return; } const target = document.getElementById(targetId); if (!target) { return; } const willOpen = target.hidden; document.querySelectorAll("[data-quick-win-edit]").forEach((editForm) => { editForm.hidden = true; }); quickWinToggleButtons.forEach((toggle) => { toggle.setAttribute("aria-expanded", "false"); toggle.textContent = "Bearbeiten"; }); if (willOpen) { target.hidden = false; button.setAttribute("aria-expanded", "true"); button.textContent = "Schließen"; } }); }); const pushButton = document.getElementById("pushToggle"); const pushHint = document.getElementById("pushHint"); const vapidKey = document.body.dataset.pushKey; const isIos = /iphone|ipad|ipod/i.test(window.navigator.userAgent); const isStandalone = window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true; function urlBase64ToUint8Array(base64String) { const padding = "=".repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); const raw = atob(base64); return Uint8Array.from([...raw].map((char) => char.charCodeAt(0))); } async function postJSON(url, payload) { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); return response.json(); } async function togglePush() { if (!("serviceWorker" in navigator) || !("PushManager" in window) || !pushButton) { return; } const registration = await navigator.serviceWorker.register("/service-worker.js"); const existing = await registration.pushManager.getSubscription(); if (existing) { await postJSON("/settings/push/unsubscribe", { endpoint: existing.endpoint }); await existing.unsubscribe(); pushButton.dataset.subscribed = "0"; pushButton.textContent = "Push aktivieren"; return; } const permission = await Notification.requestPermission(); if (permission !== "granted") { return; } const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidKey), }); await postJSON("/settings/push/subscribe", subscription.toJSON()); pushButton.dataset.subscribed = "1"; pushButton.textContent = "Push deaktivieren"; } if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/service-worker.js").catch(() => {}); } if (pushButton && (!("serviceWorker" in navigator) || !("PushManager" in window))) { pushButton.disabled = true; if (pushHint) { pushHint.textContent = "Dieser Browser unterstützt Web-Push hier aktuell nicht."; } } else if (pushButton && isIos && !isStandalone) { pushButton.disabled = true; if (pushHint) { pushHint.textContent = "Auf iPhone/iPad funktioniert Web-Push erst nach „Zum Home-Bildschirm“ und Öffnen als Web-App."; } } else if (pushButton && vapidKey) { pushButton.addEventListener("click", () => { togglePush().catch((error) => console.error("Push toggle failed", error)); }); } if (Number.isFinite(celebratePoints) && celebratePoints > 0) { celebrateCompletion(celebratePoints); clearCelebrationQuery(); } restoreScrollPosition(); })();