Refactor, rename the concept of "download media" to "download assets", including related mentions

This commit is contained in:
Tyrrrz 2022-10-29 17:39:53 +03:00
parent 1131f8659d
commit 560a069c35
18 changed files with 168 additions and 120 deletions

View file

@ -36,7 +36,7 @@ public class SelfContainedSpecs : IClassFixture<TempOutputFixture>
ChannelIds = new[] { ChannelIds.SelfContainedTestCases }, ChannelIds = new[] { ChannelIds.SelfContainedTestCases },
ExportFormat = ExportFormat.HtmlDark, ExportFormat = ExportFormat.HtmlDark,
OutputPath = filePath, OutputPath = filePath,
ShouldDownloadMedia = true ShouldDownloadAssets = true
}.ExecuteAsync(new FakeConsole()); }.ExecuteAsync(new FakeConsole());
// Assert // Assert

View file

@ -76,15 +76,15 @@ public abstract class ExportCommandBase : TokenCommandBase
[CommandOption( [CommandOption(
"media", "media",
Description = "Download referenced media content." Description = "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)."
)] )]
public bool ShouldDownloadMedia { get; init; } public bool ShouldDownloadAssets { get; init; }
[CommandOption( [CommandOption(
"reuse-media", "reuse-media",
Description = "Reuse already existing media content to skip redundant downloads." Description = "Reuse previously downloaded assets to avoid redundant requests."
)] )]
public bool ShouldReuseMedia { get; init; } public bool ShouldReuseAssets { get; init; }
[CommandOption( [CommandOption(
"dateformat", "dateformat",
@ -97,9 +97,9 @@ public abstract class ExportCommandBase : TokenCommandBase
protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList<Channel> channels) protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList<Channel> channels)
{ {
// Reuse media option should only be used when the media option is set. // Reuse assets option should only be used when the download assets option is set.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/425 // https://github.com/Tyrrrz/DiscordChatExporter/issues/425
if (ShouldReuseMedia && !ShouldDownloadMedia) if (ShouldReuseAssets && !ShouldDownloadAssets)
{ {
throw new CommandException( throw new CommandException(
"Option --reuse-media cannot be used without --media." "Option --reuse-media cannot be used without --media."
@ -158,8 +158,8 @@ public abstract class ExportCommandBase : TokenCommandBase
Before, Before,
PartitionLimit, PartitionLimit,
MessageFilter, MessageFilter,
ShouldDownloadMedia, ShouldDownloadAssets,
ShouldReuseMedia, ShouldReuseAssets,
DateFormat DateFormat
); );

View file

@ -12,18 +12,18 @@ using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
internal partial class MediaDownloader internal partial class ExportAssetDownloader
{ {
private readonly string _workingDirPath; private readonly string _workingDirPath;
private readonly bool _reuseMedia; private readonly bool _reuse;
// File paths of already downloaded media // File paths of the previously downloaded assets
private readonly Dictionary<string, string> _pathCache = new(StringComparer.Ordinal); private readonly Dictionary<string, string> _pathCache = new(StringComparer.Ordinal);
public MediaDownloader(string workingDirPath, bool reuseMedia) public ExportAssetDownloader(string workingDirPath, bool reuse)
{ {
_workingDirPath = workingDirPath; _workingDirPath = workingDirPath;
_reuseMedia = reuseMedia; _reuse = reuse;
} }
public async ValueTask<string> DownloadAsync(string url, CancellationToken cancellationToken = default) public async ValueTask<string> DownloadAsync(string url, CancellationToken cancellationToken = default)
@ -35,7 +35,7 @@ internal partial class MediaDownloader
var filePath = Path.Combine(_workingDirPath, fileName); var filePath = Path.Combine(_workingDirPath, fileName);
// Reuse existing files if we're allowed to // Reuse existing files if we're allowed to
if (!_reuseMedia || !File.Exists(filePath)) if (!_reuse || !File.Exists(filePath))
{ {
Directory.CreateDirectory(_workingDirPath); Directory.CreateDirectory(_workingDirPath);
@ -76,7 +76,7 @@ internal partial class MediaDownloader
} }
} }
internal partial class MediaDownloader internal partial class ExportAssetDownloader
{ {
private static string GetUrlHash(string url) private static string GetUrlHash(string url)
{ {

View file

@ -14,7 +14,7 @@ namespace DiscordChatExporter.Core.Exporting;
internal class ExportContext internal class ExportContext
{ {
private readonly MediaDownloader _mediaDownloader; private readonly ExportAssetDownloader _assetDownloader;
public ExportRequest Request { get; } public ExportRequest Request { get; }
@ -35,7 +35,7 @@ internal class ExportContext
Channels = channels; Channels = channels;
Roles = roles; Roles = roles;
_mediaDownloader = new MediaDownloader(request.OutputMediaDirPath, request.ShouldReuseMedia); _assetDownloader = new ExportAssetDownloader(request.OutputAssetsDirPath, request.ShouldReuseAssets);
} }
public string FormatDate(DateTimeOffset date) => Request.DateFormat switch public string FormatDate(DateTimeOffset date) => Request.DateFormat switch
@ -63,14 +63,14 @@ internal class ExportContext
.FirstOrDefault(); .FirstOrDefault();
} }
public async ValueTask<string> ResolveMediaUrlAsync(string url, CancellationToken cancellationToken = default) public async ValueTask<string> ResolveAssetUrlAsync(string url, CancellationToken cancellationToken = default)
{ {
if (!Request.ShouldDownloadMedia) if (!Request.ShouldDownloadAssets)
return url; return url;
try try
{ {
var filePath = await _mediaDownloader.DownloadAsync(url, cancellationToken); var filePath = await _assetDownloader.DownloadAsync(url, cancellationToken);
// We want relative path so that the output files can be copied around without breaking. // We want relative path so that the output files can be copied around without breaking.
// Base directory path may be null if the file is stored at the root or relative to working directory. // Base directory path may be null if the file is stored at the root or relative to working directory.

View file

@ -19,8 +19,8 @@ public partial record ExportRequest(
Snowflake? Before, Snowflake? Before,
PartitionLimit PartitionLimit, PartitionLimit PartitionLimit,
MessageFilter MessageFilter, MessageFilter MessageFilter,
bool ShouldDownloadMedia, bool ShouldDownloadAssets,
bool ShouldReuseMedia, bool ShouldReuseAssets,
string DateFormat) string DateFormat)
{ {
private string? _outputBaseFilePath; private string? _outputBaseFilePath;
@ -35,7 +35,7 @@ public partial record ExportRequest(
public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath; public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath;
public string OutputMediaDirPath => $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}"; public string OutputAssetsDirPath => $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}";
} }
public partial record ExportRequest public partial record ExportRequest

View file

@ -37,7 +37,7 @@ internal partial class CsvMessageWriter : MessageWriter
buffer buffer
.AppendIfNotEmpty(',') .AppendIfNotEmpty(',')
.Append(await Context.ResolveMediaUrlAsync(attachment.Url, cancellationToken)); .Append(await Context.ResolveAssetUrlAsync(attachment.Url, cancellationToken));
} }
await _writer.WriteAsync(CsvEncode(buffer.ToString())); await _writer.WriteAsync(CsvEncode(buffer.ToString()));

View file

@ -10,13 +10,17 @@
@inherits MiniRazor.TemplateBase<MessageGroupTemplateContext> @inherits MiniRazor.TemplateBase<MessageGroupTemplateContext>
@{ @{
ValueTask<string> ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url); ValueTask<string> ResolveAssetUrlAsync(string url) =>
Model.ExportContext.ResolveAssetUrlAsync(url);
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date); string FormatDate(DateTimeOffset date) =>
Model.ExportContext.FormatDate(date);
ValueTask<string> FormatMarkdownAsync(string markdown) => Model.FormatMarkdownAsync(markdown); ValueTask<string> FormatMarkdownAsync(string markdown) =>
Model.FormatMarkdownAsync(markdown);
ValueTask<string> FormatEmbedMarkdownAsync(string markdown) => Model.FormatMarkdownAsync(markdown, false); ValueTask<string> FormatEmbedMarkdownAsync(string markdown) =>
Model.FormatMarkdownAsync(markdown, false);
var firstMessage = Model.Messages.First(); var firstMessage = Model.Messages.First();
@ -102,7 +106,7 @@
} }
// Avatar // Avatar
<img class="chatlog__avatar" src="@await ResolveUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy"> <img class="chatlog__avatar" src="@await ResolveAssetUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy">
} }
else else
{ {
@ -119,7 +123,7 @@
<div class="chatlog__reference"> <div class="chatlog__reference">
@if (message.ReferencedMessage is not null) @if (message.ReferencedMessage is not null)
{ {
<img class="chatlog__reference-avatar" src="@await ResolveUrlAsync(message.ReferencedMessage.Author.AvatarUrl)" alt="Avatar" loading="lazy"> <img class="chatlog__reference-avatar" src="@await ResolveAssetUrlAsync(message.ReferencedMessage.Author.AvatarUrl)" alt="Avatar" loading="lazy">
<div class="chatlog__reference-author" style="@(referencedUserColor is not null ? $"color: rgb({referencedUserColor.Value.R}, {referencedUserColor.Value.G}, {referencedUserColor.Value.B})" : null)" title="@message.ReferencedMessage.Author.FullName">@referencedUserNick</div> <div class="chatlog__reference-author" style="@(referencedUserColor is not null ? $"color: rgb({referencedUserColor.Value.R}, {referencedUserColor.Value.G}, {referencedUserColor.Value.B})" : null)" title="@message.ReferencedMessage.Author.FullName">@referencedUserNick</div>
<div class="chatlog__reference-content"> <div class="chatlog__reference-content">
<span class="chatlog__reference-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')"> <span class="chatlog__reference-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')">
@ -200,20 +204,20 @@
@{/* Attachment preview */} @{/* Attachment preview */}
@if (attachment.IsImage) @if (attachment.IsImage)
{ {
<a href="@await ResolveUrlAsync(attachment.Url)"> <a href="@await ResolveAssetUrlAsync(attachment.Url)">
<img class="chatlog__attachment-media" src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Image attachment")" title="Image: @attachment.FileName (@attachment.FileSize)" loading="lazy"> <img class="chatlog__attachment-media" src="@await ResolveAssetUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Image attachment")" title="Image: @attachment.FileName (@attachment.FileSize)" loading="lazy">
</a> </a>
} }
else if (attachment.IsVideo) else if (attachment.IsVideo)
{ {
<video class="chatlog__attachment-media" controls> <video class="chatlog__attachment-media" controls>
<source src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Video attachment")" title="Video: @attachment.FileName (@attachment.FileSize)"> <source src="@await ResolveAssetUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Video attachment")" title="Video: @attachment.FileName (@attachment.FileSize)">
</video> </video>
} }
else if (attachment.IsAudio) else if (attachment.IsAudio)
{ {
<audio class="chatlog__attachment-media" controls> <audio class="chatlog__attachment-media" controls>
<source src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Audio attachment")" title="Audio: @attachment.FileName (@attachment.FileSize)"> <source src="@await ResolveAssetUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Audio attachment")" title="Audio: @attachment.FileName (@attachment.FileSize)">
</audio> </audio>
} }
else else
@ -223,7 +227,7 @@
<use href="#attachment-icon"/> <use href="#attachment-icon"/>
</svg> </svg>
<div class="chatlog__attachment-generic-name"> <div class="chatlog__attachment-generic-name">
<a href="@await ResolveUrlAsync(attachment.Url)"> <a href="@await ResolveAssetUrlAsync(attachment.Url)">
@attachment.FileName @attachment.FileName
</a> </a>
</div> </div>
@ -270,7 +274,7 @@
<div class="chatlog__embed-author-container"> <div class="chatlog__embed-author-container">
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl)) @if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
{ {
<img class="chatlog__embed-author-icon" src="@await ResolveUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'"> <img class="chatlog__embed-author-icon" src="@await ResolveAssetUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'">
} }
@if (!string.IsNullOrWhiteSpace(embed.Author.Name)) @if (!string.IsNullOrWhiteSpace(embed.Author.Name))
@ -319,8 +323,8 @@
else if (embed.Kind == EmbedKind.Image && !string.IsNullOrWhiteSpace(embed.Url)) else if (embed.Kind == EmbedKind.Image && !string.IsNullOrWhiteSpace(embed.Url))
{ {
<div class="chatlog__embed"> <div class="chatlog__embed">
<a href="@await ResolveUrlAsync(embed.Url)"> <a href="@await ResolveAssetUrlAsync(embed.Url)">
<img class="chatlog__embed-generic-image" src="@await ResolveUrlAsync(embed.Url)" alt="Embedded image" loading="lazy"> <img class="chatlog__embed-generic-image" src="@await ResolveAssetUrlAsync(embed.Url)" alt="Embedded image" loading="lazy">
</a> </a>
</div> </div>
} }
@ -329,7 +333,7 @@
{ {
<div class="chatlog__embed"> <div class="chatlog__embed">
<video class="chatlog__embed-generic-gifv" loop width="@embed.Video.Width" height="@embed.Video.Height" onmouseover="this.play()" onmouseout="this.pause()"> <video class="chatlog__embed-generic-gifv" loop width="@embed.Video.Width" height="@embed.Video.Height" onmouseover="this.play()" onmouseout="this.pause()">
<source src="@await ResolveUrlAsync(embed.Video.ProxyUrl ?? embed.Video.Url)" alt="Embedded video"> <source src="@await ResolveAssetUrlAsync(embed.Video.ProxyUrl ?? embed.Video.Url)" alt="Embedded video">
</video> </video>
</div> </div>
} }
@ -356,7 +360,7 @@
<div class="chatlog__embed-author-container"> <div class="chatlog__embed-author-container">
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl)) @if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
{ {
<img class="chatlog__embed-author-icon" src="@await ResolveUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'"> <img class="chatlog__embed-author-icon" src="@await ResolveAssetUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'">
} }
@if (!string.IsNullOrWhiteSpace(embed.Author.Name)) @if (!string.IsNullOrWhiteSpace(embed.Author.Name))
@ -430,8 +434,8 @@
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url)) @if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url))
{ {
<div class="chatlog__embed-thumbnail-container"> <div class="chatlog__embed-thumbnail-container">
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)"> <a class="chatlog__embed-thumbnail-link" href="@await ResolveAssetUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
<img class="chatlog__embed-thumbnail" src="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)" alt="Thumbnail" loading="lazy"> <img class="chatlog__embed-thumbnail" src="@await ResolveAssetUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)" alt="Thumbnail" loading="lazy">
</a> </a>
</div> </div>
} }
@ -446,8 +450,8 @@
if (!string.IsNullOrWhiteSpace(image.Url)) if (!string.IsNullOrWhiteSpace(image.Url))
{ {
<div class="chatlog__embed-image-container"> <div class="chatlog__embed-image-container">
<a class="chatlog__embed-image-link" href="@await ResolveUrlAsync(image.ProxyUrl ?? image.Url)"> <a class="chatlog__embed-image-link" href="@await ResolveAssetUrlAsync(image.ProxyUrl ?? image.Url)">
<img class="chatlog__embed-image" src="@await ResolveUrlAsync(image.ProxyUrl ?? image.Url)" alt="Image" loading="lazy"> <img class="chatlog__embed-image" src="@await ResolveAssetUrlAsync(image.ProxyUrl ?? image.Url)" alt="Image" loading="lazy">
</a> </a>
</div> </div>
} }
@ -462,7 +466,7 @@
@{/* Footer icon */} @{/* Footer icon */}
@if (!string.IsNullOrWhiteSpace(embed.Footer?.IconUrl)) @if (!string.IsNullOrWhiteSpace(embed.Footer?.IconUrl))
{ {
<img class="chatlog__embed-footer-icon" src="@await ResolveUrlAsync(embed.Footer.IconProxyUrl ?? embed.Footer.IconUrl)" alt="Footer icon" loading="lazy"> <img class="chatlog__embed-footer-icon" src="@await ResolveAssetUrlAsync(embed.Footer.IconProxyUrl ?? embed.Footer.IconUrl)" alt="Footer icon" loading="lazy">
} }
<span class="chatlog__embed-footer-text"> <span class="chatlog__embed-footer-text">
@ -496,11 +500,11 @@
<div class="chatlog__sticker" title="@sticker.Name"> <div class="chatlog__sticker" title="@sticker.Name">
@if (sticker.Format is StickerFormat.Png or StickerFormat.PngAnimated) @if (sticker.Format is StickerFormat.Png or StickerFormat.PngAnimated)
{ {
<img class="chatlog__sticker--media" src="@await ResolveUrlAsync(sticker.SourceUrl)" alt="Sticker"> <img class="chatlog__sticker--media" src="@await ResolveAssetUrlAsync(sticker.SourceUrl)" alt="Sticker">
} }
else if (sticker.Format == StickerFormat.Lottie) else if (sticker.Format == StickerFormat.Lottie)
{ {
<div class="chatlog__sticker--media" data-source="@await ResolveUrlAsync(sticker.SourceUrl)"></div> <div class="chatlog__sticker--media" data-source="@await ResolveAssetUrlAsync(sticker.SourceUrl)"></div>
} }
</div> </div>
} }
@ -512,7 +516,7 @@
@foreach (var reaction in message.Reactions) @foreach (var reaction in message.Reactions)
{ {
<div class="chatlog__reaction" title="@reaction.Emoji.Code"> <div class="chatlog__reaction" title="@reaction.Emoji.Code">
<img class="chatlog__emoji chatlog__emoji--small" alt="@reaction.Emoji.Name" src="@await ResolveUrlAsync(reaction.Emoji.ImageUrl)" loading="lazy"> <img class="chatlog__emoji chatlog__emoji--small" alt="@reaction.Emoji.Name" src="@await ResolveAssetUrlAsync(reaction.Emoji.ImageUrl)" loading="lazy">
<span class="chatlog__reaction-count">@reaction.Count</span> <span class="chatlog__reaction-count">@reaction.Count</span>
</div> </div>
} }

