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

@@ -40,6 +40,10 @@ import { VoiceActivityService, VoiceConnectionFacade } from '../../../domains/vo
import { VoiceSessionFacade, VoiceWorkspaceService } from '../../../domains/voice-session';
import { VoicePlaybackService } from '../../../domains/voice-connection/application/voice-playback.service';
import { VoiceControlsComponent } from '../../../domains/voice-session/feature/voice-controls/voice-controls.component';
import {
isChannelNameTaken,
normalizeChannelName
} from '../../../store/rooms/room-channels.rules';
import {
ContextMenuComponent,
UserAvatarComponent,
@@ -152,6 +156,7 @@ export class RoomsSidePanelComponent {
contextChannel = signal<Channel | null>(null);
renamingChannelId = signal<string | null>(null);
channelNameError = signal<string | null>(null);
showCreateChannelDialog = signal(false);
createChannelType = signal<'text' | 'voice'>('text');
@@ -243,6 +248,7 @@ export class RoomsSidePanelComponent {
const ch = this.contextChannel();
this.closeChannelMenu();
this.channelNameError.set(null);
if (ch) {
this.renamingChannelId.set(ch.id);
@@ -251,10 +257,29 @@ export class RoomsSidePanelComponent {
confirmRename(event: Event) {
const input = event.target as HTMLInputElement;
const name = input.value.trim();
const name = normalizeChannelName(input.value);
const channelId = this.renamingChannelId();
if (channelId && name) {
if (!channelId) {
return;
}
const validationError = this.getChannelNameError(name, channelId);
if (validationError) {
this.channelNameError.set(validationError);
requestAnimationFrame(() => {
input.focus();
input.select();
});
return;
}
this.channelNameError.set(null);
const currentName = this.currentRoom()?.channels?.find((channel) => channel.id === channelId)?.name;
if (currentName !== name) {
this.store.dispatch(RoomsActions.renameChannel({ channelId, name }));
}
@@ -262,6 +287,7 @@ export class RoomsSidePanelComponent {
}
cancelRename() {
this.channelNameError.set(null);
this.renamingChannelId.set(null);
}
@@ -300,14 +326,19 @@ export class RoomsSidePanelComponent {
createChannel(type: 'text' | 'voice') {
this.createChannelType.set(type);
this.newChannelName = '';
this.channelNameError.set(null);
this.showCreateChannelDialog.set(true);
}
confirmCreateChannel() {
const name = this.newChannelName.trim();
const name = normalizeChannelName(this.newChannelName);
if (!name)
const validationError = this.getChannelNameError(name);
if (validationError) {
this.channelNameError.set(validationError);
return;
}
const type = this.createChannelType();
const existing = type === 'text' ? this.textChannels() : this.voiceChannels();
@@ -319,13 +350,35 @@ export class RoomsSidePanelComponent {
};
this.store.dispatch(RoomsActions.addChannel({ channel }));
this.channelNameError.set(null);
this.showCreateChannelDialog.set(false);
}
cancelCreateChannel() {
this.channelNameError.set(null);
this.showCreateChannelDialog.set(false);
}
clearChannelNameError(): void {
if (this.channelNameError()) {
this.channelNameError.set(null);
}
}
private getChannelNameError(name: string, excludeChannelId?: string): string | null {
if (!name) {
return 'Channel name is required.';
}
const channels = this.currentRoom()?.channels ?? [];
if (isChannelNameTaken(channels, name, excludeChannelId)) {
return 'Channel names must be unique in a server.';
}
return null;
}
openUserContextMenu(evt: MouseEvent, user: User) {
evt.preventDefault();