From b8805916517ab3565c30a1ce0f4a4531115a05e9 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 8 Oct 2026 00:39:12 +0000 Subject: [PATCH] Update static/js/04-main.js --- static/js/04-main.js | 239 ++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 150 deletions(-) diff --git a/static/js/04-main.js b/static/js/04-main.js index dcdc488..afab36f 100644 --- a/static/js/04-main.js +++ b/static/js/04-main.js @@ -5,10 +5,8 @@ window.Glow = window.Glow || {}; 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; @@ -16,20 +14,61 @@ 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; @@ -42,7 +81,6 @@ 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); @@ -52,7 +90,6 @@ 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 = []; @@ -61,25 +98,19 @@ 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 @@ -89,7 +120,6 @@ Glow.updateViewUI = () => { Glow.els.presenceLabel?.classList.add("hidden"); return; } - if (dmListActive) { Glow.els.headerTitle.textContent = "Direct messages"; Glow.els.headerSubtitle.textContent = Glow.state.currentUser @@ -98,7 +128,6 @@ Glow.updateViewUI = () => { Glow.els.presenceLabel?.classList.add("hidden"); return; } - if (threadActive) { Glow.els.headerTitle.textContent = "Thread"; Glow.els.headerSubtitle.textContent = @@ -106,21 +135,18 @@ Glow.updateViewUI = () => { Glow.updateThreadControls?.(); return; } - if (pagesActive) { Glow.els.headerTitle.textContent = "Pages"; Glow.els.headerSubtitle.textContent = "Shared knowledge and resources"; Glow.els.presenceLabel?.classList.add("hidden"); 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"); return; } - if (profileActive) { Glow.els.headerTitle.textContent = Glow.state.activeProfile?.display_name || @@ -130,19 +156,19 @@ Glow.updateViewUI = () => { Glow.els.presenceLabel?.classList.add("hidden"); return; } - if (settingsActive) { Glow.els.headerTitle.textContent = "Settings"; Glow.els.headerSubtitle.textContent = "Appearance, profile, and preferences"; Glow.els.presenceLabel?.classList.add("hidden"); return; } - if (view === "dm" && dm) { const peer = (dm.members || []).find( member => Number(member.id) !== Number(Glow.state.currentUser?.id) - ) || dm.members?.[0]; + ) || + dm.other_user || + dm.members?.[0]; Glow.els.headerTitle.textContent = peer?.display_name || peer?.username || "Direct message"; Glow.els.headerSubtitle.textContent = @@ -151,7 +177,6 @@ Glow.updateViewUI = () => { Glow.updateChatControls(); return; } - if (room) { Glow.els.headerTitle.textContent = room.name; Glow.els.headerSubtitle.textContent = @@ -162,32 +187,24 @@ Glow.updateViewUI = () => { Glow.els.headerTitle.textContent = "No rooms"; Glow.els.headerSubtitle.textContent = "No accessible chat rooms"; } - Glow.updateChatControls(); }; 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?.(); @@ -198,7 +215,6 @@ 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( @@ -218,7 +234,6 @@ Glow.checkInvite = async () => { } } } - if (teamToken) { try { const data = await Glow.apiAcceptTeamInvite(teamToken); @@ -238,18 +253,14 @@ 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"); @@ -263,38 +274,30 @@ 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." ); @@ -315,7 +318,6 @@ Glow.logout = async () => { try { await Glow.apiRequest("/api/logout", { method: "POST" }); } catch (_) {} - Glow.state.currentUser = null; Glow.state.csrfToken = ""; Glow.state.rooms = []; @@ -332,7 +334,6 @@ Glow.logout = async () => { Glow.state.activePage = null; Glow.state.activeProfile = null; Glow.state.settings = null; - Glow.clearMessages(); Glow.clearFeed(); Glow.clearDms(); @@ -340,17 +341,12 @@ Glow.logout = async () => { Glow.closeReactionPopup?.(); Glow.closeMediaModal?.(); Glow.endLocalCallUi?.(); - clearTimeout(Glow.state.calls?.invitePollTimer); - Glow.updateAuthUI(); - Glow.els.authOverlay.classList.remove("hidden"); Glow.els.accessKeyInput.focus(); - await Glow.refreshCsrfToken().catch(() => {}); await Glow.loadFeed().catch(() => {}); - Glow.showToast("Signed out."); }; @@ -359,7 +355,6 @@ Glow.toggleNotifications = async () => { Glow.showToast("Browser notifications are not supported.", "error"); return; } - if (!Glow.state.notificationsEnabled) { const permission = await Notification.requestPermission(); if (permission !== "granted") { @@ -367,7 +362,6 @@ Glow.toggleNotifications = async () => { return; } } - Glow.state.notificationsEnabled = !Glow.state.notificationsEnabled; localStorage.setItem( "glow_notifications_enabled", @@ -402,19 +396,16 @@ Glow.toggleTypingIndicators = () => { Glow.pingPresence = async () => { if (!Glow.state.currentUser) return; - const payload = { view: Glow.state.activeView }; - if (Glow.state.activeDm?.id) { - payload.direct_conversation_id = Glow.state.activeDm.id; + payload.dm_id = Glow.state.activeDm.id; } else if (Glow.state.activeChannel?.id) { payload.channel_id = Glow.state.activeChannel.id; } else if (Glow.state.activeRoom?.id) { payload.room_id = Glow.state.activeRoom.id; } - try { await Glow.apiPingPresence(payload); } catch (_) {} @@ -429,7 +420,6 @@ 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, @@ -440,7 +430,6 @@ 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; @@ -448,7 +437,6 @@ Glow.refreshPresence = async () => { Glow.els.presenceLabel.classList.remove("hidden"); return; } - Glow.els.presenceLabel.classList.add("hidden"); } catch (_) { Glow.els.presenceLabel?.classList.add("hidden"); @@ -458,7 +446,6 @@ 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(); @@ -475,7 +462,6 @@ Glow.schedulePoll = () => { } else if (Glow.state.activeView === "dms") { await Glow.loadDms?.().catch(() => {}); } - await Glow.pingPresence?.(); await Glow.refreshPresence?.(); Glow.schedulePoll(); @@ -484,10 +470,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); @@ -498,26 +484,23 @@ 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) { @@ -533,7 +516,6 @@ Glow.els.attachmentInput.addEventListener("change", () => { } Glow.setSelectedAttachment(files); }); - Glow.els.threadAttachmentInput?.addEventListener("change", () => { const files = Glow.normalizeSelectedFiles(Glow.els.threadAttachmentInput.files); if (!files.length) { @@ -549,20 +531,16 @@ 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?.(); @@ -576,30 +554,24 @@ 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"; @@ -607,20 +579,17 @@ 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); @@ -628,121 +597,99 @@ 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.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") 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(); + 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(); + } } }); - document.addEventListener("visibilitychange", () => { Glow.schedulePoll(); Glow.calls?.scheduleInvitePolling?.(); - if (!document.hidden) { Glow.resetUnreadMessages(); - if (Glow.state.activeView === "feed") { Glow.pollFeed(); } else if ( @@ -759,15 +706,12 @@ document.addEventListener("visibilitychange", () => { } 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 ( @@ -784,16 +728,13 @@ window.addEventListener("online", () => { } 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" || @@ -803,7 +744,6 @@ setInterval(() => { Glow.pollTyping(); } }, 2500); - setInterval(() => { if ( Glow.state.activeView === "room" || @@ -814,7 +754,6 @@ setInterval(() => { Glow.refreshPresence?.(); } }, 10000); - Glow.applyAccentColor(Glow.state.accentColor); Glow.autoResizeComposer(); Glow.updateMessageCount();