View file

@ -14,11 +14,14 @@
string GetFontUrl(int weight) => string GetFontUrl(int weight) =>
$"https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/whitney-{weight}.woff"; $"https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/whitney-{weight}.woff";
ValueTask<string> ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url, CancellationToken); ValueTask<string> ResolveAssetUrlAsync(string url) =>
Model.ExportContext.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date); string FormatDate(DateTimeOffset date) =>
Model.ExportContext.FormatDate(date);
ValueTask<string> FormatMarkdownAsync(string markdown) => Model.FormatMarkdownAsync(markdown); ValueTask<string> FormatMarkdownAsync(string markdown) =>
Model.FormatMarkdownAsync(markdown);
} }
<!DOCTYPE html> <!DOCTYPE html>
@ -32,31 +35,31 @@
@{/* Styling */} @{/* Styling */}
<style> <style>
@@font-face { @@font-face {
src: url(@await ResolveUrlAsync(GetFontUrl(300))); src: url(@await ResolveAssetUrlAsync(GetFontUrl(300)));
font-family: Whitney; font-family: Whitney;
font-weight: 300; font-weight: 300;
} }
@@font-face { @@font-face {
src: url(@await ResolveUrlAsync(GetFontUrl(400))); src: url(@await ResolveAssetUrlAsync(GetFontUrl(400)));
font-family: Whitney; font-family: Whitney;
font-weight: 400; font-weight: 400;
} }
@@font-face { @@font-face {
src: url(@await ResolveUrlAsync(GetFontUrl(500))); src: url(@await ResolveAssetUrlAsync(GetFontUrl(500)));
font-family: Whitney; font-family: Whitney;
font-weight: 500; font-weight: 500;
} }
@@font-face { @@font-face {
src: url(@await ResolveUrlAsync(GetFontUrl(600))); src: url(@await ResolveAssetUrlAsync(GetFontUrl(600)));
font-family: Whitney; font-family: Whitney;
font-weight: 600; font-weight: 600;
} }
@@font-face { @@font-face {
src: url(@await ResolveUrlAsync(GetFontUrl(700))); src: url(@await ResolveAssetUrlAsync(GetFontUrl(700)));
font-family: Whitney; font-family: Whitney;
font-weight: 700; font-weight: 700;
} }
@ -752,8 +755,8 @@
</style> </style>
@{/* Syntax highlighting */} @{/* Syntax highlighting */}
<link rel="stylesheet" href="@await ResolveUrlAsync($"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/solarized-{Model.ThemeName.ToLowerInvariant()}.min.css")"> <link rel="stylesheet" href="@await ResolveAssetUrlAsync($"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/solarized-{Model.ThemeName.ToLowerInvariant()}.min.css")">
<script src="@await ResolveUrlAsync("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js")"></script> <script src="@await ResolveAssetUrlAsync("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js")"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.chatlog__markdown-pre--multiline').forEach(e => hljs.highlightBlock(e)); document.querySelectorAll('.chatlog__markdown-pre--multiline').forEach(e => hljs.highlightBlock(e));
@ -761,7 +764,7 @@
</script> </script>
@{/* Lottie animation support */} @{/* Lottie animation support */}
<script src="@await ResolveUrlAsync("https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.8.1/lottie.min.js")"></script> <script src="@await ResolveAssetUrlAsync("https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.8.1/lottie.min.js")"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.chatlog__sticker--media[data-source]').forEach(e => { document.querySelectorAll('.chatlog__sticker--media[data-source]').forEach(e => {
@ -843,7 +846,7 @@
<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 ResolveUrlAsync(Model.ExportContext.Request.Guild.IconUrl)" alt="Guild icon" loading="lazy"> <img class="preamble__guild-icon" src="@await ResolveAssetUrlAsync(Model.ExportContext.Request.Guild.IconUrl)" alt="Guild icon" loading="lazy">
</div> </div>
<div class="preamble__entries-container"> <div class="preamble__entries-container">
<div class="preamble__entry">@Model.ExportContext.Request.Guild.Name</div> <div class="preamble__entry">@Model.ExportContext.Request.Guild.Name</div>

