diff --git a/static/js/03-admin.js b/static/js/03-admin.js index 408e167..0abccee 100644 --- a/static/js/03-admin.js +++ b/static/js/03-admin.js @@ -9,6 +9,72 @@ Glow.closeAdminPanel = () => { Glow.els.adminFab.focus(); }; +Glow.setAdminTab = tab => { + Glow.state.adminTab = ["rooms", "users", "teams"].includes(tab) ? tab : "rooms"; + + const isRooms = Glow.state.adminTab === "rooms"; + const isUsers = Glow.state.adminTab === "users"; + const isTeams = Glow.state.adminTab === "teams"; + + Glow.els.adminTabRooms?.classList.toggle("active", isRooms); + Glow.els.adminTabUsers?.classList.toggle("active", isUsers); + Glow.els.adminTabTeams?.classList.toggle("active", isTeams); + + Glow.els.adminTabRooms?.setAttribute("aria-selected", String(isRooms)); + Glow.els.adminTabUsers?.setAttribute("aria-selected", String(isUsers)); + Glow.els.adminTabTeams?.setAttribute("aria-selected", String(isTeams)); + + Glow.els.adminRoomsPanel?.classList.toggle("hidden", !isRooms); + Glow.els.adminUsersPanel?.classList.toggle("hidden", !isUsers); + Glow.els.adminTeamsPanel?.classList.toggle("hidden", !isTeams); +}; + +Glow.buildAdminRow = ({ + title, + subtitle, + actions = [], + className = "" +}) => { + const row = document.createElement("div"); + row.className = className || "admin-room"; + + const details = document.createElement("div"); + const name = document.createElement("div"); + name.className = + className.includes("user") + ? "admin-user-name" + : className.includes("team") + ? "admin-team-name" + : "admin-room-name"; + name.textContent = title; + + const info = document.createElement("div"); + info.className = + className.includes("user") + ? "admin-user-info" + : className.includes("team") + ? "admin-team-info" + : "admin-room-info"; + info.textContent = subtitle || ""; + + details.append(name, info); + + const actionsWrap = document.createElement("div"); + actionsWrap.className = + className.includes("user") + ? "admin-user-actions" + : className.includes("team") + ? "admin-team-actions" + : "admin-room-actions"; + + for (const action of actions) { + actionsWrap.append(action); + } + + row.append(details, actionsWrap); + return row; +}; + Glow.renderAdminUsers = users => { Glow.els.adminUsers.replaceChildren(); @@ -25,39 +91,18 @@ Glow.renderAdminUsers = users => { } for (const user of users) { - const row = document.createElement("div"); - row.className = "admin-user"; - - const details = document.createElement("div"); - - const name = document.createElement("div"); - name.className = "admin-user-name"; - name.textContent = `${user.username} · ${user.role}`; - - const info = document.createElement("div"); - info.className = "admin-user-info"; - info.textContent = - `${user.status} · ${user.message_count} message(s)` + - (user.restricted_until - ? ` · until ${new Date(user.restricted_until).toLocaleString()}` - : ""); - - details.append(name, info); - - const actions = document.createElement("div"); - actions.className = "admin-user-actions"; - const current = Glow.state.currentUser && Number(Glow.state.currentUser.id) === Number(user.id); + const actions = []; + if (!current && Glow.isAdmin()) { const role = document.createElement("button"); role.className = "small-button"; role.type = "button"; role.textContent = user.role === "moderator" ? "Make user" : "Make moderator"; - role.addEventListener("click", () => { Glow.updateAdminUser(user.id, { role: user.role === "moderator" ? "user" : "moderator" @@ -69,7 +114,6 @@ Glow.renderAdminUsers = users => { restrict.type = "button"; restrict.textContent = user.status === "restricted" ? "Unrestrict" : "Restrict"; - restrict.addEventListener("click", () => { Glow.toggleRestriction(user); }); @@ -79,16 +123,25 @@ Glow.renderAdminUsers = users => { blacklist.type = "button"; blacklist.textContent = user.status === "blacklisted" ? "Unblacklist" : "Blacklist"; - blacklist.addEventListener("click", () => { Glow.toggleBlacklist(user); }); - actions.append(role, restrict, blacklist); + actions.push(role, restrict, blacklist); } - row.append(details, actions); - Glow.els.adminUsers.append(row); + Glow.els.adminUsers.append( + Glow.buildAdminRow({ + title: `${user.username} · ${user.role}`, + subtitle: + `${user.status} · ${user.message_count} message(s)` + + (user.restricted_until + ? ` · until ${new Date(user.restricted_until).toLocaleString()}` + : ""), + actions, + className: "admin-user" + }) + ); } }; @@ -108,25 +161,7 @@ Glow.renderAdminRooms = rooms => { } for (const room of rooms) { - const row = document.createElement("div"); - row.className = "admin-room"; - - const details = document.createElement("div"); - - const name = document.createElement("div"); - name.className = "admin-room-name"; - name.textContent = `${room.name} · ${room.visibility}`; - - const info = document.createElement("div"); - info.className = "admin-room-info"; - info.textContent = - `/${room.slug}` + - (room.description ? ` · ${room.description}` : ""); - - details.append(name, info); - - const actions = document.createElement("div"); - actions.className = "admin-room-actions"; + const actions = []; if (Glow.isAdmin()) { const edit = document.createElement("button"); @@ -134,8 +169,7 @@ Glow.renderAdminRooms = rooms => { edit.type = "button"; edit.textContent = "Edit"; edit.addEventListener("click", () => Glow.editRoom(room)); - - actions.append(edit); + actions.push(edit); if (room.visibility === "private") { const members = document.createElement("button"); @@ -154,7 +188,7 @@ Glow.renderAdminRooms = rooms => { Glow.createInviteLink(room); }); - actions.append(members, invite); + actions.push(members, invite); } if (room.slug !== "general") { @@ -163,13 +197,128 @@ Glow.renderAdminRooms = rooms => { remove.type = "button"; remove.textContent = "Delete"; remove.addEventListener("click", () => Glow.deleteRoom(room)); - - actions.append(remove); + actions.push(remove); } } - row.append(details, actions); - Glow.els.adminRooms.append(row); + Glow.els.adminRooms.append( + Glow.buildAdminRow({ + title: `${room.name} · ${room.visibility}`, + subtitle: + `/${room.slug}` + + (room.description ? ` · ${room.description}` : ""), + actions, + className: "admin-room" + }) + ); + } +}; + +Glow.renderAdminTeamsChannels = () => { + const box = Glow.els.adminTeamsChannels; + if (!box) return; + box.replaceChildren(); + + const heading = document.createElement("h3"); + heading.textContent = "Teams / channels"; + box.append(heading); + + const team = Glow.state.activeTeam; + const channel = Glow.state.activeChannel || Glow.state.activeRoom; + + if (!team) { + const empty = document.createElement("p"); + empty.className = "admin-empty"; + empty.textContent = "Select a team to manage team/channel actions."; + box.append(empty); + return; + } + + const teamActions = []; + if (team.can_manage) { + const editTeam = document.createElement("button"); + editTeam.className = "small-button"; + editTeam.type = "button"; + editTeam.textContent = "Edit team"; + editTeam.addEventListener("click", () => Glow.openEditTeamDialog?.(team)); + + const members = document.createElement("button"); + members.className = "small-button"; + members.type = "button"; + members.textContent = "Members"; + members.addEventListener("click", () => Glow.manageTeamMembers?.(team)); + + const invite = document.createElement("button"); + invite.className = "small-button"; + invite.type = "button"; + invite.textContent = "Invite"; + invite.addEventListener("click", () => Glow.createTeamInviteLink?.(team)); + + const addChannel = document.createElement("button"); + addChannel.className = "small-button"; + addChannel.type = "button"; + addChannel.textContent = "Add channel"; + addChannel.addEventListener("click", () => Glow.openCreateChannelDialog?.(team)); + + teamActions.push(editTeam, members, invite, addChannel); + } + + box.append( + Glow.buildAdminRow({ + title: team.name, + subtitle: team.description || `/${team.slug}`, + actions: teamActions, + className: "admin-team-row" + }) + ); + + const channels = Array.isArray(Glow.state.channels) ? Glow.state.channels : []; + + if (!channels.length) { + const empty = document.createElement("p"); + empty.className = "admin-empty"; + empty.textContent = "No channels available for this team."; + box.append(empty); + return; + } + + for (const item of channels) { + const actions = []; + const isActive = channel && item.slug === channel.slug; + + if (team.can_manage) { + const edit = document.createElement("button"); + edit.className = "small-button"; + edit.type = "button"; + edit.textContent = "Edit"; + edit.addEventListener("click", () => { + Glow.openEditChannelDialog?.(team, item); + }); + actions.push(edit); + + if (!item.legacy_room_id) { + const remove = document.createElement("button"); + remove.className = "small-button danger"; + remove.type = "button"; + remove.textContent = "Delete"; + remove.addEventListener("click", async () => { + const previous = Glow.state.activeChannel; + Glow.state.activeChannel = item; + await Glow.deleteCurrentChannel?.(team); + Glow.state.activeChannel = previous; + }); + actions.push(remove); + } + } + + box.append( + Glow.buildAdminRow({ + title: `${isActive ? "• " : ""}# ${item.name}`, + subtitle: item.description || `/${item.slug}`, + actions, + className: "admin-team-row" + }) + ); } }; @@ -179,8 +328,15 @@ Glow.openAdminPanel = async () => { Glow.els.adminPanel.classList.add("open"); Glow.els.adminPanel.setAttribute("aria-hidden", "false"); - Glow.els.adminUsers.textContent = "Loading users…"; - Glow.els.adminRooms.textContent = "Loading rooms…"; + if (Glow.els.adminUsers) { + Glow.els.adminUsers.textContent = "Loading users…"; + } + if (Glow.els.adminRooms) { + Glow.els.adminRooms.textContent = "Loading rooms…"; + } + if (Glow.els.adminTeamsChannels) { + Glow.els.adminTeamsChannels.textContent = "Loading teams/channels…"; + } try { const [usersData, roomsData] = await Promise.all([ @@ -190,7 +346,9 @@ Glow.openAdminPanel = async () => { Glow.renderAdminUsers(usersData.users || []); Glow.renderAdminRooms(roomsData.rooms || []); - Glow.updateAdminPreferenceButtons(); + Glow.renderAdminTeamsChannels(); + + Glow.setAdminTab(Glow.state.adminTab || "rooms"); setTimeout(() => Glow.els.closeAdminButton.focus(), 0); } catch (error) { @@ -203,15 +361,7 @@ Glow.updateThemeButton = () => { Glow.els.themeButton.textContent = "Accent color"; }; -Glow.updateAdminPreferenceButtons = () => { - Glow.els.typingToggleButton.textContent = - `Typing indicators: ${Glow.state.typingIndicatorsEnabled ? "on" : "off"}`; - - Glow.els.notificationsToggleButton.textContent = - `Notifications: ${Glow.state.notificationsEnabled ? "on" : "off"}`; - - Glow.updateThemeButton(); -}; +Glow.updateAdminPreferenceButtons = () => {}; Glow.openAccentColorPicker = async () => { const container = document.createElement("div"); @@ -590,7 +740,7 @@ Glow.manageRoomMembers = async room => { row.className = "member-row"; const name = document.createElement("span"); - name.textContent = `${member.username} · ${member.membership_role}`; + name.textContent = `${member.username} · ${member.membership_role}`; const remove = document.createElement("button"); remove.className = "small-button danger"; @@ -687,3 +837,15 @@ Glow.createInviteLink = async room => { Glow.showToast(error.message, "error"); } }; + +Glow.els.adminTabRooms?.addEventListener("click", () => { + Glow.setAdminTab("rooms"); +}); + +Glow.els.adminTabUsers?.addEventListener("click", () => { + Glow.setAdminTab("users"); +}); + +Glow.els.adminTabTeams?.addEventListener("click", () => { + Glow.setAdminTab("teams"); +}); \ No newline at end of file