From c2ee6f047d988301040285399449939a27786a26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:15:04 +0000 Subject: [PATCH] GUI: double-clicking a child-less server auto-opens export setup Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- .../Discord/Data/Message.cs | 2 +- .../Discord/Data/MessageReference.cs | 5 +- .../Discord/Data/MessageSnapshot.cs | 29 ++++++----- .../Exporting/JsonMessageWriter.cs | 6 +-- .../Exporting/PlainTextMessageWriter.cs | 6 +-- .../Utils/Extensions/CollectionExtensions.cs | 2 +- .../Components/DashboardViewModel.cs | 48 +++++++++++++++++++ .../Views/Components/DashboardView.axaml | 1 + .../Views/Components/DashboardView.axaml.cs | 4 ++ 9 files changed, 78 insertions(+), 25 deletions(-) diff --git a/DiscordChatExporter.Core/Discord/Data/Message.cs b/DiscordChatExporter.Core/Discord/Data/Message.cs index ad568695..9b1c4f92 100644 --- a/DiscordChatExporter.Core/Discord/Data/Message.cs +++ b/DiscordChatExporter.Core/Discord/Data/Message.cs @@ -180,7 +180,7 @@ public partial record Message // Currently Discord only supports 1 snapshot per forward var forwardedMessage = json.GetPropertyOrNull("message_snapshots") ?.EnumerateArrayOrNull() - ?.Select(j => j.GetPropertyOrNull("message")) + ?.Select(j => j.GetPropertyOrNull("message")) .WhereNotNull() .Select(MessageSnapshot.Parse) .FirstOrDefault(); diff --git a/DiscordChatExporter.Core/Discord/Data/MessageReference.cs b/DiscordChatExporter.Core/Discord/Data/MessageReference.cs index 73374d6a..48c68d5f 100644 --- a/DiscordChatExporter.Core/Discord/Data/MessageReference.cs +++ b/DiscordChatExporter.Core/Discord/Data/MessageReference.cs @@ -9,14 +9,15 @@ public record MessageReference( MessageReferenceKind Kind, Snowflake? MessageId, Snowflake? ChannelId, - Snowflake? GuildId) + Snowflake? GuildId +) { public static MessageReference Parse(JsonElement json) { var kind = json.GetPropertyOrNull("type")?.GetInt32OrNull()?.Pipe(t => (MessageReferenceKind)t) ?? MessageReferenceKind.Default; - + var messageId = json.GetPropertyOrNull("message_id") ?.GetNonWhiteSpaceStringOrNull() ?.Pipe(Snowflake.Parse); diff --git a/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs b/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs index db0d1722..212502b4 100644 --- a/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs +++ b/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs @@ -15,7 +15,8 @@ public record MessageSnapshot( string Content, IReadOnlyList Attachments, IReadOnlyList Embeds, - IReadOnlyList Stickers) + IReadOnlyList Stickers +) { public static MessageSnapshot Parse(JsonElement json) { @@ -23,37 +24,35 @@ public record MessageSnapshot( json.GetPropertyOrNull("timestamp")?.GetDateTimeOffsetOrNull() ?? DateTimeOffset.MinValue; - var editedTimestamp = json - .GetPropertyOrNull("edited_timestamp") - ?.GetDateTimeOffsetOrNull(); + var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffsetOrNull(); var content = json.GetPropertyOrNull("content")?.GetStringOrNull() ?? ""; var attachments = - json - .GetPropertyOrNull("attachments") + json.GetPropertyOrNull("attachments") ?.EnumerateArrayOrNull() ?.Select(Attachment.Parse) .ToArray() ?? []; var embeds = - json - .GetPropertyOrNull("embeds") - ?.EnumerateArrayOrNull() - ?.Select(Embed.Parse) - .ToArray() + json.GetPropertyOrNull("embeds")?.EnumerateArrayOrNull()?.Select(Embed.Parse).ToArray() ?? []; var stickers = - json - .GetPropertyOrNull("sticker_items") + json.GetPropertyOrNull("sticker_items") ?.EnumerateArrayOrNull() ?.Select(Sticker.Parse) .ToArray() ?? []; - return new MessageSnapshot(timestamp, - editedTimestamp, content, attachments, embeds, stickers); + return new MessageSnapshot( + timestamp, + editedTimestamp, + content, + attachments, + embeds, + stickers + ); } } diff --git a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs index c7c2039f..6bb8cc11 100644 --- a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs @@ -352,7 +352,7 @@ internal class JsonMessageWriter(Stream stream, ExportContext context) _writer.WriteEndObject(); await _writer.FlushAsync(cancellationToken); } - + private async ValueTask WriteStickerAsync( Sticker sticker, CancellationToken cancellationToken = default @@ -553,14 +553,14 @@ internal class JsonMessageWriter(Stream stream, ExportContext context) _writer.WriteString( "timestamp", - Context.NormalizeDate(message.ForwardedMessage.Timestamp) + Context.NormalizeDate(message.ForwardedMessage.Timestamp) ); _writer.WriteString( "timestampEdited", message.ForwardedMessage.EditedTimestamp?.Pipe(Context.NormalizeDate) ); - + _writer.WriteString( "content", await FormatMarkdownAsync(message.ForwardedMessage.Content, cancellationToken) diff --git a/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs b/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs index 6ddfcc1d..7bd6e438 100644 --- a/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs @@ -239,9 +239,9 @@ internal class PlainTextMessageWriter(Stream stream, ExportContext context) ); } - await _writer.WriteLineAsync( - $"Originally sent: {Context.FormatDate(forwardedMessage.Timestamp)}" - ); + await _writer.WriteLineAsync( + $"Originally sent: {Context.FormatDate(forwardedMessage.Timestamp)}" + ); await WriteAttachmentsAsync(forwardedMessage.Attachments, cancellationToken); await WriteEmbedsAsync(forwardedMessage.Embeds, cancellationToken); diff --git a/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs index 72a8f249..abb1dd9a 100644 --- a/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs +++ b/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs @@ -24,7 +24,7 @@ public static class CollectionExtensions } } } - + extension(IEnumerable source) where T : struct { diff --git a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs index 71e9429f..68652e16 100644 --- a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs @@ -223,6 +223,54 @@ public partial class DashboardViewModel : ViewModelBase } } + [RelayCommand] + private async Task ShowSetupIfSingleChannelAsync() + { + // Wait for any in-progress channel loading to complete + if (IsBusy) + { + var tcs = new TaskCompletionSource(); + + void Handler(object? _, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(IsBusy) && !IsBusy) + tcs.TrySetResult(); + } + + PropertyChanged += Handler; + + // Re-check after subscribing to avoid missing the transition + if (!IsBusy) + { + PropertyChanged -= Handler; + } + else + { + try + { + await tcs.Task; + } + finally + { + PropertyChanged -= Handler; + } + } + } + + // Only auto-export when there is exactly one child-less (non-category) channel + if ( + AvailableChannels is not { Count: 1 } + || AvailableChannels[0].Children.Count != 0 + || AvailableChannels[0].Channel.IsCategory + ) + return; + + SelectedChannels.Clear(); + SelectedChannels.Add(AvailableChannels[0]); + + await ExportAsync(); + } + private bool CanExport() => !IsBusy && _discord is not null && SelectedGuild is not null && SelectedChannels.Any(); diff --git a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml index 53852afd..94278025 100644 --- a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml +++ b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml @@ -99,6 +99,7 @@ BorderThickness="0,0,1,0" Grid.Column="0"> SelectionChangedEventArgs args ) => DataContext.PullChannelsCommand.Execute(null); + private void AvailableGuildsListBox_OnDoubleTapped(object? sender, TappedEventArgs args) => + DataContext.ShowSetupIfSingleChannelCommand.Execute(null); + private void AvailableChannelsTreeView_OnSelectionChanged( object? sender, SelectionChangedEventArgs args