Add channel and permission management, sounds, themes and the public server list
- Channels: create, edit, delete, link and move (drag and drop for channels and people), right-click on free space in the channel list - Permission editor with inherited rules, allow/deny per permission, groups and members; menus grey out actions the server does not allow - Notification sounds for connection, channel, chat, mute and push-to-talk events; custom files and TeamSpeak 3 style sound packs (settings.ini), stored in IndexedDB - Color schemes: Dark, Light, Midnight, Frost and Windows 95 - Public server list with live UDP ping, fetched through Chromium's network stack - Kick, ban and connection-lost card with Reconnect - Right-click menus for text fields, links, images and message authors - Formatting toolbar in the message box, shared with the description editor - Resizable channel list, menu headers, registration from the self menu only - Fix: client-side pacing now mirrors Murmur's whole-second leaky bucket exactly, so quick actions are no longer dropped silently Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+32
-2
@@ -2,13 +2,41 @@ import { session } from './session.svelte.ts';
|
||||
import { ui } from './ui.svelte.ts';
|
||||
import { store } from './settings.svelte.ts';
|
||||
import { voice, userKey } from './audio/voice.svelte.ts';
|
||||
import { sounds } from './audio/sounds.svelte.ts';
|
||||
import type { MenuItem } from './menu.svelte.ts';
|
||||
import type { Channel, User } from '../core/client.ts';
|
||||
import { PERM, type Channel, type User } from '../core/client.ts';
|
||||
|
||||
// Right-click menu for a channel in the tree
|
||||
export function channelMenu(c: Channel): MenuItem[] {
|
||||
const here = session.self?.channelId === c.id;
|
||||
const mine = session.self?.channelId ?? 0;
|
||||
// Greyed out only when the server said no; unknown permissions stay enabled
|
||||
const no = (bit: number) => session.can(c.id, bit) === false;
|
||||
const linked = c.links.has(mine);
|
||||
const manage: MenuItem[] = [
|
||||
{ separator: true, label: '' },
|
||||
{ label: 'Create channel...', disabled: no(PERM.MakeChannel) && no(PERM.MakeTempChannel), action: () => (ui.channelDialog = { mode: 'create', parent: c.id }) },
|
||||
{ label: 'Edit channel...', disabled: no(PERM.Write), action: () => (ui.channelDialog = { mode: 'edit', id: c.id }) },
|
||||
{ label: 'Permissions...', disabled: no(PERM.Write), action: () => (ui.channelDialog = { mode: 'edit', id: c.id, tab: 'permissions' }) }
|
||||
];
|
||||
if (!here) {
|
||||
manage.push(linked
|
||||
? { label: 'Unlink from my channel', disabled: no(PERM.LinkChannel), action: () => session.client?.setLinks(c.id, [], [mine]) }
|
||||
: { label: 'Link to my channel', disabled: no(PERM.LinkChannel), action: () => session.client?.setLinks(c.id, [mine], []) });
|
||||
}
|
||||
if (c.links.size) manage.push({ label: 'Unlink all', disabled: no(PERM.LinkChannel), action: () => session.client?.setLinks(c.id, [], [...c.links]) });
|
||||
if (c.id !== 0) {
|
||||
manage.push({
|
||||
label: 'Delete channel...', danger: true, disabled: no(PERM.Write),
|
||||
action: () => (ui.prompt = {
|
||||
title: `Delete ${c.name}?`, label: '', confirm: 'Delete channel', danger: true, input: 'none',
|
||||
text: 'The channel, its subchannels and their permissions are removed for everyone. People inside are moved out.',
|
||||
onsubmit: () => { session.client?.removeChannel(c.id); sounds.play('channelDeleted'); }
|
||||
})
|
||||
});
|
||||
}
|
||||
return [
|
||||
{ header: true, label: c.name || 'Root' },
|
||||
{ label: here ? 'You are here' : 'Join channel', action: () => session.joinChannel(c.id), disabled: here || !c.canEnter },
|
||||
{ label: 'Open chat', action: () => { session.showChannel(c.id, 'chat'); ui.panelOpen = true; } },
|
||||
{ label: 'Show description', action: () => { session.showChannel(c.id, 'info'); ui.panelOpen = true; } },
|
||||
@@ -17,7 +45,8 @@ export function channelMenu(c: Channel): MenuItem[] {
|
||||
{ label: 'Expand all', action: () => session.setAllExpanded(true) },
|
||||
{ label: 'Collapse all', action: () => session.setAllExpanded(false) },
|
||||
{ separator: true, label: '' },
|
||||
{ label: 'Copy channel name', action: () => navigator.clipboard.writeText(c.name || 'Root') }
|
||||
{ label: 'Copy channel name', action: () => navigator.clipboard.writeText(c.name || 'Root') },
|
||||
...manage
|
||||
];
|
||||
}
|
||||
|
||||
@@ -27,6 +56,7 @@ export function userMenu(u: User): MenuItem[] {
|
||||
const self = session.self;
|
||||
const isSelf = u.session === self?.session;
|
||||
const items: MenuItem[] = [
|
||||
{ header: true, label: store.displayName(u) },
|
||||
{ label: 'View profile', action: () => { session.showUser(u.session); ui.panelOpen = true; } }
|
||||
];
|
||||
if (isSelf) {
|
||||
|
||||
Reference in New Issue
Block a user