Add option to disable markdown formatting

Closes #741
This commit is contained in:
Tyrrrz 2023-02-15 22:55:17 +02:00
parent c405121c0b
commit 20a952aec5
12 changed files with 87 additions and 42 deletions

View file

@ -80,6 +80,12 @@ public abstract class ExportCommandBase : TokenCommandBase
)] )]
public int ParallelLimit { get; init; } = 1; public int ParallelLimit { get; init; } = 1;
[CommandOption(
"markdown",
Description = "Process markdown, mentions, and other special tokens."
)]
public bool ShouldFormatMarkdown { get; init; } = true;
[CommandOption( [CommandOption(
"media", "media",
Description = "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)." Description = "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)."
@ -171,6 +177,7 @@ public abstract class ExportCommandBase : TokenCommandBase
Before, Before,
PartitionLimit, PartitionLimit,
MessageFilter, MessageFilter,
ShouldFormatMarkdown,
ShouldDownloadAssets, ShouldDownloadAssets,
ShouldReuseAssets, ShouldReuseAssets,
DateFormat DateFormat

View file

@ -18,10 +18,12 @@ internal partial class CsvMessageWriter : MessageWriter
_writer = new StreamWriter(stream); _writer = new StreamWriter(stream);
} }
private ValueTask<string> FormatMarkdownAsync( private async ValueTask<string> FormatMarkdownAsync(
string markdown, string markdown,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
PlainTextMarkdownVisitor.FormatAsync(Context, markdown, cancellationToken); Context.Request.ShouldFormatMarkdown
? await PlainTextMarkdownVisitor.FormatAsync(Context, markdown, cancellationToken)
: markdown;
public override async ValueTask WritePreambleAsync(CancellationToken cancellationToken = default) => public override async ValueTask WritePreambleAsync(CancellationToken cancellationToken = default) =>
await _writer.WriteLineAsync("AuthorID,Author,Date,Content,Attachments,Reactions"); await _writer.WriteLineAsync("AuthorID,Author,Date,Content,Attachments,Reactions");

View file

@ -19,6 +19,7 @@ public partial record ExportRequest(
Snowflake? Before, Snowflake? Before,
PartitionLimit PartitionLimit, PartitionLimit PartitionLimit,
MessageFilter MessageFilter, MessageFilter MessageFilter,
bool ShouldFormatMarkdown,
bool ShouldDownloadAssets, bool ShouldDownloadAssets,
bool ShouldReuseAssets, bool ShouldReuseAssets,
string DateFormat) string DateFormat)

View file

@ -75,7 +75,7 @@ internal class HtmlMessageWriter : MessageWriter
Minify( Minify(
await new PreambleTemplate await new PreambleTemplate
{ {
ExportContext = Context, Context = Context,
ThemeName = _themeName ThemeName = _themeName
}.RenderAsync(cancellationToken) }.RenderAsync(cancellationToken)
) )
@ -90,7 +90,7 @@ internal class HtmlMessageWriter : MessageWriter
Minify( Minify(
await new MessageGroupTemplate await new MessageGroupTemplate
{ {
ExportContext = Context, Context = Context,
Messages = messages Messages = messages
}.RenderAsync(cancellationToken) }.RenderAsync(cancellationToken)
) )

View file

@ -28,10 +28,12 @@ internal class JsonMessageWriter : MessageWriter
}); });
} }
private ValueTask<string> FormatMarkdownAsync( private async ValueTask<string> FormatMarkdownAsync(
string markdown, string markdown,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
PlainTextMarkdownVisitor.FormatAsync(Context, markdown, cancellationToken); Context.Request.ShouldFormatMarkdown
? await PlainTextMarkdownVisitor.FormatAsync(Context, markdown, cancellationToken)
: markdown;
private async ValueTask WriteAttachmentAsync( private async ValueTask WriteAttachmentAsync(
Attachment attachment, Attachment attachment,

View file

@ -10,40 +10,44 @@
@inherits RazorBlade.HtmlTemplate @inherits RazorBlade.HtmlTemplate
@functions { @functions {
public required ExportContext ExportContext { get; init; } public required ExportContext Context { get; init; }
public required IReadOnlyList<Message> Messages { get; init; } public required IReadOnlyList<Message> Messages { get; init; }
} }
@{ @{
ValueTask<string> ResolveAssetUrlAsync(string url) => ValueTask<string> ResolveAssetUrlAsync(string url) =>
ExportContext.ResolveAssetUrlAsync(url, CancellationToken); Context.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset instant) => string FormatDate(DateTimeOffset instant) =>
ExportContext.FormatDate(instant); Context.FormatDate(instant);
ValueTask<string> FormatMarkdownAsync(string markdown) => async ValueTask<string> FormatMarkdownAsync(string markdown) =>
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown, true, CancellationToken); Context.Request.ShouldFormatMarkdown
? await HtmlMarkdownVisitor.FormatAsync(Context, markdown, true, CancellationToken)
: markdown;
ValueTask<string> FormatEmbedMarkdownAsync(string markdown) => async ValueTask<string> FormatEmbedMarkdownAsync(string markdown) =>
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown, false, CancellationToken); Context.Request.ShouldFormatMarkdown
? await HtmlMarkdownVisitor.FormatAsync(Context, markdown, false, CancellationToken)
: markdown;
var firstMessage = Messages.First(); var firstMessage = Messages.First();
var userMember = ExportContext.TryGetMember(firstMessage.Author.Id); var userMember = Context.TryGetMember(firstMessage.Author.Id);
var userColor = ExportContext.TryGetUserColor(firstMessage.Author.Id); var userColor = Context.TryGetUserColor(firstMessage.Author.Id);
var userNick = firstMessage.Author.IsBot var userNick = firstMessage.Author.IsBot
? firstMessage.Author.Name ? firstMessage.Author.Name
: userMember?.Nick ?? firstMessage.Author.Name; : userMember?.Nick ?? firstMessage.Author.Name;
var referencedUserMember = firstMessage.ReferencedMessage is not null var referencedUserMember = firstMessage.ReferencedMessage is not null
? ExportContext.TryGetMember(firstMessage.ReferencedMessage.Author.Id) ? Context.TryGetMember(firstMessage.ReferencedMessage.Author.Id)
: null; : null;
var referencedUserColor = firstMessage.ReferencedMessage is not null var referencedUserColor = firstMessage.ReferencedMessage is not null
? ExportContext.TryGetUserColor(firstMessage.ReferencedMessage.Author.Id) ? Context.TryGetUserColor(firstMessage.ReferencedMessage.Author.Id)
: null; : null;
var referencedUserNick = firstMessage.ReferencedMessage is not null var referencedUserNick = firstMessage.ReferencedMessage is not null
@ -65,8 +69,8 @@
{ {
// System notifications are grouped even if the message author is different. // System notifications are grouped even if the message author is different.
// That's why we have to update the user values with the author of the current message. // That's why we have to update the user values with the author of the current message.
userMember = ExportContext.TryGetMember(message.Author.Id); userMember = Context.TryGetMember(message.Author.Id);
userColor = ExportContext.TryGetUserColor(message.Author.Id); userColor = Context.TryGetUserColor(message.Author.Id);
userNick = message.Author.IsBot userNick = message.Author.IsBot
? message.Author.Name ? message.Author.Name
: userMember?.Nick ?? message.Author.Name; : userMember?.Nick ?? message.Author.Name;
@ -323,7 +327,7 @@
foreach (var inviteCode in inviteCodes) foreach (var inviteCode in inviteCodes)
{ {
var invite = await ExportContext.Discord.TryGetGuildInviteAsync(inviteCode, CancellationToken); var invite = await Context.Discord.TryGetGuildInviteAsync(inviteCode, CancellationToken);
if (invite is null) if (invite is null)
{ {
continue; continue;

View file

@ -18,10 +18,12 @@ internal class PlainTextMessageWriter : MessageWriter
_writer = new StreamWriter(stream); _writer = new StreamWriter(stream);
} }
private ValueTask<string> FormatMarkdownAsync( private async ValueTask<string> FormatMarkdownAsync(
string markdown, string markdown,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
PlainTextMarkdownVisitor.FormatAsync(Context, markdown, cancellationToken); Context.Request.ShouldFormatMarkdown
? await PlainTextMarkdownVisitor.FormatAsync(Context, markdown, cancellationToken)
: markdown;
private async ValueTask WriteMessageHeaderAsync(Message message) private async ValueTask WriteMessageHeaderAsync(Message message)
{ {

View file

@ -4,7 +4,7 @@
@inherits RazorBlade.HtmlTemplate @inherits RazorBlade.HtmlTemplate
@functions { @functions {
public required ExportContext ExportContext { get; init; } public required ExportContext Context { get; init; }
public required string ThemeName { get; init; } public required string ThemeName { get; init; }
} }
@ -19,20 +19,22 @@
$"https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/whitney-{weight}.woff"; $"https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/whitney-{weight}.woff";
ValueTask<string> ResolveAssetUrlAsync(string url) => ValueTask<string> ResolveAssetUrlAsync(string url) =>
ExportContext.ResolveAssetUrlAsync(url, CancellationToken); Context.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset instant) => string FormatDate(DateTimeOffset instant) =>
ExportContext.FormatDate(instant); Context.FormatDate(instant);
ValueTask<string> FormatMarkdownAsync(string markdown) => async ValueTask<string> FormatMarkdownAsync(string markdown) =>
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown, true, CancellationToken); Context.Request.ShouldFormatMarkdown
? await HtmlMarkdownVisitor.FormatAsync(Context, markdown, true, CancellationToken)
: markdown;
} }
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>@ExportContext.Request.Guild.Name - @ExportContext.Request.Channel.Name</title> <title>@Context.Request.Guild.Name - @Context.Request.Channel.Name</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
@ -914,31 +916,31 @@
<div class="preamble"> <div class="preamble">
<div class="preamble__guild-icon-container"> <div class="preamble__guild-icon-container">
<img class="preamble__guild-icon" src="@await ResolveAssetUrlAsync(ExportContext.Request.Channel.IconUrl ?? ExportContext.Request.Guild.IconUrl)" alt="Guild icon" loading="lazy"> <img class="preamble__guild-icon" src="@await ResolveAssetUrlAsync(Context.Request.Channel.IconUrl ?? Context.Request.Guild.IconUrl)" alt="Guild icon" loading="lazy">
</div> </div>
<div class="preamble__entries-container"> <div class="preamble__entries-container">
<div class="preamble__entry">@ExportContext.Request.Guild.Name</div> <div class="preamble__entry">@Context.Request.Guild.Name</div>
<div class="preamble__entry">@ExportContext.Request.Channel.Category.Name / @ExportContext.Request.Channel.Name</div> <div class="preamble__entry">@Context.Request.Channel.Category.Name / @Context.Request.Channel.Name</div>
@if (!string.IsNullOrWhiteSpace(ExportContext.Request.Channel.Topic)) @if (!string.IsNullOrWhiteSpace(Context.Request.Channel.Topic))
{ {
<div class="preamble__entry preamble__entry--small">@Html.Raw(await FormatMarkdownAsync(ExportContext.Request.Channel.Topic))</div> <div class="preamble__entry preamble__entry--small">@Html.Raw(await FormatMarkdownAsync(Context.Request.Channel.Topic))</div>
} }
@if (ExportContext.Request.After is not null || ExportContext.Request.Before is not null) @if (Context.Request.After is not null || Context.Request.Before is not null)
{ {
<div class="preamble__entry preamble__entry--small"> <div class="preamble__entry preamble__entry--small">
@if (ExportContext.Request.After is not null && ExportContext.Request.Before is not null) @if (Context.Request.After is not null && Context.Request.Before is not null)
{ {
@($"Between {FormatDate(ExportContext.Request.After.Value.ToDate())} and {FormatDate(ExportContext.Request.Before.Value.ToDate())}") @($"Between {FormatDate(Context.Request.After.Value.ToDate())} and {FormatDate(Context.Request.Before.Value.ToDate())}")
} }
else if (ExportContext.Request.After is not null) else if (Context.Request.After is not null)
{ {
@($"After {FormatDate(ExportContext.Request.After.Value.ToDate())}") @($"After {FormatDate(Context.Request.After.Value.ToDate())}")
} }
else if (ExportContext.Request.Before is not null) else if (Context.Request.Before is not null)
{ {
@($"Before {FormatDate(ExportContext.Request.Before.Value.ToDate())}") @($"Before {FormatDate(Context.Request.Before.Value.ToDate())}")
} }
</div> </div>
} }

View file

@ -31,6 +31,8 @@ public partial class SettingsService : SettingsManager
public string? LastMessageFilterValue { get; set; } public string? LastMessageFilterValue { get; set; }
public bool LastShouldFormatMarkdown { get; set; } = true;
public bool LastShouldDownloadAssets { get; set; } public bool LastShouldDownloadAssets { get; set; }
public SettingsService() public SettingsService()

View file

@ -191,6 +191,7 @@ public class DashboardViewModel : PropertyChangedBase
dialog.Before?.Pipe(Snowflake.FromDate), dialog.Before?.Pipe(Snowflake.FromDate),
dialog.PartitionLimit, dialog.PartitionLimit,
dialog.MessageFilter, dialog.MessageFilter,
dialog.ShouldFormatMarkdown,
dialog.ShouldDownloadAssets, dialog.ShouldDownloadAssets,
_settingsService.ShouldReuseAssets, _settingsService.ShouldReuseAssets,
_settingsService.DateFormat _settingsService.DateFormat

View file

@ -59,6 +59,8 @@ public class ExportSetupViewModel : DialogScreen
? MessageFilter.Parse(MessageFilterValue) ? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null; : MessageFilter.Null;
public bool ShouldFormatMarkdown { get; set; }
public bool ShouldDownloadAssets { get; set; } public bool ShouldDownloadAssets { get; set; }
public bool IsAdvancedSectionDisplayed { get; set; } public bool IsAdvancedSectionDisplayed { get; set; }
@ -72,6 +74,7 @@ public class ExportSetupViewModel : DialogScreen
SelectedFormat = _settingsService.LastExportFormat; SelectedFormat = _settingsService.LastExportFormat;
PartitionLimitValue = _settingsService.LastPartitionLimitValue; PartitionLimitValue = _settingsService.LastPartitionLimitValue;
MessageFilterValue = _settingsService.LastMessageFilterValue; MessageFilterValue = _settingsService.LastMessageFilterValue;
ShouldFormatMarkdown = _settingsService.LastShouldFormatMarkdown;
ShouldDownloadAssets = _settingsService.LastShouldDownloadAssets; ShouldDownloadAssets = _settingsService.LastShouldDownloadAssets;
// Show the "advanced options" section by default if any // Show the "advanced options" section by default if any
@ -129,6 +132,7 @@ public class ExportSetupViewModel : DialogScreen
_settingsService.LastExportFormat = SelectedFormat; _settingsService.LastExportFormat = SelectedFormat;
_settingsService.LastPartitionLimitValue = PartitionLimitValue; _settingsService.LastPartitionLimitValue = PartitionLimitValue;
_settingsService.LastMessageFilterValue = MessageFilterValue; _settingsService.LastMessageFilterValue = MessageFilterValue;
_settingsService.LastShouldFormatMarkdown = ShouldFormatMarkdown;
_settingsService.LastShouldDownloadAssets = ShouldDownloadAssets; _settingsService.LastShouldDownloadAssets = ShouldDownloadAssets;
Close(true); Close(true);

View file

@ -230,10 +230,28 @@
materialDesign:HintAssist.IsFloating="True" materialDesign:HintAssist.IsFloating="True"
Style="{DynamicResource MaterialDesignOutlinedTextBox}" Style="{DynamicResource MaterialDesignOutlinedTextBox}"
Text="{Binding MessageFilterValue}" Text="{Binding MessageFilterValue}"
ToolTip="Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image')." /> ToolTip="Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image')" />
<!-- Markdown formatting -->
<Grid Margin="16,8" ToolTip="Process markdown, mentions, and other special tokens">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="Format markdown" />
<ToggleButton
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
IsChecked="{Binding ShouldFormatMarkdown}" />
</Grid>
<!-- Download assets --> <!-- Download assets -->
<Grid Margin="16,16" ToolTip="Download assets referenced by the export (user avatars, attached files, embedded images, etc.)"> <Grid Margin="16,8" ToolTip="Download assets referenced by the export (user avatars, attached files, embedded images, etc.)">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />