Update static/js/03-admin.js

This commit is contained in:
dev 2026-11-02 04:37:34 +00:00
parent c0a58bbde8
commit 86b0a7a5ab

View File

@ -6,7 +6,7 @@ window.Glow = window.Glow || {};
Glow.closeAdminPanel = () => { Glow.closeAdminPanel = () => {
Glow.els.adminPanel.classList.remove("open"); Glow.els.adminPanel.classList.remove("open");
Glow.els.adminPanel.setAttribute("aria-hidden", "true"); Glow.els.adminPanel.setAttribute("aria-hidden", "true");
Glow.els.adminFab.focus(); Glow.els.adminFab?.focus();
}; };
Glow.setAdminTab = tab => { Glow.setAdminTab = tab => {
@ -29,16 +29,12 @@ Glow.setAdminTab = tab => {
Glow.els.adminTeamsPanel?.classList.toggle("hidden", !isTeams); Glow.els.adminTeamsPanel?.classList.toggle("hidden", !isTeams);
}; };
Glow.buildAdminRow = ({ Glow.buildAdminRow = ({ title, subtitle, actions = [], className = "" }) => {
title,
subtitle,
actions = [],
className = ""
}) => {
const row = document.createElement("div"); const row = document.createElement("div");
row.className = className || "admin-room"; row.className = className || "admin-room";
const details = document.createElement("div"); const details = document.createElement("div");
const name = document.createElement("div"); const name = document.createElement("div");
name.className = name.className =
className.includes("user") className.includes("user")
@ -79,13 +75,13 @@ Glow.renderAdminUsers = users => {
Glow.els.adminUsers.replaceChildren(); Glow.els.adminUsers.replaceChildren();
const heading = document.createElement("h3"); const heading = document.createElement("h3");
heading.textContent = "Users"; heading.textContent = "users";
Glow.els.adminUsers.append(heading); Glow.els.adminUsers.append(heading);
if (!users.length) { if (!users.length) {
const empty = document.createElement("p"); const empty = document.createElement("p");
empty.className = "admin-empty"; empty.className = "admin-empty";
empty.textContent = "No users found."; empty.textContent = "no users found.";
Glow.els.adminUsers.append(empty); Glow.els.adminUsers.append(empty);
return; return;
} }
@ -102,7 +98,7 @@ Glow.renderAdminUsers = users => {
role.className = "small-button"; role.className = "small-button";
role.type = "button"; role.type = "button";
role.textContent = role.textContent =
user.role === "moderator" ? "Make user" : "Make moderator"; user.role === "moderator" ? "make user" : "make moderator";
role.addEventListener("click", () => { role.addEventListener("click", () => {
Glow.updateAdminUser(user.id, { Glow.updateAdminUser(user.id, {
role: user.role === "moderator" ? "user" : "moderator" role: user.role === "moderator" ? "user" : "moderator"
@ -113,7 +109,7 @@ Glow.renderAdminUsers = users => {
restrict.className = "small-button"; restrict.className = "small-button";
restrict.type = "button"; restrict.type = "button";
restrict.textContent = restrict.textContent =
user.status === "restricted" ? "Unrestrict" : "Restrict"; user.status === "restricted" ? "unrestrict" : "restrict";
restrict.addEventListener("click", () => { restrict.addEventListener("click", () => {
Glow.toggleRestriction(user); Glow.toggleRestriction(user);
}); });
@ -122,7 +118,7 @@ Glow.renderAdminUsers = users => {
blacklist.className = "small-button danger"; blacklist.className = "small-button danger";
blacklist.type = "button"; blacklist.type = "button";
blacklist.textContent = blacklist.textContent =
user.status === "blacklisted" ? "Unblacklist" : "Blacklist"; user.status === "blacklisted" ? "unblacklist" : "blacklist";
blacklist.addEventListener("click", () => { blacklist.addEventListener("click", () => {
Glow.toggleBlacklist(user); Glow.toggleBlacklist(user);
}); });
@ -132,11 +128,11 @@ Glow.renderAdminUsers = users => {
Glow.els.adminUsers.append( Glow.els.adminUsers.append(
Glow.buildAdminRow({ Glow.buildAdminRow({
title: `${user.username} · ${user.role}`, title: `${user.username} · ${user.role}`,
subtitle: subtitle:
`${user.status} · ${user.message_count} message(s)` + `${user.status} · ${user.message_count} message(s)` +
(user.restricted_until (user.restricted_until
? ` · until ${new Date(user.restricted_until).toLocaleString()}` ? ` · until ${new Date(user.restricted_until).toLocaleString()}`
: ""), : ""),
actions, actions,
className: "admin-user" className: "admin-user"
@ -149,13 +145,13 @@ Glow.renderAdminRooms = rooms => {
Glow.els.adminRooms.replaceChildren(); Glow.els.adminRooms.replaceChildren();
const heading = document.createElement("h3"); const heading = document.createElement("h3");
heading.textContent = "Rooms"; heading.textContent = "rooms";
Glow.els.adminRooms.append(heading); Glow.els.adminRooms.append(heading);
if (!rooms.length) { if (!rooms.length) {
const empty = document.createElement("p"); const empty = document.createElement("p");
empty.className = "admin-empty"; empty.className = "admin-empty";
empty.textContent = "No rooms found."; empty.textContent = "no rooms found.";
Glow.els.adminRooms.append(empty); Glow.els.adminRooms.append(empty);
return; return;
} }
@ -167,7 +163,7 @@ Glow.renderAdminRooms = rooms => {
const edit = document.createElement("button"); const edit = document.createElement("button");
edit.className = "small-button"; edit.className = "small-button";
edit.type = "button"; edit.type = "button";
edit.textContent = "Edit"; edit.textContent = "edit";
edit.addEventListener("click", () => Glow.editRoom(room)); edit.addEventListener("click", () => Glow.editRoom(room));
actions.push(edit); actions.push(edit);
@ -175,7 +171,7 @@ Glow.renderAdminRooms = rooms => {
const members = document.createElement("button"); const members = document.createElement("button");
members.className = "small-button"; members.className = "small-button";
members.type = "button"; members.type = "button";
members.textContent = "Members"; members.textContent = "members";
members.addEventListener("click", () => { members.addEventListener("click", () => {
Glow.manageRoomMembers(room); Glow.manageRoomMembers(room);
}); });
@ -183,7 +179,7 @@ Glow.renderAdminRooms = rooms => {
const invite = document.createElement("button"); const invite = document.createElement("button");
invite.className = "small-button"; invite.className = "small-button";
invite.type = "button"; invite.type = "button";
invite.textContent = "Invite link"; invite.textContent = "invite link";
invite.addEventListener("click", () => { invite.addEventListener("click", () => {
Glow.createInviteLink(room); Glow.createInviteLink(room);
}); });
@ -195,7 +191,7 @@ Glow.renderAdminRooms = rooms => {
const remove = document.createElement("button"); const remove = document.createElement("button");
remove.className = "small-button danger"; remove.className = "small-button danger";
remove.type = "button"; remove.type = "button";
remove.textContent = "Delete"; remove.textContent = "delete";
remove.addEventListener("click", () => Glow.deleteRoom(room)); remove.addEventListener("click", () => Glow.deleteRoom(room));
actions.push(remove); actions.push(remove);
} }
@ -203,10 +199,8 @@ Glow.renderAdminRooms = rooms => {
Glow.els.adminRooms.append( Glow.els.adminRooms.append(
Glow.buildAdminRow({ Glow.buildAdminRow({
title: `${room.name} · ${room.visibility}`, title: `${room.name} · ${room.visibility}`,
subtitle: subtitle: `/${room.slug}` + (room.description ? ` · ${room.description}` : ""),
`/${room.slug}` +
(room.description ? ` · ${room.description}` : ""),
actions, actions,
className: "admin-room" className: "admin-room"
}) })
@ -220,7 +214,7 @@ Glow.renderAdminTeamsChannels = () => {
box.replaceChildren(); box.replaceChildren();
const heading = document.createElement("h3"); const heading = document.createElement("h3");
heading.textContent = "Teams / channels"; heading.textContent = "teams / channels";
box.append(heading); box.append(heading);
const team = Glow.state.activeTeam; const team = Glow.state.activeTeam;
@ -229,7 +223,7 @@ Glow.renderAdminTeamsChannels = () => {
if (!team) { if (!team) {
const empty = document.createElement("p"); const empty = document.createElement("p");
empty.className = "admin-empty"; empty.className = "admin-empty";
empty.textContent = "Select a team to manage team/channel actions."; empty.textContent = "select a team to manage team or channel actions.";
box.append(empty); box.append(empty);
return; return;
} }
@ -239,25 +233,25 @@ Glow.renderAdminTeamsChannels = () => {
const editTeam = document.createElement("button"); const editTeam = document.createElement("button");
editTeam.className = "small-button"; editTeam.className = "small-button";
editTeam.type = "button"; editTeam.type = "button";
editTeam.textContent = "Edit team"; editTeam.textContent = "edit team";
editTeam.addEventListener("click", () => Glow.openEditTeamDialog?.(team)); editTeam.addEventListener("click", () => Glow.openEditTeamDialog?.(team));
const members = document.createElement("button"); const members = document.createElement("button");
members.className = "small-button"; members.className = "small-button";
members.type = "button"; members.type = "button";
members.textContent = "Members"; members.textContent = "members";
members.addEventListener("click", () => Glow.manageTeamMembers?.(team)); members.addEventListener("click", () => Glow.manageTeamMembers?.(team));
const invite = document.createElement("button"); const invite = document.createElement("button");
invite.className = "small-button"; invite.className = "small-button";
invite.type = "button"; invite.type = "button";
invite.textContent = "Invite"; invite.textContent = "invite";
invite.addEventListener("click", () => Glow.createTeamInviteLink?.(team)); invite.addEventListener("click", () => Glow.createTeamInviteLink?.(team));
const addChannel = document.createElement("button"); const addChannel = document.createElement("button");
addChannel.className = "small-button"; addChannel.className = "small-button";
addChannel.type = "button"; addChannel.type = "button";
addChannel.textContent = "Add channel"; addChannel.textContent = "add channel";
addChannel.addEventListener("click", () => Glow.openCreateChannelDialog?.(team)); addChannel.addEventListener("click", () => Glow.openCreateChannelDialog?.(team));
teamActions.push(editTeam, members, invite, addChannel); teamActions.push(editTeam, members, invite, addChannel);
@ -273,11 +267,10 @@ Glow.renderAdminTeamsChannels = () => {
); );
const channels = Array.isArray(Glow.state.channels) ? Glow.state.channels : []; const channels = Array.isArray(Glow.state.channels) ? Glow.state.channels : [];
if (!channels.length) { if (!channels.length) {
const empty = document.createElement("p"); const empty = document.createElement("p");
empty.className = "admin-empty"; empty.className = "admin-empty";
empty.textContent = "No channels available for this team."; empty.textContent = "no channels available for this team.";
box.append(empty); box.append(empty);
return; return;
} }
@ -290,7 +283,7 @@ Glow.renderAdminTeamsChannels = () => {
const edit = document.createElement("button"); const edit = document.createElement("button");
edit.className = "small-button"; edit.className = "small-button";
edit.type = "button"; edit.type = "button";
edit.textContent = "Edit"; edit.textContent = "edit";
edit.addEventListener("click", () => { edit.addEventListener("click", () => {
Glow.openEditChannelDialog?.(team, item); Glow.openEditChannelDialog?.(team, item);
}); });
@ -300,7 +293,7 @@ Glow.renderAdminTeamsChannels = () => {
const remove = document.createElement("button"); const remove = document.createElement("button");
remove.className = "small-button danger"; remove.className = "small-button danger";
remove.type = "button"; remove.type = "button";
remove.textContent = "Delete"; remove.textContent = "delete";
remove.addEventListener("click", async () => { remove.addEventListener("click", async () => {
const previous = Glow.state.activeChannel; const previous = Glow.state.activeChannel;
Glow.state.activeChannel = item; Glow.state.activeChannel = item;
@ -313,7 +306,7 @@ Glow.renderAdminTeamsChannels = () => {
box.append( box.append(
Glow.buildAdminRow({ Glow.buildAdminRow({
title: `${isActive ? "• " : ""}# ${item.name}`, title: `${isActive ? "• " : ""}# ${item.name}`,
subtitle: item.description || `/${item.slug}`, subtitle: item.description || `/${item.slug}`,
actions, actions,
className: "admin-team-row" className: "admin-team-row"
@ -329,13 +322,13 @@ Glow.openAdminPanel = async () => {
Glow.els.adminPanel.setAttribute("aria-hidden", "false"); Glow.els.adminPanel.setAttribute("aria-hidden", "false");
if (Glow.els.adminUsers) { if (Glow.els.adminUsers) {
Glow.els.adminUsers.textContent = "Loading users…"; Glow.els.adminUsers.textContent = "loading users…";
} }
if (Glow.els.adminRooms) { if (Glow.els.adminRooms) {
Glow.els.adminRooms.textContent = "Loading rooms…"; Glow.els.adminRooms.textContent = "loading rooms…";
} }
if (Glow.els.adminTeamsChannels) { if (Glow.els.adminTeamsChannels) {
Glow.els.adminTeamsChannels.textContent = "Loading teams/channels…"; Glow.els.adminTeamsChannels.textContent = "loading teams/channels…";
} }
try { try {
@ -347,127 +340,14 @@ Glow.openAdminPanel = async () => {
Glow.renderAdminUsers(usersData.users || []); Glow.renderAdminUsers(usersData.users || []);
Glow.renderAdminRooms(roomsData.rooms || []); Glow.renderAdminRooms(roomsData.rooms || []);
Glow.renderAdminTeamsChannels(); Glow.renderAdminTeamsChannels();
Glow.setAdminTab(Glow.state.adminTab || "rooms"); Glow.setAdminTab(Glow.state.adminTab || "rooms");
setTimeout(() => Glow.els.closeAdminButton.focus(), 0); setTimeout(() => Glow.els.closeAdminButton?.focus(), 0);
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not load moderation data.", "error");
} }
}; };
Glow.updateThemeButton = () => {
if (!Glow.els.themeButton) return;
Glow.els.themeButton.textContent = "Accent color";
};
Glow.updateAdminPreferenceButtons = () => {};
Glow.openAccentColorPicker = async () => {
const container = document.createElement("div");
container.className = "accent-picker";
const description = document.createElement("p");
description.className = "accent-picker-description";
description.textContent =
"Choose an accent color for focus rings, buttons, selected controls, and highlighted mentions. It is stored only in this browser unless saved in account settings.";
const label = document.createElement("label");
label.className = "accent-picker-label";
label.textContent = "Accent color";
const row = document.createElement("div");
row.className = "accent-picker-row";
const colorInput = document.createElement("input");
colorInput.className = "accent-color-input";
colorInput.type = "color";
colorInput.value =
Glow.normalizeAccentColor(Glow.state.accentColor) ||
Glow.DEFAULT_ACCENT_COLOR;
colorInput.setAttribute("aria-label", "Choose accent color");
const hexInput = document.createElement("input");
hexInput.className = "field accent-hex-input";
hexInput.type = "text";
hexInput.value = colorInput.value;
hexInput.maxLength = 7;
hexInput.autocomplete = "off";
hexInput.autocapitalize = "off";
hexInput.spellcheck = false;
hexInput.placeholder = "#f7dce8";
hexInput.setAttribute(
"aria-label",
"Accent color hexadecimal value"
);
const preview = document.createElement("span");
preview.className = "accent-preview";
preview.style.background = colorInput.value;
preview.setAttribute("aria-hidden", "true");
const error = document.createElement("div");
error.className = "accent-picker-error";
error.setAttribute("role", "status");
const updatePreview = value => {
const normalized = Glow.normalizeAccentColor(value);
if (!normalized) {
preview.style.background = "transparent";
error.textContent =
"Use a six-digit hex color, such as #7dd3fc.";
return false;
}
preview.style.background = normalized;
error.textContent = "";
if (colorInput.value !== normalized) {
colorInput.value = normalized;
}
return true;
};
colorInput.addEventListener("input", () => {
const color = Glow.normalizeAccentColor(colorInput.value);
if (!color) return;
hexInput.value = color;
updatePreview(color);
});
hexInput.addEventListener("input", () => {
updatePreview(hexInput.value.trim());
});
row.append(colorInput, hexInput, preview);
label.append(row);
container.append(description, label, error);
const result = await Glow.openDialog({
title: "Accent color",
body: container,
confirmText: "Save color",
cancelText: "Cancel"
});
if (!result) return;
const accent = Glow.normalizeAccentColor(hexInput.value);
if (!accent) {
Glow.showToast(
"Choose a valid six-digit hexadecimal color.",
"error"
);
return;
}
Glow.applyAccentColor(accent);
Glow.showToast("Accent color updated.");
};
Glow.toggleRestriction = async user => { Glow.toggleRestriction = async user => {
if (user.status === "restricted") { if (user.status === "restricted") {
return Glow.updateAdminUser(user.id, { return Glow.updateAdminUser(user.id, {
@ -477,14 +357,14 @@ Glow.toggleRestriction = async user => {
} }
const values = await Glow.openDialog({ const values = await Glow.openDialog({
title: `Restrict ${user.username}`, title: `restrict ${user.username}`,
body: "Enter the restriction duration in minutes.", body: "enter the restriction duration in minutes.",
confirmText: "Restrict", confirmText: "restrict",
danger: true, danger: true,
fields: [ fields: [
{ {
name: "minutes", name: "minutes",
label: "Duration in minutes", label: "duration in minutes",
type: "number", type: "number",
value: "60", value: "60",
maxLength: 5 maxLength: 5
@ -495,20 +375,14 @@ Glow.toggleRestriction = async user => {
if (!values) return; if (!values) return;
const minutes = Number(values.minutes); const minutes = Number(values.minutes);
if (!Number.isFinite(minutes) || minutes < 1 || minutes > 43200) { if (!Number.isFinite(minutes) || minutes < 1 || minutes > 43200) {
Glow.showToast( Glow.showToast("enter a duration between 1 minute and 30 days.", "error");
"Enter a duration between 1 minute and 30 days.",
"error"
);
return; return;
} }
await Glow.updateAdminUser(user.id, { await Glow.updateAdminUser(user.id, {
status: "restricted", status: "restricted",
restricted_until: new Date( restricted_until: new Date(Date.now() + minutes * 60 * 1000).toISOString()
Date.now() + minutes * 60 * 1000
).toISOString()
}); });
}; };
@ -517,12 +391,12 @@ Glow.toggleBlacklist = async user => {
const result = await Glow.openDialog({ const result = await Glow.openDialog({
title: blacklisting title: blacklisting
? `Blacklist ${user.username}?` ? `blacklist ${user.username}?`
: `Unblacklist ${user.username}?`, : `unblacklist ${user.username}?`,
body: blacklisting body: blacklisting
? "The account will be denied access immediately." ? "the account will be denied access immediately."
: "The account will be restored to active status.", : "the account will be restored to active status.",
confirmText: blacklisting ? "Blacklist" : "Unblacklist", confirmText: blacklisting ? "blacklist" : "unblacklist",
danger: blacklisting danger: blacklisting
}); });
@ -541,36 +415,36 @@ Glow.updateAdminUser = async (userId, changes) => {
body: JSON.stringify(changes) body: JSON.stringify(changes)
}); });
Glow.showToast("User updated."); Glow.showToast("user updated.");
await Glow.openAdminPanel(); await Glow.openAdminPanel();
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not update user.", "error");
} }
}; };
Glow.createRoom = async () => { Glow.createRoom = async () => {
const values = await Glow.openDialog({ const values = await Glow.openDialog({
title: "Create room", title: "create room",
body: "Choose a room name, description, and visibility.", body: "choose a room name, description, and visibility.",
confirmText: "Create", confirmText: "create",
fields: [ fields: [
{ {
name: "name", name: "name",
label: "Room name", label: "room name",
placeholder: "Project chat", placeholder: "project chat",
maxLength: 60 maxLength: 60
}, },
{ {
name: "description", name: "description",
label: "Description", label: "description",
placeholder: "Optional room description", placeholder: "optional room description",
multiline: true, multiline: true,
rows: 3, rows: 3,
maxLength: 300 maxLength: 300
}, },
{ {
name: "visibility", name: "visibility",
label: "Visibility: public or private", label: "visibility: public or private",
value: "public", value: "public",
maxLength: 7 maxLength: 7
} }
@ -579,46 +453,41 @@ Glow.createRoom = async () => {
if (!values) return; if (!values) return;
const visibility = values.visibility.toLowerCase(); const visibility = String(values.visibility || "").toLowerCase();
if (!["public", "private"].includes(visibility)) { if (!["public", "private"].includes(visibility)) {
Glow.showToast("Visibility must be public or private.", "error"); Glow.showToast("visibility must be public or private.", "error");
return; return;
} }
try { try {
await Glow.apiRequest("/api/rooms", { await Glow.apiCreateRoom({
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: values.name, name: values.name,
description: values.description, description: values.description,
visibility visibility
})
}); });
Glow.showToast("Room created."); Glow.showToast("room created.");
await Glow.loadRooms(); await Glow.loadRooms();
await Glow.openAdminPanel(); await Glow.openAdminPanel();
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not create room.", "error");
} }
}; };
Glow.editRoom = async room => { Glow.editRoom = async room => {
const values = await Glow.openDialog({ const values = await Glow.openDialog({
title: `Edit ${room.name}`, title: `edit ${room.name}`,
confirmText: "Save", confirmText: "save",
fields: [ fields: [
{ {
name: "name", name: "name",
label: "Room name", label: "room name",
value: room.name, value: room.name,
maxLength: 60 maxLength: 60
}, },
{ {
name: "description", name: "description",
label: "Description", label: "description",
value: room.description || "", value: room.description || "",
multiline: true, multiline: true,
rows: 3, rows: 3,
@ -626,7 +495,7 @@ Glow.editRoom = async room => {
}, },
{ {
name: "visibility", name: "visibility",
label: "Visibility: public or private", label: "visibility: public or private",
value: room.visibility, value: room.visibility,
maxLength: 7 maxLength: 7
} }
@ -635,10 +504,9 @@ Glow.editRoom = async room => {
if (!values) return; if (!values) return;
const visibility = values.visibility.toLowerCase(); const visibility = String(values.visibility || "").toLowerCase();
if (!["public", "private"].includes(visibility)) { if (!["public", "private"].includes(visibility)) {
Glow.showToast("Visibility must be public or private.", "error"); Glow.showToast("visibility must be public or private.", "error");
return; return;
} }
@ -651,13 +519,8 @@ Glow.editRoom = async room => {
Glow.updateRoom = async (slug, changes) => { Glow.updateRoom = async (slug, changes) => {
try { try {
await Glow.apiRequest(`/api/rooms/${encodeURIComponent(slug)}`, { await Glow.apiUpdateRoom(slug, changes);
method: "PATCH", Glow.showToast("room updated.");
headers: { "Content-Type": "application/json" },
body: JSON.stringify(changes)
});
Glow.showToast("Room updated.");
await Glow.loadRooms(); await Glow.loadRooms();
if (!Glow.state.activeDm) { if (!Glow.state.activeDm) {
@ -666,15 +529,15 @@ Glow.updateRoom = async (slug, changes) => {
await Glow.openAdminPanel(); await Glow.openAdminPanel();
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not update room.", "error");
} }
}; };
Glow.deleteRoom = async room => { Glow.deleteRoom = async room => {
const result = await Glow.openDialog({ const result = await Glow.openDialog({
title: `Delete ${room.name}?`, title: `delete ${room.name}?`,
body: "This permanently deletes the room and every message in it.", body: "this permanently deletes the room and every message in it.",
confirmText: "Delete room", confirmText: "delete room",
danger: true danger: true
}); });
@ -685,7 +548,7 @@ Glow.deleteRoom = async room => {
method: "DELETE" method: "DELETE"
}); });
Glow.showToast("Room deleted."); Glow.showToast("room deleted.");
await Glow.loadRooms(); await Glow.loadRooms();
if (!Glow.state.activeDm) { if (!Glow.state.activeDm) {
@ -694,43 +557,39 @@ Glow.deleteRoom = async room => {
await Glow.openAdminPanel(); await Glow.openAdminPanel();
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not delete room.", "error");
} }
}; };
Glow.clearActiveRoom = async () => { Glow.clearActiveRoom = async () => {
const room = Glow.state.activeRoom; const room = Glow.state.activeRoom;
if (!room || Glow.state.activeDm) return; if (!room || Glow.state.activeDm) return;
const result = await Glow.openDialog({ const result = await Glow.openDialog({
title: `Clear ${room.name}?`, title: `clear ${room.name}?`,
body: "All messages in this room will be permanently deleted.", body: "all messages in this room will be permanently deleted.",
confirmText: "Clear room", confirmText: "clear room",
danger: true danger: true
}); });
if (!result) return; if (!result) return;
try { try {
await Glow.apiRequest( await Glow.apiRequest(`/api/rooms/${encodeURIComponent(room.slug)}/messages`, {
`/api/rooms/${encodeURIComponent(room.slug)}/messages`, method: "DELETE"
{ method: "DELETE" } });
);
Glow.clearMessages(); Glow.clearMessages();
Glow.renderEmptyState(); Glow.renderEmptyState();
Glow.showToast("Room messages cleared."); Glow.showToast("room messages cleared.");
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not clear room.", "error");
} }
}; };
Glow.manageRoomMembers = async room => { Glow.manageRoomMembers = async room => {
try { try {
const data = await Glow.apiRequest( const data = await Glow.apiRequest(`/api/rooms/${encodeURIComponent(room.slug)}/members`);
`/api/rooms/${encodeURIComponent(room.slug)}/members`
);
const container = document.createElement("div"); const container = document.createElement("div");
container.className = "member-list"; container.className = "member-list";
@ -740,12 +599,12 @@ Glow.manageRoomMembers = async room => {
row.className = "member-row"; row.className = "member-row";
const name = document.createElement("span"); const name = document.createElement("span");
name.textContent = `${member.username} · ${member.membership_role}`; name.textContent = `${member.username} · ${member.membership_role}`;
const remove = document.createElement("button"); const remove = document.createElement("button");
remove.className = "small-button danger"; remove.className = "small-button danger";
remove.type = "button"; remove.type = "button";
remove.textContent = "Remove"; remove.textContent = "remove";
remove.disabled = member.membership_role === "owner"; remove.disabled = member.membership_role === "owner";
remove.addEventListener("click", async () => { remove.addEventListener("click", async () => {
@ -754,12 +613,11 @@ Glow.manageRoomMembers = async room => {
`/api/rooms/${encodeURIComponent(room.slug)}/members/${member.id}`, `/api/rooms/${encodeURIComponent(room.slug)}/members/${member.id}`,
{ method: "DELETE" } { method: "DELETE" }
); );
Glow.showToast("member removed.");
Glow.showToast("Member removed.");
Glow.closeDialog({}); Glow.closeDialog({});
Glow.manageRoomMembers(room); Glow.manageRoomMembers(room);
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not remove member.", "error");
} }
}); });
@ -770,11 +628,11 @@ Glow.manageRoomMembers = async room => {
const values = await Glow.openDialog({ const values = await Glow.openDialog({
title: `${room.name} members`, title: `${room.name} members`,
body: container, body: container,
confirmText: "Add member", confirmText: "add member",
fields: [ fields: [
{ {
name: "username", name: "username",
label: "Username to add", label: "username to add",
placeholder: "username", placeholder: "username",
maxLength: 30 maxLength: 30
} }
@ -783,46 +641,37 @@ Glow.manageRoomMembers = async room => {
if (!values?.username) return; if (!values?.username) return;
await Glow.apiRequest( await Glow.apiRequest(`/api/rooms/${encodeURIComponent(room.slug)}/members`, {
`/api/rooms/${encodeURIComponent(room.slug)}/members`,
{
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: values.username }) body: JSON.stringify({ username: values.username })
} });
);
Glow.showToast("Member added."); Glow.showToast("member added.");
await Glow.manageRoomMembers(room); await Glow.manageRoomMembers(room);
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not manage room members.", "error");
} }
}; };
Glow.createInviteLink = async room => { Glow.createInviteLink = async room => {
try { try {
const data = await Glow.apiRequest( const data = await Glow.apiRequest(`/api/rooms/${encodeURIComponent(room.slug)}/invites`, {
`/api/rooms/${encodeURIComponent(room.slug)}/invites`,
{
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({}) body: JSON.stringify({})
} });
);
const url = new URL( const url = new URL(data.invite.url, window.location.origin).toString();
data.invite.url,
window.location.origin
).toString();
const values = await Glow.openDialog({ const values = await Glow.openDialog({
title: "Private-room invite link", title: "private-room invite link",
body: "Copy and share this link. It can be used once before it expires.", body: "copy and share this link. it can be used once before it expires.",
confirmText: "Copy link", confirmText: "copy link",
fields: [ fields: [
{ {
name: "invite", name: "invite",
label: "Invite link", label: "invite link",
value: url, value: url,
maxLength: 500 maxLength: 500
} }
@ -831,10 +680,10 @@ Glow.createInviteLink = async room => {
if (values) { if (values) {
await navigator.clipboard.writeText(url); await navigator.clipboard.writeText(url);
Glow.showToast("Invite link copied."); Glow.showToast("invite link copied.");
} }
} catch (error) { } catch (error) {
Glow.showToast(error.message, "error"); Glow.showToast(error.message || "could not create invite link.", "error");
} }
}; };