From 8a3358aec75d5b287a841a54203f003d6597fc29 Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 30 Oct 2026 00:59:10 +0000 Subject: [PATCH] Update static/js/04-main.js --- static/js/04-main.js | 328 +++++++++++++++++++++++++++---------------- 1 file changed, 209 insertions(+), 119 deletions(-) diff --git a/static/js/04-main.js b/static/js/04-main.js index 4ec49f2..0bb2dba 100644 --- a/static/js/04-main.js +++ b/static/js/04-main.js @@ -2,36 +2,13 @@ "use strict"; window.Glow = window.Glow || {}; -Glow.presenceModeLabel = mode => { - switch (String(mode || "").toLowerCase()) { - case "silent": - return "silent"; - case "focus": - return "focus"; - case "dnd": - return "do not disturb"; - default: - return "online"; - } -}; - -const originalUpdateAuthUI = Glow.updateAuthUI; -Glow.updateAuthUI = () => { - if (typeof originalUpdateAuthUI === "function") { - originalUpdateAuthUI(); - } - const user = Glow.state.currentUser; - if (user && Glow.els.headerAccount) { - const mode = Glow.presenceModeLabel(user.presence_mode); - Glow.els.headerAccount.textContent = `${user.username} · ${user.role} · ${mode}`; - } -}; - Glow.loadRooms = async () => { const previousSlug = Glow.state.activeRoom?.slug; const data = await Glow.apiRequest("/api/rooms"); + Glow.state.rooms = data.rooms || []; Glow.els.roomSelect.replaceChildren(); + for (const room of Glow.state.rooms) { const option = document.createElement("option"); option.value = room.slug; @@ -39,61 +16,20 @@ Glow.loadRooms = async () => { `${room.name}${room.visibility === "private" ? " (private)" : ""}`; Glow.els.roomSelect.append(option); } + Glow.els.roomSelect.disabled = !Glow.state.rooms.length; + const selected = Glow.state.rooms.find(room => room.slug === previousSlug) || Glow.state.rooms.find(room => room.slug === "general") || Glow.state.rooms[0] || null; + Glow.state.activeRoom = selected; Glow.updateViewUI(); Glow.updateChatControls(); }; -Glow.renderEmojiAutocomplete = (input, query) => { - const box = Glow.els.emojiAutocomplete; - if (!box || !input) return; - const value = String(query || "").toLowerCase(); - const entries = Object.entries(Glow.EMOJI_ALIASES) - .filter(([alias]) => alias.includes(value)) - .slice(0, 8); - Glow.state.emojiSuggestions = entries; - if (!entries.length) { - box.classList.add("hidden"); - box.replaceChildren(); - return; - } - box.replaceChildren(); - for (const [alias, emoji] of entries) { - const button = document.createElement("button"); - button.className = "emoji-suggestion"; - button.type = "button"; - button.textContent = `${emoji} ::${alias}`; - button.addEventListener("click", () => { - const target = Glow.state.emojiActiveInput || input; - const current = target.value; - const replaced = current.replace(/::([a-z0-9_+-]*)$/i, `${emoji} `); - target.value = replaced; - target.dispatchEvent(new Event("input", { bubbles: true })); - box.classList.add("hidden"); - target.focus(); - }); - box.append(button); - } - Glow.state.emojiActiveInput = input; - box.classList.remove("hidden"); -}; - -Glow.maybeShowEmojiAutocomplete = input => { - const text = String(input?.value || ""); - const match = text.match(/::([a-z0-9_+-]*)$/i); - if (!match) { - Glow.els.emojiAutocomplete?.classList.add("hidden"); - return; - } - Glow.renderEmojiAutocomplete(input, match[1]); -}; - Glow.updateViewUI = () => { const view = Glow.state.activeView || "feed"; const room = Glow.state.activeRoom; @@ -106,6 +42,7 @@ Glow.updateViewUI = () => { const pageActive = view === "page"; const profileActive = view === "profile"; const settingsActive = view === "settings"; + Glow.els.feedBox.classList.toggle("hidden", !feedActive); Glow.els.dmBox?.classList.toggle("hidden", !dmListActive); Glow.els.chatBox.classList.toggle("hidden", !chatActive); @@ -115,6 +52,7 @@ Glow.updateViewUI = () => { Glow.els.profileBox?.classList.toggle("hidden", !(profileActive || settingsActive)); Glow.els.composerWrap.classList.toggle("hidden", !chatActive); Glow.els.threadComposerWrap?.classList.toggle("hidden", !threadActive); + if (!chatActive) { Glow.state.typingUsers = []; Glow.state.dmTypingUsers = []; @@ -123,19 +61,25 @@ Glow.updateViewUI = () => { Glow.els.typingBar.classList.add("hidden"); } } + Glow.els.feedButton.classList.toggle("active", feedActive); Glow.els.feedButton.setAttribute("aria-pressed", String(feedActive)); + Glow.els.pagesButton?.classList.toggle("active", pagesActive || pageActive); Glow.els.pagesButton?.setAttribute("aria-pressed", String(pagesActive || pageActive)); + Glow.els.dmsButton?.classList.toggle("active", dmListActive || view === "dm"); Glow.els.dmsButton?.setAttribute("aria-pressed", String(dmListActive || view === "dm")); + Glow.els.backButton?.classList.toggle( "hidden", !(threadActive || pageActive || profileActive || settingsActive || view === "dm") ); + Glow.els.roomSelect.classList.toggle("active", chatActive && !dm); Glow.els.teamSelect?.classList.toggle("hidden", dmListActive || view === "dm"); Glow.els.roomSelect?.classList.toggle("hidden", dmListActive || view === "dm"); + if (feedActive) { Glow.els.headerTitle.textContent = "Feed"; Glow.els.headerSubtitle.textContent = Glow.state.currentUser @@ -143,35 +87,45 @@ Glow.updateViewUI = () => { : "Sign in or create an account to personalize your feed"; Glow.els.roomSelect.disabled = !Glow.state.rooms.length; Glow.els.presenceLabel?.classList.add("hidden"); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (dmListActive) { Glow.els.headerTitle.textContent = "Direct messages"; Glow.els.headerSubtitle.textContent = Glow.state.currentUser ? "Private conversations" : "Sign in to open private conversations"; Glow.els.presenceLabel?.classList.add("hidden"); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (threadActive) { Glow.els.headerTitle.textContent = "Thread"; Glow.els.headerSubtitle.textContent = dm ? "Direct message replies" : room?.name || "Conversation replies"; Glow.updateThreadControls?.(); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (pagesActive) { Glow.els.headerTitle.textContent = "Pages"; Glow.els.headerSubtitle.textContent = "Shared knowledge and resources"; Glow.els.presenceLabel?.classList.add("hidden"); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (pageActive) { Glow.els.headerTitle.textContent = Glow.state.activePage?.title || "Page"; Glow.els.headerSubtitle.textContent = "Shared knowledge and resources"; Glow.els.presenceLabel?.classList.add("hidden"); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (profileActive) { Glow.els.headerTitle.textContent = Glow.state.activeProfile?.display_name || @@ -179,29 +133,33 @@ Glow.updateViewUI = () => { "Profile"; Glow.els.headerSubtitle.textContent = "User profile"; Glow.els.presenceLabel?.classList.add("hidden"); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (settingsActive) { Glow.els.headerTitle.textContent = "Settings"; Glow.els.headerSubtitle.textContent = "Appearance, profile, and preferences"; Glow.els.presenceLabel?.classList.add("hidden"); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (view === "dm" && dm) { const peer = (dm.members || []).find( member => Number(member.id) !== Number(Glow.state.currentUser?.id) - ) || - dm.other_user || - dm.members?.[0]; + ) || dm.members?.[0]; Glow.els.headerTitle.textContent = peer?.display_name || peer?.username || "Direct message"; Glow.els.headerSubtitle.textContent = peer?.username ? `@${peer.username}` : "Private conversation"; Glow.refreshPresence?.(); Glow.updateChatControls(); + Glow.calls?.renderHeaderCallChip?.(); return; } + if (room) { Glow.els.headerTitle.textContent = room.name; Glow.els.headerSubtitle.textContent = @@ -212,24 +170,33 @@ Glow.updateViewUI = () => { Glow.els.headerTitle.textContent = "No rooms"; Glow.els.headerSubtitle.textContent = "No accessible chat rooms"; } + Glow.updateChatControls(); + Glow.calls?.renderHeaderCallChip?.(); }; Glow.updateAuthUI = () => { const user = Glow.state.currentUser; const authenticated = Boolean(user); + Glow.els.authOverlay.classList.toggle("hidden", authenticated); Glow.els.logoutButton.classList.toggle("hidden", !authenticated); + Glow.els.headerAccount.textContent = user ? `${user.username} · ${user.role}` : "not signed in"; + document.body.classList.toggle("is-moderator", Glow.canModerate()); document.body.classList.toggle("is-admin", Glow.isAdmin()); + Glow.els.adminFab.classList.toggle("visible", Glow.canModerate()); + Glow.els.presenceDot.classList.toggle("online", authenticated); Glow.els.presenceDot.classList.toggle("new", Boolean(user?.status === "restricted")); + if (user?.status === "restricted") { Glow.showToast("Your account is temporarily restricted.", "error"); } + Glow.updateViewUI(); Glow.updateChatControls(); Glow.updateThreadControls?.(); @@ -240,6 +207,7 @@ Glow.checkInvite = async () => { const params = new URLSearchParams(window.location.search); const roomToken = params.get("invite"); const teamToken = params.get("team_invite"); + if (roomToken) { try { const data = await Glow.apiRequest( @@ -259,6 +227,7 @@ Glow.checkInvite = async () => { } } } + if (teamToken) { try { const data = await Glow.apiAcceptTeamInvite(teamToken); @@ -278,14 +247,18 @@ Glow.checkAuth = async () => { const data = await Glow.apiRequest("/api/me"); Glow.state.currentUser = data.authenticated ? data.user : null; Glow.state.csrfToken = data.csrf_token || ""; + Glow.updateAuthUI(); + await Glow.checkInvite(); await Glow.loadRooms(); await Glow.loadDms?.().catch(() => {}); + Glow.state.activeView = "feed"; Glow.updateViewUI(); await Glow.loadFeed(); await Glow.loadMySettings?.().catch(() => {}); + Glow.calls?.scheduleInvitePolling?.(); } catch (error) { Glow.showToast(error.message || "Could not connect.", "error"); @@ -299,30 +272,38 @@ Glow.authenticate = async endpoint => { const accessKey = Glow.els.accessKeyInput.value; const username = Glow.els.usernameInput.value.trim().toLowerCase(); const password = Glow.els.passwordInput.value; + if (!accessKey || !username || !password) { Glow.showAuthNotice("Enter the private access key, username, and password."); return; } + Glow.els.loginButton.disabled = true; Glow.els.registerButton.disabled = true; + try { const data = await Glow.apiRequest(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ access_key: accessKey, username, password }) }); + Glow.state.currentUser = data.user; Glow.state.csrfToken = data.csrf_token || ""; Glow.els.passwordInput.value = ""; + Glow.updateAuthUI(); + await Glow.checkInvite(); await Glow.loadRooms(); await Glow.loadDms?.().catch(() => {}); + Glow.state.activeView = "feed"; Glow.updateViewUI(); await Glow.loadFeed(); await Glow.loadMySettings?.().catch(() => {}); Glow.calls?.scheduleInvitePolling?.(); + Glow.showToast( endpoint.endsWith("register") ? "Account created." : "Signed in." ); @@ -340,9 +321,28 @@ Glow.authenticate = async endpoint => { }; Glow.logout = async () => { + if (Glow.state.calls.activeCall?.groupSession?.id) { + const values = await Glow.openDialog({ + title: "Leave active call and sign out?", + body: "You are currently in a voice call. Signing out will disconnect you from the call.", + confirmText: "Leave and sign out", + cancelText: "Stay signed in", + danger: true + }); + + if (!values) return; + } + try { await Glow.apiRequest("/api/logout", { method: "POST" }); } catch (_) {} + + if (Glow.state.calls.activeCall?.groupSession?.id) { + try { + await Glow.apiLeaveGroupCall(Glow.state.calls.activeCall.groupSession.id); + } catch (_) {} + } + Glow.state.currentUser = null; Glow.state.csrfToken = ""; Glow.state.rooms = []; @@ -359,6 +359,7 @@ Glow.logout = async () => { Glow.state.activePage = null; Glow.state.activeProfile = null; Glow.state.settings = null; + Glow.clearMessages(); Glow.clearFeed(); Glow.clearDms(); @@ -366,12 +367,19 @@ Glow.logout = async () => { Glow.closeReactionPopup?.(); Glow.closeMediaModal?.(); Glow.endLocalCallUi?.(); + clearTimeout(Glow.state.calls?.invitePollTimer); + clearTimeout(Glow.state.calls?.participantPollTimer); + clearTimeout(Glow.state.calls?.heartbeatTimer); + Glow.updateAuthUI(); + Glow.els.authOverlay.classList.remove("hidden"); Glow.els.accessKeyInput.focus(); + await Glow.refreshCsrfToken().catch(() => {}); await Glow.loadFeed().catch(() => {}); + Glow.showToast("Signed out."); }; @@ -380,6 +388,7 @@ Glow.toggleNotifications = async () => { Glow.showToast("Browser notifications are not supported.", "error"); return; } + if (!Glow.state.notificationsEnabled) { const permission = await Notification.requestPermission(); if (permission !== "granted") { @@ -387,6 +396,7 @@ Glow.toggleNotifications = async () => { return; } } + Glow.state.notificationsEnabled = !Glow.state.notificationsEnabled; localStorage.setItem( "glow_notifications_enabled", @@ -421,9 +431,11 @@ Glow.toggleTypingIndicators = () => { Glow.pingPresence = async () => { if (!Glow.state.currentUser) return; + const payload = { view: Glow.state.activeView }; + if (Glow.state.activeDm?.id) { payload.dm_id = Glow.state.activeDm.id; } else if (Glow.state.activeChannel?.id) { @@ -431,6 +443,7 @@ Glow.pingPresence = async () => { } else if (Glow.state.activeRoom?.id) { payload.room_id = Glow.state.activeRoom.id; } + try { await Glow.apiPingPresence(payload); } catch (_) {} @@ -445,6 +458,7 @@ Glow.refreshPresence = async () => { Glow.els.presenceLabel.classList.remove("hidden"); return; } + if (Glow.state.activeTeam?.slug && Glow.state.activeChannel?.slug) { const data = await Glow.apiGetChannelPresence( Glow.state.activeTeam.slug, @@ -455,6 +469,7 @@ Glow.refreshPresence = async () => { Glow.els.presenceLabel.classList.remove("hidden"); return; } + if (Glow.state.activeRoom?.slug) { const data = await Glow.apiGetRoomPresence(Glow.state.activeRoom.slug); Glow.state.presence.roomOnlineCount = Number(data.online_count) || 0; @@ -462,6 +477,7 @@ Glow.refreshPresence = async () => { Glow.els.presenceLabel.classList.remove("hidden"); return; } + Glow.els.presenceLabel.classList.add("hidden"); } catch (_) { Glow.els.presenceLabel?.classList.add("hidden"); @@ -471,6 +487,7 @@ Glow.refreshPresence = async () => { Glow.schedulePoll = () => { clearTimeout(Glow.state.pollTimer); if (Glow.els.blockedOverlay.classList.contains("hidden") === false) return; + Glow.state.pollTimer = setTimeout(async () => { if (Glow.state.activeView === "feed") { await Glow.pollFeed(); @@ -480,13 +497,12 @@ Glow.schedulePoll = () => { Glow.state.activeView === "dm" ) { await Glow.pollMessages(); - await Glow.pollCallSignals?.(); } else if (Glow.state.activeView === "thread") { await Glow.pollThread?.(); - await Glow.pollCallSignals?.(); } else if (Glow.state.activeView === "dms") { await Glow.loadDms?.().catch(() => {}); } + await Glow.pingPresence?.(); await Glow.refreshPresence?.(); Glow.schedulePoll(); @@ -495,10 +511,10 @@ Glow.schedulePoll = () => { Glow.els.chatForm.addEventListener("submit", Glow.sendMessage); Glow.els.threadForm?.addEventListener("submit", event => Glow.sendThreadReply?.(event)); + Glow.els.messageInput.addEventListener("input", () => { Glow.autoResizeComposer(); Glow.updateMessageCount(); - Glow.maybeShowEmojiAutocomplete?.(Glow.els.messageInput); clearTimeout(Glow.state.typingTimer); if (Glow.els.messageInput.value.trim()) { Glow.sendTyping(true); @@ -509,23 +525,26 @@ Glow.els.messageInput.addEventListener("input", () => { Glow.sendTyping(false); } }); + Glow.els.threadInput?.addEventListener("input", () => { Glow.autoResizeThreadComposer?.(); Glow.updateThreadMessageCount?.(); - Glow.maybeShowEmojiAutocomplete?.(Glow.els.threadInput); }); + Glow.els.messageInput.addEventListener("keydown", event => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); Glow.sendMessage(event); } }); + Glow.els.threadInput?.addEventListener("keydown", event => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); Glow.sendThreadReply?.(event); } }); + Glow.els.attachmentInput.addEventListener("change", () => { const files = Glow.normalizeSelectedFiles(Glow.els.attachmentInput.files); if (!files.length) { @@ -541,6 +560,7 @@ Glow.els.attachmentInput.addEventListener("change", () => { } Glow.setSelectedAttachment(files); }); + Glow.els.threadAttachmentInput?.addEventListener("change", () => { const files = Glow.normalizeSelectedFiles(Glow.els.threadAttachmentInput.files); if (!files.length) { @@ -556,16 +576,20 @@ Glow.els.threadAttachmentInput?.addEventListener("change", () => { } Glow.setSelectedThreadAttachment?.(files); }); + Glow.els.feedButton.addEventListener("click", Glow.openFeed); + Glow.els.dmsButton?.addEventListener("click", async () => { Glow.state.activeView = "dms"; Glow.updateViewUI(); await Glow.loadDms?.(); Glow.schedulePoll(); }); + Glow.els.pagesButton?.addEventListener("click", () => { Glow.openPages?.(); }); + Glow.els.backButton?.addEventListener("click", async () => { if (Glow.state.activeView === "thread") { await Glow.returnToChannel?.(); @@ -579,24 +603,30 @@ Glow.els.backButton?.addEventListener("click", async () => { await Glow.loadDms?.(); } }); + Glow.els.loginButton.addEventListener("click", () => { Glow.authenticate("/api/login"); }); + Glow.els.registerButton.addEventListener("click", () => { Glow.authenticate("/api/register"); }); + Glow.els.passwordInput.addEventListener("keydown", event => { if (event.key === "Enter") { event.preventDefault(); Glow.authenticate("/api/login"); } }); + Glow.els.logoutButton.addEventListener("click", Glow.logout); + Glow.els.roomSelect.addEventListener("change", async () => { const selected = Glow.state.rooms.find( room => room.slug === Glow.els.roomSelect.value ); if (!selected) return; + Glow.state.activeDm = null; Glow.state.activeRoom = selected; Glow.state.activeView = "room"; @@ -604,17 +634,20 @@ Glow.els.roomSelect.addEventListener("change", async () => { Glow.state.dmTypingUsers = []; Glow.renderTyping(); Glow.updateViewUI(); + await Glow.loadMessages(); Glow.pollTyping(); Glow.pingPresence?.(); Glow.refreshPresence?.(); Glow.schedulePoll(); }); + Glow.els.teamSelect?.addEventListener("change", () => { const teams = Glow.state.teams || []; Glow.state.activeTeam = teams.find(team => team.slug === Glow.els.teamSelect.value) || null; }); + Glow.els.adminFab.addEventListener("click", Glow.openAdminPanel); Glow.els.closeAdminButton.addEventListener("click", Glow.closeAdminPanel); Glow.els.createRoomButton.addEventListener("click", Glow.createRoom); @@ -622,99 +655,125 @@ Glow.els.clearMessagesButton.addEventListener("click", Glow.clearActiveRoom); Glow.els.typingToggleButton.addEventListener("click", Glow.toggleTypingIndicators); Glow.els.notificationsToggleButton.addEventListener("click", Glow.toggleNotifications); Glow.els.themeButton?.addEventListener("click", Glow.openAccentColorPicker); + Glow.els.settingsButton?.addEventListener("click", () => { Glow.toggleHeaderMenu?.(); }); + +Glow.els.callStatusChip?.addEventListener("click", () => { + Glow.openCallPanel?.(); +}); + Glow.els.jumpToLatestButton.addEventListener("click", () => { Glow.els.chatBox.scrollTop = Glow.els.chatBox.scrollHeight; Glow.resetUnreadMessages(); }); + Glow.els.chatBox.addEventListener("scroll", () => { if (Glow.isNearBottom()) { Glow.resetUnreadMessages(); } }); + Glow.els.adminPanel.addEventListener("click", event => { if (event.target === Glow.els.adminPanel) { Glow.closeAdminPanel(); } }); + Glow.els.dialogOverlay.addEventListener("click", event => { if (event.target === Glow.els.dialogOverlay) { Glow.closeDialog(null); } }); + Glow.els.headerMenuOverlay?.addEventListener("click", event => { if (event.target === Glow.els.headerMenuOverlay) { Glow.closeHeaderMenu?.(); } }); + Glow.els.closeReactionPopupButton?.addEventListener("click", () => { Glow.closeReactionPopup?.(); }); + Glow.els.reactionPopupOverlay?.addEventListener("click", event => { if (event.target === Glow.els.reactionPopupOverlay) { Glow.closeReactionPopup?.(); } }); + Glow.els.closeMediaButton?.addEventListener("click", () => { Glow.closeMediaModal?.(); }); + Glow.els.mediaOverlay?.addEventListener("click", event => { if (event.target === Glow.els.mediaOverlay) { Glow.closeMediaModal?.(); } }); + Glow.els.closeCallPanelButton?.addEventListener("click", () => { Glow.els.callOverlay?.classList.add("hidden"); }); + document.addEventListener("keydown", event => { - if (event.key === "Escape") { - if (!Glow.els.dialogOverlay.classList.contains("hidden")) { - Glow.closeDialog(null); - return; - } - if (!Glow.els.mediaOverlay?.classList.contains("hidden")) { - Glow.closeMediaModal?.(); - return; - } - if (!Glow.els.reactionPopupOverlay?.classList.contains("hidden")) { - Glow.closeReactionPopup?.(); - return; - } - if (!Glow.els.headerMenuOverlay?.classList.contains("hidden")) { - Glow.closeHeaderMenu?.(); - return; - } - if (!Glow.els.callOverlay?.classList.contains("hidden")) { - Glow.els.callOverlay.classList.add("hidden"); - return; - } - Glow.els.emojiAutocomplete?.classList.add("hidden"); - if (Glow.state.activeView === "thread") { - Glow.returnToChannel?.(); - return; - } - if (Glow.state.activeView === "page") { - Glow.openPages?.(); - return; - } - if (Glow.state.activeView === "dm") { - Glow.state.activeView = "dms"; - Glow.updateViewUI(); - Glow.loadDms?.(); - return; - } - if (Glow.els.adminPanel.classList.contains("open")) { - Glow.closeAdminPanel(); - } + if (event.key !== "Escape") return; + + if (!Glow.els.dialogOverlay.classList.contains("hidden")) { + Glow.closeDialog(null); + return; + } + + if (!Glow.els.mediaOverlay?.classList.contains("hidden")) { + Glow.closeMediaModal?.(); + return; + } + + if (!Glow.els.reactionPopupOverlay?.classList.contains("hidden")) { + Glow.closeReactionPopup?.(); + return; + } + + if (!Glow.els.headerMenuOverlay?.classList.contains("hidden")) { + Glow.closeHeaderMenu?.(); + return; + } + + if (!Glow.els.callOverlay?.classList.contains("hidden")) { + Glow.els.callOverlay.classList.add("hidden"); + return; + } + + if (Glow.state.activeView === "thread") { + Glow.returnToChannel?.(); + return; + } + + if (Glow.state.activeView === "page") { + Glow.openPages?.(); + return; + } + + if (Glow.state.activeView === "dm") { + Glow.state.activeView = "dms"; + Glow.updateViewUI(); + Glow.loadDms?.(); + return; + } + + if (Glow.els.adminPanel.classList.contains("open")) { + Glow.closeAdminPanel(); } }); + document.addEventListener("visibilitychange", () => { Glow.schedulePoll(); Glow.calls?.scheduleInvitePolling?.(); + if (!document.hidden) { Glow.resetUnreadMessages(); + if (Glow.state.activeView === "feed") { Glow.pollFeed(); } else if ( @@ -724,19 +783,20 @@ document.addEventListener("visibilitychange", () => { ) { Glow.pollMessages(); Glow.pollTyping(); - Glow.pollCallSignals?.(); } else if (Glow.state.activeView === "thread") { Glow.pollThread?.(); - Glow.pollCallSignals?.(); } else if (Glow.state.activeView === "dms") { Glow.loadDms?.(); } + Glow.pingPresence?.(); Glow.refreshPresence?.(); } }); + window.addEventListener("online", () => { Glow.showToast("Back online."); + if (Glow.state.activeView === "feed") { Glow.pollFeed(); } else if ( @@ -746,20 +806,21 @@ window.addEventListener("online", () => { ) { Glow.pollMessages(); Glow.pollTyping(); - Glow.pollCallSignals?.(); } else if (Glow.state.activeView === "thread") { Glow.pollThread?.(); - Glow.pollCallSignals?.(); } else if (Glow.state.activeView === "dms") { Glow.loadDms?.(); } + Glow.pingPresence?.(); Glow.refreshPresence?.(); Glow.calls?.scheduleInvitePolling?.(); }); + window.addEventListener("offline", () => { Glow.showToast("You are offline.", "error"); }); + setInterval(() => { if ( Glow.state.activeView === "room" || @@ -769,6 +830,7 @@ setInterval(() => { Glow.pollTyping(); } }, 2500); + setInterval(() => { if ( Glow.state.activeView === "room" || @@ -779,6 +841,34 @@ setInterval(() => { Glow.refreshPresence?.(); } }, 10000); + +window.addEventListener("beforeunload", () => { + const sessionId = Glow.state.calls.activeCall?.groupSession?.id; + if (!sessionId || !navigator.sendBeacon) return; + + const csrf = Glow.state.csrfToken || ""; + const blob = new Blob([JSON.stringify({})], { + type: "application/json" + }); + + navigator.sendBeacon( + `/api/group-calls/${encodeURIComponent(sessionId)}/leave`, + blob + ); + + fetch(`/api/group-calls/${encodeURIComponent(sessionId)}/leave`, { + method: "POST", + credentials: "same-origin", + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": csrf, + Accept: "application/json" + }, + body: JSON.stringify({}), + keepalive: true + }).catch(() => {}); +}); + Glow.applyAccentColor(Glow.state.accentColor); Glow.autoResizeComposer(); Glow.updateMessageCount();