mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 08:36:11 +02:00
feat: Add ASCII art size selection for image attachments and enhance message context menu
This commit is contained in:
@@ -40,7 +40,7 @@ public sealed partial class MainWindow : Runnable
|
||||
private readonly UserListSource _usersListSource;
|
||||
private bool _usersPanelVisible = true;
|
||||
private const int UsersPanelWidth = 22;
|
||||
private const string DefaultInputTitle = "Message │ Enter=send │ Ctrl+N=newline │ Tab=complete │ Ctrl+K=search";
|
||||
private const string DefaultInputTitle = "Message │ Enter=send │ Tab=complete │ Ctrl+K=search │ F6=pick message";
|
||||
private static readonly Key F2Key = Key.F2;
|
||||
private bool _hasStagedAttachments;
|
||||
|
||||
@@ -57,12 +57,13 @@ public sealed partial class MainWindow : Runnable
|
||||
private static readonly Key CtrlXKey = Key.X.WithCtrl;
|
||||
private static readonly Key CtrlCKey = Key.C.WithCtrl;
|
||||
private static readonly Key CtrlYKey = Key.Y.WithCtrl;
|
||||
private static readonly Key F6Key = Key.F6;
|
||||
|
||||
// Available slash commands for Tab autocomplete
|
||||
private static readonly string[] SlashCommands =
|
||||
[
|
||||
"/status", "/nick", "/color", "/theme", "/send",
|
||||
"/avatar", "/profile", "/servers", "/join", "/passwd", "/leave", "/clear", "/downloadpath",
|
||||
"/avatar", "/profile", "/servers", "/join", "/passwd", "/leave", "/clear", "/size", "/downloadpath",
|
||||
"/topic", "/users", "/kick", "/ban", "/unban",
|
||||
"/mute", "/unmute", "/role", "/nuke", "/test-sound", "/quit", "/help"
|
||||
];
|
||||
@@ -248,6 +249,7 @@ public sealed partial class MainWindow : Runnable
|
||||
_messageList.Source = new ChatListSource();
|
||||
_messageList.Accepting += OnMessageListAccepting;
|
||||
_messageList.KeyDown += OnMessageListKeyDown;
|
||||
_messageList.MouseEvent += OnMessageListMouseEvent;
|
||||
_messageList.VerticalScrollBar.Scrolled += OnMessageListVerticalScrollBarScrolled;
|
||||
_messageList.VerticalScrollBar.Visible = true;
|
||||
|
||||
@@ -330,10 +332,10 @@ public sealed partial class MainWindow : Runnable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the attachment staging indicator shown on the input frame's title.
|
||||
/// Passing an empty list restores the default hint.
|
||||
/// Updates the attachment staging indicator shown on the input frame's title, including the
|
||||
/// current ASCII-art size for images. Passing an empty list restores the default hint.
|
||||
/// </summary>
|
||||
public void SetStagedAttachments(IReadOnlyList<string> fileNames)
|
||||
public void SetStagedAttachments(IReadOnlyList<string> fileNames, string asciiSizeLabel)
|
||||
{
|
||||
_hasStagedAttachments = fileNames.Count > 0;
|
||||
if (fileNames.Count == 0)
|
||||
@@ -343,9 +345,9 @@ public sealed partial class MainWindow : Runnable
|
||||
else
|
||||
{
|
||||
var names = string.Join(", ", fileNames);
|
||||
if (names.Length > 60)
|
||||
names = names[..57] + "...";
|
||||
_inputFrame.Title = $"📎 {fileNames.Count} staged: {names} │ Enter=send │ /clear to drop";
|
||||
if (names.Length > 45)
|
||||
names = names[..42] + "...";
|
||||
_inputFrame.Title = $"📎 {fileNames.Count}: {names} │ art: {asciiSizeLabel} (/size) │ Enter=send │ /clear";
|
||||
}
|
||||
_inputFrame.SetNeedsDraw();
|
||||
}
|
||||
@@ -541,6 +543,14 @@ public sealed partial class MainWindow : Runnable
|
||||
|
||||
private void OnMessageListKeyDown(object? sender, Key e)
|
||||
{
|
||||
// F6 returns focus to the input box.
|
||||
if (e.KeyCode == F6Key.KeyCode)
|
||||
{
|
||||
_inputField.SetFocus();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.KeyCode != Key.Delete.KeyCode && e.KeyCode != Key.Backspace.KeyCode)
|
||||
return;
|
||||
|
||||
@@ -557,12 +567,104 @@ public sealed partial class MainWindow : Runnable
|
||||
|
||||
// Server enforces the real permission (own message, or Mod+ over a lower role);
|
||||
// the client just confirms intent and lets the server reject if disallowed.
|
||||
var confirm = MessageBox.Query(_app, "Delete Message",
|
||||
"Delete this message?", "Delete", "Cancel");
|
||||
ConfirmDeleteMessage(messageId);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMessageListMouseEvent(object? sender, Mouse e)
|
||||
{
|
||||
if (!e.Flags.HasFlag(MouseFlags.RightButtonClicked))
|
||||
return;
|
||||
|
||||
if (_messageList.Source is not ChatListSource source || source.Count == 0 || e.Position is not { } pos)
|
||||
return;
|
||||
|
||||
var index = _messageList.TopItem + pos.Y;
|
||||
if (index < 0 || index >= source.Count)
|
||||
return;
|
||||
|
||||
// Select the right-clicked row (so the menu acts on it and it highlights), then show the menu.
|
||||
_messageList.SelectedItem = index;
|
||||
_messageList.SetFocus();
|
||||
|
||||
var line = source.GetLine(index);
|
||||
if (line is null)
|
||||
return;
|
||||
|
||||
ShowMessageContextMenu(line, e.ScreenPosition);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and shows a right-click context menu for a message line: attachment actions,
|
||||
/// mention/profile for the sender, copy, and delete (permission enforced server-side).
|
||||
/// </summary>
|
||||
private void ShowMessageContextMenu(ChatLine line, System.Drawing.Point screenPosition)
|
||||
{
|
||||
var items = new List<View>();
|
||||
var sender = line.SenderUsername;
|
||||
|
||||
if (line.AttachmentKind is { } kind && line.AttachmentUrl is { } url && line.AttachmentFileName is { } name)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case AttachmentKind.Image:
|
||||
items.Add(new MenuItem("Save original image", "", () => OnImageSaveRequested?.Invoke(url, name), Key.Empty));
|
||||
break;
|
||||
case AttachmentKind.Audio:
|
||||
items.Add(new MenuItem("Play audio", "", () => OnAudioPlayRequested?.Invoke(url, name), Key.Empty));
|
||||
break;
|
||||
default:
|
||||
items.Add(new MenuItem("Download file", "", () => OnFileDownloadRequested?.Invoke(url, name), Key.Empty));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sender is not null)
|
||||
{
|
||||
items.Add(new MenuItem($"Mention @{sender}", "", () => MentionUser(sender), Key.Empty));
|
||||
items.Add(new MenuItem($"View {sender}'s profile", "", () => OnUserProfileRequested?.Invoke(sender), Key.Empty));
|
||||
}
|
||||
|
||||
items.Add(new MenuItem("Copy text", "", () => CopyToClipboard(line.ToString()), Key.Empty));
|
||||
|
||||
if (line.MessageId is { } messageId)
|
||||
{
|
||||
items.Add(new Line());
|
||||
items.Add(new MenuItem("Delete message", "", () => ConfirmDeleteMessage(messageId), Key.Empty));
|
||||
}
|
||||
|
||||
if (items.Count == 0)
|
||||
return;
|
||||
|
||||
var menu = new PopoverMenu(items);
|
||||
_app.Popovers?.Register(menu);
|
||||
menu.MakeVisible(screenPosition);
|
||||
}
|
||||
|
||||
private void MentionUser(string username)
|
||||
{
|
||||
_inputField.InsertText($"@{username} ");
|
||||
_inputField.SetFocus();
|
||||
}
|
||||
|
||||
private void CopyToClipboard(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
_app.Clipboard?.TrySetClipboardData(text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Copy to clipboard failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfirmDeleteMessage(Guid messageId)
|
||||
{
|
||||
var confirm = MessageBox.Query(_app, "Delete Message", "Delete this message?", "Delete", "Cancel");
|
||||
if (confirm == 0)
|
||||
OnDeleteMessageRequested?.Invoke(messageId);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMessageListVerticalScrollBarScrolled(object? sender, EventArgs<int> e)
|
||||
@@ -619,6 +721,13 @@ public sealed partial class MainWindow : Runnable
|
||||
ShowSearchDialog();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.KeyCode == F6Key.KeyCode)
|
||||
{
|
||||
// Move focus into the message list so you can select a message (arrows) and
|
||||
// delete it (Delete). F6 again returns focus here. (Esc is the app quit key.)
|
||||
FocusMessageList();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.KeyCode == CtrlVKey.KeyCode || e.KeyCode == CtrlYKey.KeyCode)
|
||||
{
|
||||
// If a file was copied in the OS file manager, the clipboard holds a file list
|
||||
@@ -1049,6 +1158,26 @@ public sealed partial class MainWindow : Runnable
|
||||
_inputField.SetFocus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves focus into the message list for selection (arrows) and deletion (Delete). Selects the
|
||||
/// most recent message when nothing is selected. No-op when the channel has no messages.
|
||||
/// </summary>
|
||||
private void FocusMessageList()
|
||||
{
|
||||
if (_messageList.Source is not ChatListSource source || source.Count == 0)
|
||||
return;
|
||||
|
||||
if (!_messageList.SelectedItem.HasValue
|
||||
|| _messageList.SelectedItem < 0
|
||||
|| _messageList.SelectedItem >= source.Count)
|
||||
{
|
||||
_messageList.SelectedItem = source.Count - 1;
|
||||
}
|
||||
|
||||
_messageList.SetFocus();
|
||||
_messageList.SetNeedsDraw();
|
||||
}
|
||||
|
||||
private void RefreshMessages()
|
||||
{
|
||||
var messages = _messageManager.GetMessages(_messageManager.CurrentChannel);
|
||||
|
||||
Reference in New Issue
Block a user