diff --git a/docs/changelog/index.md b/docs/changelog/index.md index a15eed2..7fac42f 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -4,6 +4,7 @@ Release history for EchoHub. ## Releases +- [v0.2.3](v0.2.3.md) - Moderation, Private Channels & UI Overhaul - [v0.2.2](v0.2.2.md) - Startup & Shutdown Fixes - [v0.2.1](v0.2.1.md) - Shutdown & CI Fixes - [v0.2.0](v0.2.0.md) - IRC Gateway diff --git a/docs/changelog/toc.yml b/docs/changelog/toc.yml index da8e733..addf516 100644 --- a/docs/changelog/toc.yml +++ b/docs/changelog/toc.yml @@ -1,5 +1,7 @@ - name: Overview href: index.md +- name: v0.2.3 + href: v0.2.3.md - name: v0.2.2 href: v0.2.2.md - name: v0.2.1 diff --git a/docs/changelog/v0.2.3.md b/docs/changelog/v0.2.3.md new file mode 100644 index 0000000..f4a4986 --- /dev/null +++ b/docs/changelog/v0.2.3.md @@ -0,0 +1,55 @@ +# v0.2.3 - Moderation, Private Channels & UI Overhaul + +## Features + +### Moderation System +- Added server roles: Owner, Admin, Mod, Member — first registered user is automatically Owner +- New `/kick`, `/ban`, `/unban`, `/mute`, `/unmute`, `/role`, `/nuke` commands for moderators and admins +- `ModerationController` with full REST API for role assignment, kicks, bans, mutes, message deletion, and channel nuking +- Mutes support optional duration (auto-expire) and blocked users cannot log in +- Role claim included in JWT tokens; role badges shown in the online users panel + +### Private Channels +- Channels can be created as public or private via a checkbox in the Create Channel dialog +- Public channels are visible to all users; private channels only appear for members who joined them +- Persistent channel membership tracked in the database (`ChannelMembership` table) +- `GET /api/channels` returns the combined list: public channels + user's joined private channels +- Channel creators are automatically added as members + +### Online Users Panel +- Collapsible right-side panel showing online users in the current channel (toggle with F2) +- Users displayed with status indicators, role badges, and their custom nickname colors +- Panel updates on join, leave, and status change events + +### @mention Highlighting +- `@username` text rendered in orange accent color in all messages +- Messages mentioning the current user get a full-line amber background highlight +- Works across multi-line messages and continuation lines + +### ASCII Art Improvements +- Half-block character rendering (`▀`/`█`) with separate foreground + background colors for 2x vertical resolution +- Switched from ANSI escape codes to printable color tags (`{F:RRGGBB}`, `{B:RRGGBB}`, `{X}`) — no control bytes in stored content +- Optional size parameter for `/send` command: `-s` (40x40), `-m` (80x80, default), `-l` (120x120) +- IRC gateway converts color tags back to ANSI for IRC client compatibility + +### Client UI +- Version number shown in the status bar +- Custom colored rendering for channel list (active indicator, unread count badges) +- Avatar upload field added to the profile edit dialog (file path or URL) +- Update check notification on connect — shows a system message if a newer GitHub release exists +- Chat messages no longer show selection/focus highlight +- Exit shortcut changed from Ctrl+C to Alt+Q — frees Ctrl+C for copy +- Default history increased from 50 to 100 messages on channel join + +## Fixes + +- Fixed `#general` channel not visible after adding the `IsPublic` column — migration default changed to `true` and startup service ensures it +- Fixed channels disappearing when creating a new channel — replaced full re-fetch with incremental updates +- Wired `OnChannelUpdated` SignalR event so new public channels appear for all connected users in real time +- Fixed color tag parser using wrong regex group numbers (6,7,8 instead of 1,2,3) — new ASCII art was rendering without colors + +## Infrastructure + +- Startup `DataMigrationService` automatically converts old ANSI-format messages to the new color tag format on server boot, logging the count of migrated records +- Three new EF Core migrations: `AddModerationRoles`, `AddChannelIsPublic`, `AddChannelMembership` +- `ChannelMembership` table with cascade delete on both channel and user removal diff --git a/src/Directory.Build.props b/src/Directory.Build.props index f79de17..d137f55 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.2 + 0.2.3 true $(NoWarn);CS1591 diff --git a/src/EchoHub.Client/UI/MainWindow.cs b/src/EchoHub.Client/UI/MainWindow.cs index ed7c9f5..0d70ebe 100644 --- a/src/EchoHub.Client/UI/MainWindow.cs +++ b/src/EchoHub.Client/UI/MainWindow.cs @@ -40,7 +40,7 @@ public sealed class MainWindow : Runnable // Cached Key constants — compare via .KeyCode to avoid Key.Equals (which also checks Handled) private static readonly Key EnterKey = Key.Enter; private static readonly Key NewlineKey = Key.N.WithCtrl; - private static readonly Key CtrlCKey = Key.C.WithCtrl; + private static readonly Key AltQKey = Key.Q.WithAlt; private static readonly Key TabKey = Key.Tab; // Available slash commands for Tab autocomplete @@ -240,7 +240,7 @@ public sealed class MainWindow : Runnable _messageList.ViewportChanged += (_, _) => OnChatViewportChanged(); _chatFrame.ViewportChanged += (_, _) => OnChatViewportChanged(); - // Window-level key handling for Ctrl+C (quit), F2 (toggle users panel) + // Window-level key handling for Alt+Q (quit), F2 (toggle users panel) KeyDown += OnWindowKeyDown; } @@ -379,7 +379,7 @@ public sealed class MainWindow : Runnable } e.Handled = true; } - else if (e.KeyCode == CtrlCKey.KeyCode) + else if (e.KeyCode == AltQKey.KeyCode) { _app.RequestStop(); e.Handled = true; @@ -432,7 +432,7 @@ public sealed class MainWindow : Runnable private void OnWindowKeyDown(object? sender, Key e) { - if (e.KeyCode == CtrlCKey.KeyCode) + if (e.KeyCode == AltQKey.KeyCode) { _app.RequestStop(); e.Handled = true; diff --git a/src/EchoHub.Core/Constants/HubConstants.cs b/src/EchoHub.Core/Constants/HubConstants.cs index 5a67cf0..2b980c3 100644 --- a/src/EchoHub.Core/Constants/HubConstants.cs +++ b/src/EchoHub.Core/Constants/HubConstants.cs @@ -4,7 +4,7 @@ public static class HubConstants { public const string ChatHubPath = "/hubs/chat"; public const string DefaultChannel = "general"; - public const int DefaultHistoryCount = 50; + public const int DefaultHistoryCount = 100; public const int MaxMessageLength = 2000; public const int MaxFileSizeBytes = 10 * 1024 * 1024; // 10 MB public const int MaxAvatarSizeBytes = 2 * 1024 * 1024; // 2 MB