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:
2026-10-01 00:54:03 +02:00
co-authored by Claude Opus 5.5
parent aa679dddcc
commit 9a3aeb29ad
53 changed files with 2684 additions and 170 deletions
+12
View File
@@ -132,6 +132,18 @@ await wait(800);
await page.screenshot({ path: path.join(out, 'voice-settings.png') });
await page.getByRole('button', { name: 'Done', exact: true }).click();
// Every color scheme, main view with the member list
await page.locator('.panel .icon-btn[aria-label="Close"]').click().catch(() => {});
for (const t of ['light', 'midnight', 'frost', 'win95', 'dark']) {
await page.getByTitle('Settings').click();
await page.getByRole('tab', { name: 'Appearance' }).click();
await page.locator(`[data-theme-preview="${t}"]`).click();
if (t === 'win95') await page.screenshot({ path: path.join(out, 'settings-appearance.png') });
await page.getByRole('button', { name: 'Done', exact: true }).click();
await wait(400);
if (t !== 'dark') await page.screenshot({ path: path.join(out, `theme-${t}.png`) });
}
await page.setViewportSize({ width: 390, height: 844 });
await page.locator('.scrim').first().click().catch(() => {});
await wait(500);