diff --git a/db.ts b/db.ts
index 2d14310..775addc 100644
--- a/db.ts
+++ b/db.ts
@@ -386,15 +386,20 @@ export function deleteChannelFromDb(id: string): void {
// Queue persistence functions
export function saveChannelQueue(channelId: string, trackIds: string[]): void {
- // Delete existing queue
- db.query("DELETE FROM channel_queue WHERE channel_id = ?").run(channelId);
-
- // Insert new queue
- const insert = db.query(
- "INSERT INTO channel_queue (channel_id, track_id, position) VALUES (?, ?, ?)"
- );
- for (let i = 0; i < trackIds.length; i++) {
- insert.run(channelId, trackIds[i], i);
+ db.query("BEGIN").run();
+ try {
+ db.query("DELETE FROM channel_queue WHERE channel_id = ?").run(channelId);
+
+ const insert = db.query(
+ "INSERT INTO channel_queue (channel_id, track_id, position) VALUES (?, ?, ?)"
+ );
+ for (let i = 0; i < trackIds.length; i++) {
+ insert.run(channelId, trackIds[i], i);
+ }
+ db.query("COMMIT").run();
+ } catch (e) {
+ db.query("ROLLBACK").run();
+ throw e;
}
}
diff --git a/public/channelSync.js b/public/channelSync.js
index b9ebbe9..9dff468 100644
--- a/public/channelSync.js
+++ b/public/channelSync.js
@@ -50,6 +50,28 @@
}
};
+ // Delete a channel
+ M.deleteChannel = async function(channelId) {
+ const channel = M.channels?.find(c => c.id === channelId);
+ if (!channel) return;
+ if (channel.isDefault) {
+ M.showToast("Cannot delete default channel");
+ return;
+ }
+ if (!confirm(`Delete channel "${channel.name}"?`)) return;
+ try {
+ const res = await fetch(`/api/channels/${channelId}`, { method: "DELETE" });
+ if (!res.ok) {
+ const err = await res.json();
+ M.showToast(err.error || "Failed to delete channel");
+ return;
+ }
+ M.showToast(`Channel "${channel.name}" deleted`);
+ } catch (e) {
+ M.showToast("Failed to delete channel");
+ }
+ };
+
// New channel creation with slideout input
M.createNewChannel = async function() {
const header = M.$("#channels-panel .panel-header");
@@ -129,14 +151,29 @@
const listenersHtml = Object.entries(counts).map(([name, count]) =>
`
${name}${count > 1 ? ` x${count}` : ""}
`
).join("");
+
+ // Show delete button for non-default channels if user is admin or creator
+ const canDelete = !ch.isDefault && M.currentUser &&
+ (M.currentUser.isAdmin || ch.createdBy === M.currentUser.id);
+ const deleteBtn = canDelete ? `` : "";
+
div.innerHTML = `
${listenersHtml}
`;
- div.querySelector(".channel-header").onclick = () => M.switchChannel(ch.id);
+ const headerEl = div.querySelector(".channel-header");
+ headerEl.querySelector(".channel-name").onclick = () => M.switchChannel(ch.id);
+ const delBtn = headerEl.querySelector(".btn-delete-channel");
+ if (delBtn) {
+ delBtn.onclick = (e) => {
+ e.stopPropagation();
+ M.deleteChannel(ch.id);
+ };
+ }
container.appendChild(div);
}
};
diff --git a/public/styles.css b/public/styles.css
index 85d29af..4dc1675 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -29,6 +29,9 @@ h3 { font-size: 0.8rem; color: #666; margin-bottom: 0.3rem; text-transform: uppe
#channels-list .channel-header:hover { background: #222; }
#channels-list .channel-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem; }
#channels-list .listener-count { font-size: 0.65rem; color: #666; flex-shrink: 0; margin-left: 0.3rem; }
+#channels-list .btn-delete-channel { background: none; border: none; color: #666; font-size: 0.9rem; cursor: pointer; padding: 0 0.2rem; line-height: 1; opacity: 0; transition: opacity 0.15s; }
+#channels-list .channel-header:hover .btn-delete-channel { opacity: 1; }
+#channels-list .btn-delete-channel:hover { color: #e44; }
#channels-list .channel-listeners { display: flex; flex-direction: column; margin-left: 0.5rem; border-left: 1px solid #333; padding-left: 0.3rem; }
#channels-list .listener { font-size: 0.65rem; color: #aaa; padding: 0.05rem 0; position: relative; }
#channels-list .listener::before { content: ""; position: absolute; left: -0.3rem; top: 50%; width: 0.2rem; height: 1px; background: #333; }
diff --git a/server.ts b/server.ts
index 156aab7..0d56eed 100644
--- a/server.ts
+++ b/server.ts
@@ -475,6 +475,18 @@ serve({
if (!user.is_admin && channel.createdBy !== user.id) {
return Response.json({ error: "Access denied" }, { status: 403 });
}
+
+ // Move connected clients to default channel before deleting
+ const defaultChannel = [...channels.values()].find(c => c.isDefault);
+ if (defaultChannel && channel.clients.size > 0) {
+ for (const ws of channel.clients) {
+ channel.removeClient(ws);
+ ws.data.channelId = defaultChannel.id;
+ defaultChannel.addClient(ws);
+ ws.send(JSON.stringify({ type: "switched", channelId: defaultChannel.id }));
+ }
+ }
+
channels.delete(channelId);
deleteChannelFromDb(channelId);
broadcastChannelList();