feat: Allow admin to create new text channels

This commit is contained in:
2026-03-30 01:25:56 +02:00
parent 109402cdd6
commit 83694570e3
24 changed files with 563 additions and 64 deletions

View File

@@ -1,35 +1,18 @@
import { createReducer, on } from '@ngrx/store';
import {
Room,
RoomSettings,
Channel
RoomSettings
} from '../../shared-kernel';
import { type ServerInfo } from '../../domains/server-directory';
import { RoomsActions } from './rooms.actions';
import { defaultChannels } from './room-channels.defaults';
import {
isChannelNameTaken,
normalizeChannelName,
normalizeRoomChannels
} from './room-channels.rules';
import { pruneRoomMembers } from './room-members.helpers';
/** Default channels for a new server */
export function defaultChannels(): Channel[] {
return [
{ id: 'general',
name: 'general',
type: 'text',
position: 0 },
{ id: 'random',
name: 'random',
type: 'text',
position: 1 },
{ id: 'vc-general',
name: 'General',
type: 'voice',
position: 0 },
{ id: 'vc-afk',
name: 'AFK',
type: 'voice',
position: 1 }
];
}
/** Deduplicate rooms by id, keeping the last occurrence */
function deduplicateRooms(rooms: Room[]): Room[] {
const seen = new Map<string, Room>();
@@ -46,11 +29,23 @@ function enrichRoom(room: Room): Room {
return {
...room,
hasPassword: typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password,
channels: room.channels || defaultChannels(),
channels: normalizeRoomChannels(room.channels) || defaultChannels(),
members: pruneRoomMembers(room.members || [])
};
}
function resolveActiveTextChannelId(channels: Room['channels'], currentActiveChannelId: string): string {
const textChannels = (channels ?? []).filter((channel) => channel.type === 'text');
return textChannels.some((channel) => channel.id === currentActiveChannelId)
? currentActiveChannelId
: (textChannels[0]?.id ?? 'general');
}
function getDefaultTextChannelId(room: Room): string {
return resolveActiveTextChannelId(enrichRoom(room).channels, 'general');
}
/** Upsert a room into a saved-rooms list (add or replace by id) */
function upsertRoom(savedRooms: Room[], room: Room): Room[] {
const normalizedRoom = enrichRoom(room);
@@ -169,7 +164,7 @@ export const roomsReducer = createReducer(
isSignalServerReconnecting: false,
signalServerCompatibilityError: null,
isConnected: true,
activeChannelId: 'general'
activeChannelId: getDefaultTextChannelId(enriched)
};
}),
@@ -198,7 +193,7 @@ export const roomsReducer = createReducer(
isSignalServerReconnecting: false,
signalServerCompatibilityError: null,
isConnected: true,
activeChannelId: 'general'
activeChannelId: getDefaultTextChannelId(enriched)
};
}),
@@ -242,7 +237,7 @@ export const roomsReducer = createReducer(
isConnecting: false,
signalServerCompatibilityError: null,
isConnected: true,
activeChannelId: 'general'
activeChannelId: getDefaultTextChannelId(enriched)
};
}),
@@ -317,7 +312,8 @@ export const roomsReducer = createReducer(
savedRooms: upsertRoom(state.savedRooms, room),
isSignalServerReconnecting: false,
signalServerCompatibilityError: null,
isConnected: true
isConnected: true,
activeChannelId: getDefaultTextChannelId(room)
})),
// Clear current room
@@ -375,7 +371,8 @@ export const roomsReducer = createReducer(
return {
...state,
currentRoom: updatedRoom,
savedRooms: upsertRoom(state.savedRooms, updatedRoom)
savedRooms: upsertRoom(state.savedRooms, updatedRoom),
activeChannelId: resolveActiveTextChannelId(updatedRoom.channels, state.activeChannelId)
};
}),
@@ -412,14 +409,22 @@ export const roomsReducer = createReducer(
return state;
const existing = state.currentRoom.channels || defaultChannels();
const updatedChannels = [...existing, channel];
const normalizedName = normalizeChannelName(channel.name);
if (!normalizedName || existing.some((entry) => entry.id === channel.id) || isChannelNameTaken(existing, normalizedName)) {
return state;
}
const updatedChannels = [...existing, { ...channel,
name: normalizedName }];
const updatedRoom = { ...state.currentRoom,
channels: updatedChannels };
return {
...state,
currentRoom: updatedRoom,
savedRooms: upsertRoom(state.savedRooms, updatedRoom)
savedRooms: upsertRoom(state.savedRooms, updatedRoom),
activeChannelId: resolveActiveTextChannelId(updatedRoom.channels, state.activeChannelId)
};
}),
@@ -436,7 +441,7 @@ export const roomsReducer = createReducer(
...state,
currentRoom: updatedRoom,
savedRooms: upsertRoom(state.savedRooms, updatedRoom),
activeChannelId: state.activeChannelId === channelId ? 'general' : state.activeChannelId
activeChannelId: resolveActiveTextChannelId(updatedRoom.channels, state.activeChannelId)
};
}),
@@ -445,8 +450,14 @@ export const roomsReducer = createReducer(
return state;
const existing = state.currentRoom.channels || defaultChannels();
const normalizedName = normalizeChannelName(name);
if (!normalizedName || isChannelNameTaken(existing, normalizedName, channelId)) {
return state;
}
const updatedChannels = existing.map(channel => channel.id === channelId ? { ...channel,
name } : channel);
name: normalizedName } : channel);
const updatedRoom = { ...state.currentRoom,
channels: updatedChannels };