// The navbar when signed in: "admin" and "log out" in place of "log in". Loaded right after the // nav, before the rest of the page, so the signed-out links never show first. Pages are the same // for everyone; the readable kepr_editor cookie, set beside the session, carries the form token // that log out posts back. Only the private notification preview needs a request. { const token = /(?:^|;\s*)kepr_editor=([0-9a-f]{64})(?:;|$)/.exec(document.cookie)?.[1]; const link = document.querySelector('.nav-links a[href^="/sign-in"]'); if (token && link) { const admin = document.createElement("a"); admin.href = "/admin"; admin.textContent = "admin"; const form = document.createElement("form"); form.className = "nav-logout"; form.method = "post"; form.action = "/admin/sign-out"; const field = document.createElement("input"); field.type = "hidden"; field.name = "form"; field.value = token; const button = document.createElement("button"); button.type = "submit"; button.textContent = "log out"; form.append(field, button); const bell = document.createElement("details"); bell.className = "nav-notifications"; const toggle = document.createElement("summary"); toggle.setAttribute("aria-label", "notifications"); toggle.innerHTML = ''; const dot = document.createElement("span"); dot.className = "notification-dot"; dot.hidden = true; toggle.append(dot); const preview = document.createElement("div"); preview.className = "notification-preview"; const heading = document.createElement("strong"); heading.textContent = "notifications"; const list = document.createElement("div"); list.className = "notification-list"; list.textContent = "loading…"; const all = document.createElement("a"); all.href = "/admin/notifications"; all.className = "notification-all"; all.textContent = "view all notifications ↗"; preview.append(heading, list, all); bell.append(toggle, preview); link.parentElement.after(bell); let loading = false; const refresh = async () => { if (loading) return; loading = true; try { const response = await fetch("/admin/notifications?format=json", { cache: "no-store" }); if (response.status === 401) { bell.remove(); return; } if (!response.ok) throw new Error(`notifications: ${response.status}`); const feed = await response.json(); dot.hidden = feed.unread === 0; toggle.setAttribute("aria-label", `notifications, ${feed.unread} unread`); list.replaceChildren(); for (const item of feed.notifications) { const entry = document.createElement("a"); entry.href = `/admin/notifications/${item.id}`; entry.className = item.read ? "" : "notification-unread"; const text = document.createElement("span"); text.textContent = item.text; const when = document.createElement("small"); when.textContent = `${item.read ? "" : "unread · "}${item.when}`; entry.append(text, when); list.append(entry); } if (!feed.notifications.length) list.textContent = "nothing yet"; } catch (error) { list.textContent = "Could not load notifications. Open the full list or try again."; console.error(error); } finally { loading = false; } }; bell.addEventListener("toggle", () => { if (bell.open) refresh(); }); document.addEventListener("click", (event) => { if (!bell.contains(event.target)) bell.open = false; }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && bell.open) { bell.open = false; toggle.focus(); } }); refresh(); link.replaceWith(admin, form); } }