View file

@ -39,7 +39,7 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteStartObject(); _writer.WriteStartObject();
_writer.WriteString("id", attachment.Id.ToString()); _writer.WriteString("id", attachment.Id.ToString());
_writer.WriteString("url", await Context.ResolveMediaUrlAsync(attachment.Url, cancellationToken)); _writer.WriteString("url", await Context.ResolveAssetUrlAsync(attachment.Url, cancellationToken));
_writer.WriteString("fileName", attachment.FileName); _writer.WriteString("fileName", attachment.FileName);
_writer.WriteNumber("fileSizeBytes", attachment.FileSize.TotalBytes); _writer.WriteNumber("fileSizeBytes", attachment.FileSize.TotalBytes);
@ -57,7 +57,12 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteString("url", embedAuthor.Url); _writer.WriteString("url", embedAuthor.Url);
if (!string.IsNullOrWhiteSpace(embedAuthor.IconUrl)) if (!string.IsNullOrWhiteSpace(embedAuthor.IconUrl))
_writer.WriteString("iconUrl", await Context.ResolveMediaUrlAsync(embedAuthor.IconProxyUrl ?? embedAuthor.IconUrl, cancellationToken)); {
_writer.WriteString(
"iconUrl",
await Context.ResolveAssetUrlAsync(embedAuthor.IconProxyUrl ?? embedAuthor.IconUrl, cancellationToken)
);
}
_writer.WriteEndObject(); _writer.WriteEndObject();
await _writer.FlushAsync(cancellationToken); await _writer.FlushAsync(cancellationToken);
@ -70,7 +75,12 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteStartObject(); _writer.WriteStartObject();
if (!string.IsNullOrWhiteSpace(embedImage.Url)) if (!string.IsNullOrWhiteSpace(embedImage.Url))
_writer.WriteString("url", await Context.ResolveMediaUrlAsync(embedImage.ProxyUrl ?? embedImage.Url, cancellationToken)); {
_writer.WriteString(
"url",
await Context.ResolveAssetUrlAsync(embedImage.ProxyUrl ?? embedImage.Url, cancellationToken)
);
}
_writer.WriteNumber("width", embedImage.Width); _writer.WriteNumber("width", embedImage.Width);
_writer.WriteNumber("height", embedImage.Height); _writer.WriteNumber("height", embedImage.Height);
@ -88,7 +98,12 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteString("text", embedFooter.Text); _writer.WriteString("text", embedFooter.Text);
if (!string.IsNullOrWhiteSpace(embedFooter.IconUrl)) if (!string.IsNullOrWhiteSpace(embedFooter.IconUrl))
_writer.WriteString("iconUrl", await Context.ResolveMediaUrlAsync(embedFooter.IconProxyUrl ?? embedFooter.IconUrl, cancellationToken)); {
_writer.WriteString(
"iconUrl",
await Context.ResolveAssetUrlAsync(embedFooter.IconProxyUrl ?? embedFooter.IconUrl, cancellationToken)
);
}
_writer.WriteEndObject(); _writer.WriteEndObject();
await _writer.FlushAsync(cancellationToken); await _writer.FlushAsync(cancellationToken);
@ -176,7 +191,7 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteString("id", sticker.Id.ToString()); _writer.WriteString("id", sticker.Id.ToString());
_writer.WriteString("name", sticker.Name); _writer.WriteString("name", sticker.Name);
_writer.WriteString("format", sticker.Format.ToString()); _writer.WriteString("format", sticker.Format.ToString());
_writer.WriteString("sourceUrl", await Context.ResolveMediaUrlAsync(sticker.SourceUrl, cancellationToken)); _writer.WriteString("sourceUrl", await Context.ResolveAssetUrlAsync(sticker.SourceUrl, cancellationToken));
_writer.WriteEndObject(); _writer.WriteEndObject();
await _writer.FlushAsync(cancellationToken); await _writer.FlushAsync(cancellationToken);
@ -193,7 +208,7 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteString("id", reaction.Emoji.Id.ToString()); _writer.WriteString("id", reaction.Emoji.Id.ToString());
_writer.WriteString("name", reaction.Emoji.Name); _writer.WriteString("name", reaction.Emoji.Name);
_writer.WriteBoolean("isAnimated", reaction.Emoji.IsAnimated); _writer.WriteBoolean("isAnimated", reaction.Emoji.IsAnimated);
_writer.WriteString("imageUrl", await Context.ResolveMediaUrlAsync(reaction.Emoji.ImageUrl, cancellationToken)); _writer.WriteString("imageUrl", await Context.ResolveAssetUrlAsync(reaction.Emoji.ImageUrl, cancellationToken));
_writer.WriteEndObject(); _writer.WriteEndObject();
_writer.WriteNumber("count", reaction.Count); _writer.WriteNumber("count", reaction.Count);
@ -227,7 +242,7 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteStartObject("guild"); _writer.WriteStartObject("guild");
_writer.WriteString("id", Context.Request.Guild.Id.ToString()); _writer.WriteString("id", Context.Request.Guild.Id.ToString());
_writer.WriteString("name", Context.Request.Guild.Name); _writer.WriteString("name", Context.Request.Guild.Name);
_writer.WriteString("iconUrl", await Context.ResolveMediaUrlAsync(Context.Request.Guild.IconUrl, cancellationToken)); _writer.WriteString("iconUrl", await Context.ResolveAssetUrlAsync(Context.Request.Guild.IconUrl, cancellationToken));
_writer.WriteEndObject(); _writer.WriteEndObject();
// Channel // Channel
@ -278,7 +293,7 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteString("nickname", Context.TryGetMember(message.Author.Id)?.Nick ?? message.Author.Name); _writer.WriteString("nickname", Context.TryGetMember(message.Author.Id)?.Nick ?? message.Author.Name);
_writer.WriteString("color", Context.TryGetUserColor(message.Author.Id)?.ToHex()); _writer.WriteString("color", Context.TryGetUserColor(message.Author.Id)?.ToHex());
_writer.WriteBoolean("isBot", message.Author.IsBot); _writer.WriteBoolean("isBot", message.Author.IsBot);
_writer.WriteString("avatarUrl", await Context.ResolveMediaUrlAsync(message.Author.AvatarUrl, cancellationToken)); _writer.WriteString("avatarUrl", await Context.ResolveAssetUrlAsync(message.Author.AvatarUrl, cancellationToken));
_writer.WriteEndObject(); _writer.WriteEndObject();
// Attachments // Attachments

