Add clickable mention and channel links to message content display

This commit is contained in:
Stone_Red
2026-07-16 20:54:04 +02:00
parent 019fb3d462
commit da24567681
+99 -4
View File
@@ -1,5 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.Imaging;
@@ -10,6 +12,8 @@ using Decho.ViewModels;
using EchoHub.Core.DTOs;
using EchoHub.Core.Models;
using Romzetron.Avalonia;
using System.Text.RegularExpressions;
namespace Decho.Views;
@@ -17,6 +21,7 @@ namespace Decho.Views;
public partial class MessageItemView : UserControl
{
private static readonly Regex MentionRegex = new(@"@(\w+)", RegexOptions.Compiled);
private static readonly Regex ChannelRegex = new(@"(?<!\w)#(\w+)", RegexOptions.Compiled);
private CancellationTokenSource? _loadCts;
private string? _loadedMessageId;
@@ -54,19 +59,51 @@ public partial class MessageItemView : UserControl
tb.Inlines?.Clear();
var mentionMatches = MentionRegex.Matches(content).Cast<Match>();
var channelMatches = ChannelRegex.Matches(content).Cast<Match>();
var allMatches = mentionMatches.Concat(channelMatches)
.OrderBy(m => m.Index)
.ToList();
int lastIndex = 0;
foreach (Match match in MentionRegex.Matches(content))
foreach (Match match in allMatches)
{
if (match.Index > lastIndex)
{
tb.Inlines?.Add(new Run(content[lastIndex..match.Index]));
}
tb.Inlines?.Add(new Run(match.Value)
bool isMention = match.Value[0] == '@';
var container = new InlineUIContainer
{
Foreground = new SolidColorBrush(Color.Parse("#FEE75C")),
BaselineAlignment = BaselineAlignment.Center,
};
var label = new TextBlock
{
Text = match.Value,
FontWeight = FontWeight.Bold,
});
Cursor = new Cursor(StandardCursorType.Hand),
Padding = new Thickness(0),
LineHeight = double.NaN,
};
if (isMention)
{
string username = match.Groups[1].Value;
label.Margin = new Thickness(0, 0, 0, -2);
label.Foreground = ColorPalette.Yellow05;
label.PointerPressed += (_, args) => OnMentionPointerPressed(username, args);
}
else
{
string channelName = match.Groups[1].Value;
label.Margin = new Thickness(0, 0, 0, -2);
label.Foreground = ColorPalette.Blue05;
label.PointerPressed += (_, args) => OnChannelPointerPressed(channelName, args);
}
container.Child = label;
tb.Inlines?.Add(container);
lastIndex = match.Index + match.Length;
}
@@ -77,6 +114,64 @@ public partial class MessageItemView : UserControl
}
}
private async void OnMentionPointerPressed(string username, PointerPressedEventArgs e)
{
if (DataContext is not MessageViewModel msg)
{
return;
}
MainWindowViewModel? mainVm = this.GetMainWindowViewModel();
if (mainVm is null)
{
return;
}
string serverUrl = msg.ServerUrl ?? mainVm.Chat.CurrentServerUrl;
UserProfileDto? profile = await mainVm.ConnectionService.GetUserProfileAsync(serverUrl, username);
if (profile is null)
{
return;
}
ProfileWindow dialog = new ProfileWindow(profile);
if (TopLevel.GetTopLevel(this) is Window parent)
{
await dialog.ShowDialog(parent);
}
}
private void OnChannelPointerPressed(string channelName, PointerPressedEventArgs e)
{
if (DataContext is not MessageViewModel msg)
{
return;
}
MainWindowViewModel? mainVm = this.GetMainWindowViewModel();
if (mainVm is null)
{
return;
}
string serverUrl = msg.ServerUrl ?? mainVm.Chat.CurrentServerUrl;
ServerViewModel? serverVm = mainVm.Sidebar.GetServer(serverUrl);
if (serverVm is null)
{
return;
}
ChannelViewModel? channel = serverVm.Channels.FirstOrDefault(c =>
string.Equals(c.Name, channelName, StringComparison.OrdinalIgnoreCase));
if (channel is null)
{
return;
}
mainVm.Sidebar.SelectedChannel = channel;
}
private async void OnAttachmentImageLoaded(object? sender, RoutedEventArgs e)
{
if (sender is not Image image)