View file

@ -24,15 +24,15 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
_isJumbo = isJumbo; _isJumbo = isJumbo;
} }
protected override ValueTask<MarkdownNode> VisitTextAsync(TextNode text) protected override async ValueTask<MarkdownNode> VisitTextAsync(TextNode text)
{ {
_buffer.Append(HtmlEncode(text.Text)); _buffer.Append(HtmlEncode(text.Text));
return base.VisitTextAsync(text); return await base.VisitTextAsync(text);
} }
protected override ValueTask<MarkdownNode> VisitFormattingAsync(FormattingNode formatting) protected override async ValueTask<MarkdownNode> VisitFormattingAsync(FormattingNode formatting)
{ {
var (tagOpen, tagClose) = formatting.Kind switch var (openingTag, closingTag) = formatting.Kind switch
{ {
FormattingKind.Bold => ( FormattingKind.Bold => (
"<strong>", "<strong>",
@ -67,24 +67,24 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
_ => throw new InvalidOperationException($"Unknown formatting kind '{formatting.Kind}'.") _ => throw new InvalidOperationException($"Unknown formatting kind '{formatting.Kind}'.")
}; };
_buffer.Append(tagOpen); _buffer.Append(openingTag);
var result = base.VisitFormattingAsync(formatting); var result = await base.VisitFormattingAsync(formatting);
_buffer.Append(tagClose); _buffer.Append(closingTag);
return result; return result;
} }
protected override ValueTask<MarkdownNode> VisitInlineCodeBlockAsync(InlineCodeBlockNode inlineCodeBlock) protected override async ValueTask<MarkdownNode> VisitInlineCodeBlockAsync(InlineCodeBlockNode inlineCodeBlock)
{ {
_buffer _buffer
.Append("<code class=\"chatlog__markdown-pre chatlog__markdown-pre--inline\">") .Append("<code class=\"chatlog__markdown-pre chatlog__markdown-pre--inline\">")
.Append(HtmlEncode(inlineCodeBlock.Code)) .Append(HtmlEncode(inlineCodeBlock.Code))
.Append("</code>"); .Append("</code>");
return base.VisitInlineCodeBlockAsync(inlineCodeBlock); return await base.VisitInlineCodeBlockAsync(inlineCodeBlock);
} }
protected override ValueTask<MarkdownNode> VisitMultiLineCodeBlockAsync(MultiLineCodeBlockNode multiLineCodeBlock) protected override async ValueTask<MarkdownNode> VisitMultiLineCodeBlockAsync(MultiLineCodeBlockNode multiLineCodeBlock)
{ {
var highlightCssClass = !string.IsNullOrWhiteSpace(multiLineCodeBlock.Language) var highlightCssClass = !string.IsNullOrWhiteSpace(multiLineCodeBlock.Language)
? $"language-{multiLineCodeBlock.Language}" ? $"language-{multiLineCodeBlock.Language}"
@ -95,10 +95,10 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
.Append(HtmlEncode(multiLineCodeBlock.Code)) .Append(HtmlEncode(multiLineCodeBlock.Code))
.Append("</code>"); .Append("</code>");
return base.VisitMultiLineCodeBlockAsync(multiLineCodeBlock); return await base.VisitMultiLineCodeBlockAsync(multiLineCodeBlock);
} }
protected override ValueTask<MarkdownNode> VisitLinkAsync(LinkNode link) protected override async ValueTask<MarkdownNode> VisitLinkAsync(LinkNode link)
{ {
// Try to extract message ID if the link refers to a Discord message // Try to extract message ID if the link refers to a Discord message
var linkedMessageId = Regex.Match( var linkedMessageId = Regex.Match(
@ -112,7 +112,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
: $"<a href=\"{HtmlEncode(link.Url)}\">" : $"<a href=\"{HtmlEncode(link.Url)}\">"
); );
var result = base.VisitLinkAsync(link); var result = await base.VisitLinkAsync(link);
_buffer.Append("</a>"); _buffer.Append("</a>");
return result; return result;
@ -123,13 +123,20 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated); var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated);
var jumboClass = _isJumbo ? "chatlog__emoji--large" : ""; var jumboClass = _isJumbo ? "chatlog__emoji--large" : "";
_buffer _buffer.Append(
.Append($"<img loading=\"lazy\" class=\"chatlog__emoji {jumboClass}\" alt=\"{emoji.Name}\" title=\"{emoji.Code}\" src=\"{await _context.ResolveMediaUrlAsync(emojiImageUrl)}\">"); $"<img " +
$"loading=\"lazy\" " +
$"class=\"chatlog__emoji {jumboClass}\" " +
$"alt=\"{emoji.Name}\" " +
$"title=\"{emoji.Code}\" " +
$"src=\"{await _context.ResolveAssetUrlAsync(emojiImageUrl)}\"" +
$">"
);
return await base.VisitEmojiAsync(emoji); return await base.VisitEmojiAsync(emoji);
} }
protected override ValueTask<MarkdownNode> VisitMentionAsync(MentionNode mention) protected override async ValueTask<MarkdownNode> VisitMentionAsync(MentionNode mention)
{ {
if (mention.Kind == MentionKind.Everyone) if (mention.Kind == MentionKind.Everyone)
{ {
@ -184,10 +191,10 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
.Append("</span>"); .Append("</span>");
} }
return base.VisitMentionAsync(mention); return await base.VisitMentionAsync(mention);
} }
protected override ValueTask<MarkdownNode> VisitUnixTimestampAsync(UnixTimestampNode timestamp) protected override async ValueTask<MarkdownNode> VisitUnixTimestampAsync(UnixTimestampNode timestamp)
{ {
var dateString = timestamp.Date is not null var dateString = timestamp.Date is not null
? _context.FormatDate(timestamp.Date.Value) ? _context.FormatDate(timestamp.Date.Value)
@ -203,7 +210,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
.Append(HtmlEncode(dateString)) .Append(HtmlEncode(dateString))
.Append("</span>"); .Append("</span>");
return base.VisitUnixTimestampAsync(timestamp); return await base.VisitUnixTimestampAsync(timestamp);
} }
} }
@ -211,7 +218,10 @@ internal partial class HtmlMarkdownVisitor
{ {
private static string HtmlEncode(string text) => WebUtility.HtmlEncode(text); private static string HtmlEncode(string text) => WebUtility.HtmlEncode(text);
public static async ValueTask<string> FormatAsync(ExportContext context, string markdown, bool isJumboAllowed = true) public static async ValueTask<string> FormatAsync(
ExportContext context,
string markdown,
bool isJumboAllowed = true)
{ {
var nodes = MarkdownParser.Parse(markdown); var nodes = MarkdownParser.Parse(markdown);

View file

@ -17,13 +17,13 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
_buffer = buffer; _buffer = buffer;
} }
protected override ValueTask<MarkdownNode> VisitTextAsync(TextNode text) protected override async ValueTask<MarkdownNode> VisitTextAsync(TextNode text)
{ {
_buffer.Append(text.Text); _buffer.Append(text.Text);
return base.VisitTextAsync(text); return await base.VisitTextAsync(text);
} }
protected override ValueTask<MarkdownNode> VisitEmojiAsync(EmojiNode emoji) protected override async ValueTask<MarkdownNode> VisitEmojiAsync(EmojiNode emoji)
{ {
_buffer.Append( _buffer.Append(
emoji.IsCustomEmoji emoji.IsCustomEmoji
@ -31,10 +31,10 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
: emoji.Name : emoji.Name
); );
return base.VisitEmojiAsync(emoji); return await base.VisitEmojiAsync(emoji);
} }
protected override ValueTask<MarkdownNode> VisitMentionAsync(MentionNode mention) protected override async ValueTask<MarkdownNode> VisitMentionAsync(MentionNode mention)
{ {
if (mention.Kind == MentionKind.Everyone) if (mention.Kind == MentionKind.Everyone)
{ {
@ -70,10 +70,10 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
_buffer.Append($"@{name}"); _buffer.Append($"@{name}");
} }
return base.VisitMentionAsync(mention); return await base.VisitMentionAsync(mention);
} }
protected override ValueTask<MarkdownNode> VisitUnixTimestampAsync(UnixTimestampNode timestamp) protected override async ValueTask<MarkdownNode> VisitUnixTimestampAsync(UnixTimestampNode timestamp)
{ {
_buffer.Append( _buffer.Append(
timestamp.Date is not null timestamp.Date is not null
@ -81,7 +81,7 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
: "Invalid date" : "Invalid date"
); );
return base.VisitUnixTimestampAsync(timestamp); return await base.VisitUnixTimestampAsync(timestamp);
} }
} }

View file

@ -48,7 +48,7 @@ internal class PlainTextMessageWriter : MessageWriter
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await _writer.WriteLineAsync(await Context.ResolveMediaUrlAsync(attachment.Url, cancellationToken)); await _writer.WriteLineAsync(await Context.ResolveAssetUrlAsync(attachment.Url, cancellationToken));
} }
await _writer.WriteLineAsync(); await _writer.WriteLineAsync();
@ -86,12 +86,26 @@ internal class PlainTextMessageWriter : MessageWriter
} }
if (!string.IsNullOrWhiteSpace(embed.Thumbnail?.Url)) if (!string.IsNullOrWhiteSpace(embed.Thumbnail?.Url))
await _writer.WriteLineAsync(await Context.ResolveMediaUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url, cancellationToken)); {
await _writer.WriteLineAsync(
await Context.ResolveAssetUrlAsync(
embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url,
cancellationToken
)
);
}
foreach (var image in embed.Images) foreach (var image in embed.Images)
{ {
if (!string.IsNullOrWhiteSpace(image.Url)) if (!string.IsNullOrWhiteSpace(image.Url))
await _writer.WriteLineAsync(await Context.ResolveMediaUrlAsync(image.ProxyUrl ?? image.Url, cancellationToken)); {
await _writer.WriteLineAsync(
await Context.ResolveAssetUrlAsync(
image.ProxyUrl ?? image.Url,
cancellationToken
)
);
}
} }
if (!string.IsNullOrWhiteSpace(embed.Footer?.Text)) if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
@ -114,7 +128,9 @@ internal class PlainTextMessageWriter : MessageWriter
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await _writer.WriteLineAsync(await Context.ResolveMediaUrlAsync(sticker.SourceUrl, cancellationToken)); await _writer.WriteLineAsync(
await Context.ResolveAssetUrlAsync(sticker.SourceUrl, cancellationToken)
);
} }
await _writer.WriteLineAsync(); await _writer.WriteLineAsync();

View file

@ -16,7 +16,7 @@ public partial class SettingsService : SettingsManager
public int ParallelLimit { get; set; } = 1; public int ParallelLimit { get; set; } = 1;
public bool ShouldReuseMedia { get; set; } public bool ShouldReuseAssets { get; set; }
public string? LastToken { get; set; } public string? LastToken { get; set; }
@ -26,7 +26,7 @@ public partial class SettingsService : SettingsManager
public string? LastMessageFilterValue { get; set; } public string? LastMessageFilterValue { get; set; }
public bool LastShouldDownloadMedia { get; set; } public bool LastShouldDownloadAssets { get; set; }
public SettingsService() public SettingsService()
{ {

View file

@ -186,8 +186,8 @@ public class DashboardViewModel : PropertyChangedBase
dialog.Before?.Pipe(Snowflake.FromDate), dialog.Before?.Pipe(Snowflake.FromDate),
dialog.PartitionLimit, dialog.PartitionLimit,
dialog.MessageFilter, dialog.MessageFilter,
dialog.ShouldDownloadMedia, dialog.ShouldDownloadAssets,
_settingsService.ShouldReuseMedia, _settingsService.ShouldReuseAssets,
_settingsService.DateFormat _settingsService.DateFormat
); );

View file

@ -59,7 +59,7 @@ public class ExportSetupViewModel : DialogScreen
? MessageFilter.Parse(MessageFilterValue) ? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null; : MessageFilter.Null;
public bool ShouldDownloadMedia { get; set; } public bool ShouldDownloadAssets { get; set; }
public bool IsAdvancedSectionDisplayed { get; set; } public bool IsAdvancedSectionDisplayed { get; set; }
@ -72,7 +72,7 @@ public class ExportSetupViewModel : DialogScreen
SelectedFormat = _settingsService.LastExportFormat; SelectedFormat = _settingsService.LastExportFormat;
PartitionLimitValue = _settingsService.LastPartitionLimitValue; PartitionLimitValue = _settingsService.LastPartitionLimitValue;
MessageFilterValue = _settingsService.LastMessageFilterValue; MessageFilterValue = _settingsService.LastMessageFilterValue;
ShouldDownloadMedia = _settingsService.LastShouldDownloadMedia; ShouldDownloadAssets = _settingsService.LastShouldDownloadAssets;
// Show the "advanced options" section by default if any // Show the "advanced options" section by default if any
// of the advanced options are set to non-default values. // of the advanced options are set to non-default values.
@ -81,7 +81,7 @@ public class ExportSetupViewModel : DialogScreen
Before != default || Before != default ||
!string.IsNullOrWhiteSpace(PartitionLimitValue) || !string.IsNullOrWhiteSpace(PartitionLimitValue) ||
!string.IsNullOrWhiteSpace(MessageFilterValue) || !string.IsNullOrWhiteSpace(MessageFilterValue) ||
ShouldDownloadMedia != default; ShouldDownloadAssets != default;
} }
public void ToggleAdvancedSection() => IsAdvancedSectionDisplayed = !IsAdvancedSectionDisplayed; public void ToggleAdvancedSection() => IsAdvancedSectionDisplayed = !IsAdvancedSectionDisplayed;
@ -92,7 +92,7 @@ public class ExportSetupViewModel : DialogScreen
_settingsService.LastExportFormat = SelectedFormat; _settingsService.LastExportFormat = SelectedFormat;
_settingsService.LastPartitionLimitValue = PartitionLimitValue; _settingsService.LastPartitionLimitValue = PartitionLimitValue;
_settingsService.LastMessageFilterValue = MessageFilterValue; _settingsService.LastMessageFilterValue = MessageFilterValue;
_settingsService.LastShouldDownloadMedia = ShouldDownloadMedia; _settingsService.LastShouldDownloadAssets = ShouldDownloadAssets;
// If single channel - prompt file path // If single channel - prompt file path
if (IsSingleChannel) if (IsSingleChannel)

View file

@ -38,10 +38,10 @@ public class SettingsViewModel : DialogScreen
set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10); set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10);
} }
public bool ShouldReuseMedia public bool ShouldReuseAssets
{ {
get => _settingsService.ShouldReuseMedia; get => _settingsService.ShouldReuseAssets;
set => _settingsService.ShouldReuseMedia = value; set => _settingsService.ShouldReuseAssets = value;
} }
public SettingsViewModel(SettingsService settingsService) => public SettingsViewModel(SettingsService settingsService) =>

View file

@ -167,8 +167,8 @@
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')." />
<!-- Download media --> <!-- Download assets -->
<Grid Margin="16,16" ToolTip="Download referenced media content (user avatars, attached files, embedded images, etc)"> <Grid Margin="16,16" 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" />
@ -177,12 +177,12 @@
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Download media" /> Text="Download assets" />
<ToggleButton <ToggleButton
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding ShouldDownloadMedia}" /> IsChecked="{Binding ShouldDownloadAssets}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

View file

@ -82,20 +82,20 @@
IsChecked="{Binding IsTokenPersisted}" /> IsChecked="{Binding IsTokenPersisted}" />
</DockPanel> </DockPanel>
<!-- Reuse media --> <!-- Reuse assets -->
<DockPanel <DockPanel
Margin="16,8" Margin="16,8"
Background="Transparent" Background="Transparent"
LastChildFill="False" LastChildFill="False"
ToolTip="Reuse already existing media content to skip redundant downloads"> ToolTip="Reuse previously downloaded assets to avoid redundant requests">
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
DockPanel.Dock="Left" DockPanel.Dock="Left"
Text="Reuse downloaded media" /> Text="Reuse downloaded assets" />
<ToggleButton <ToggleButton
VerticalAlignment="Center" VerticalAlignment="Center"
DockPanel.Dock="Right" DockPanel.Dock="Right"
IsChecked="{Binding ShouldReuseMedia}" /> IsChecked="{Binding ShouldReuseAssets}" />
</DockPanel> </DockPanel>
<!-- Date format --> <!-- Date format -->