Migrate from MiniRazor to RazorBlade

pull/965/head
Oleksii Holub 2 years ago
parent 5b1b720503
commit e752269467

@ -7,14 +7,14 @@
<ItemGroup>
<PackageReference Include="Gress" Version="2.0.1" />
<PackageReference Include="JsonExtensions" Version="1.2.0" />
<PackageReference Include="MiniRazor.CodeGen" Version="2.2.2" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="RazorBlade" Version="0.2.0" />
<PackageReference Include="Superpower" Version="3.0.0" />
<PackageReference Include="WebMarkupMin.Core" Version="2.12.0" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Exporting\Writers\Html\*.cshtml" IsRazorTemplate="true" />
<RazorBlade Include="Exporting\Writers\Html\*.cshtml" />
</ItemGroup>
</Project>

@ -1,43 +1,56 @@
@using System
@using System.Collections.Generic
@using System.Linq
@using System.Threading
@using System.Threading.Tasks
@using DiscordChatExporter.Core.Discord.Data
@using DiscordChatExporter.Core.Discord.Data.Embeds
@using DiscordChatExporter.Core.Exporting.Writers.Html;
@using DiscordChatExporter.Core.Exporting
@using DiscordChatExporter.Core.Exporting.Writers.Html
@using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
@using DiscordChatExporter.Core.Utils.Extensions
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
@inherits MiniRazor.TemplateBase<MessageGroupTemplateContext>
@inherits RazorBlade.HtmlTemplate
@functions {
// RazorBlade does not provide built-in support for cancellation
// https://github.com/ltrzesniewski/RazorBlade/issues/2
public CancellationToken CancellationToken { get; init; }
public ExportContext ExportContext { get; init; } = default!;
public IReadOnlyList<Message> Messages { get; init; } = Array.Empty<Message>();
}
@{
ValueTask<string> ResolveAssetUrlAsync(string url) =>
Model.ExportContext.ResolveAssetUrlAsync(url);
ExportContext.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset date) =>
Model.ExportContext.FormatDate(date);
ExportContext.FormatDate(date);
ValueTask<string> FormatMarkdownAsync(string markdown) =>
Model.FormatMarkdownAsync(markdown);
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown);
ValueTask<string> FormatEmbedMarkdownAsync(string markdown) =>
Model.FormatMarkdownAsync(markdown, false);
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown, false);
var firstMessage = Model.Messages.First();
var firstMessage = Messages.First();
var userMember = Model.ExportContext.TryGetMember(firstMessage.Author.Id);
var userMember = ExportContext.TryGetMember(firstMessage.Author.Id);
var userColor = Model.ExportContext.TryGetUserColor(firstMessage.Author.Id);
var userColor = ExportContext.TryGetUserColor(firstMessage.Author.Id);
var userNick = firstMessage.Author.IsBot
? firstMessage.Author.Name
: userMember?.Nick ?? firstMessage.Author.Name;
var referencedUserMember = firstMessage.ReferencedMessage is not null
? Model.ExportContext.TryGetMember(firstMessage.ReferencedMessage.Author.Id)
? ExportContext.TryGetMember(firstMessage.ReferencedMessage.Author.Id)
: null;
var referencedUserColor = firstMessage.ReferencedMessage is not null
? Model.ExportContext.TryGetUserColor(firstMessage.ReferencedMessage.Author.Id)
? ExportContext.TryGetUserColor(firstMessage.ReferencedMessage.Author.Id)
: null;
var referencedUserNick = firstMessage.ReferencedMessage is not null
@ -48,7 +61,7 @@
}
<div class="chatlog__message-group">
@foreach (var (message, i) in Model.Messages.WithIndex())
@foreach (var (message, i) in Messages.WithIndex())
{
var isFirst = i == 0;
@ -59,8 +72,8 @@
{
// 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.
userMember = Model.ExportContext.TryGetMember(message.Author.Id);
userColor = Model.ExportContext.TryGetUserColor(message.Author.Id);
userMember = ExportContext.TryGetMember(message.Author.Id);
userColor = ExportContext.TryGetUserColor(message.Author.Id);
userNick = message.Author.IsBot
? message.Author.Name
: userMember?.Nick ?? message.Author.Name;
@ -129,7 +142,7 @@
<span class="chatlog__reference-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')">
@if (!string.IsNullOrWhiteSpace(message.ReferencedMessage.Content) && !message.ReferencedMessage.IsContentHidden())
{
<!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(message.ReferencedMessage.Content))<!--/wmm:ignore-->
<!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(message.ReferencedMessage.Content))<!--/wmm:ignore-->
}
else if (message.ReferencedMessage.Attachments.Any() || message.ReferencedMessage.Embeds.Any())
{
@ -180,7 +193,7 @@
@{/* Text */}
@if (!string.IsNullOrWhiteSpace(message.Content) && !message.IsContentHidden())
{
<span class="chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatMarkdownAsync(message.Content))<!--/wmm:ignore--></span>
<span class="chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatMarkdownAsync(message.Content))<!--/wmm:ignore--></span>
}
@{/* Edited timestamp */}
@ -300,12 +313,12 @@
@if (!string.IsNullOrWhiteSpace(embed.Url))
{
<a class="chatlog__embed-title-link" href="@embed.Url">
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
</a>
}
else
{
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
}
</div>
}
@ -386,12 +399,12 @@
@if (!string.IsNullOrWhiteSpace(embed.Url))
{
<a class="chatlog__embed-title-link" href="@embed.Url">
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
</a>
}
else
{
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(embed.Title))<!--/wmm:ignore--></div>
}
</div>
}
@ -400,7 +413,7 @@
@if (!string.IsNullOrWhiteSpace(embed.Description))
{
<div class="chatlog__embed-description">
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(embed.Description))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(embed.Description))<!--/wmm:ignore--></div>
</div>
}
@ -414,14 +427,14 @@
@if (!string.IsNullOrWhiteSpace(field.Name))
{
<div class="chatlog__embed-field-name">
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(field.Name))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(field.Name))<!--/wmm:ignore--></div>
</div>
}
@if (!string.IsNullOrWhiteSpace(field.Value))
{
<div class="chatlog__embed-field-value">
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(await FormatEmbedMarkdownAsync(field.Value))<!--/wmm:ignore--></div>
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatEmbedMarkdownAsync(field.Value))<!--/wmm:ignore--></div>
</div>
}
</div>

@ -1,22 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
internal class MessageGroupTemplateContext
{
public ExportContext ExportContext { get; }
public IReadOnlyList<Message> Messages { get; }
public MessageGroupTemplateContext(ExportContext exportContext, IReadOnlyList<Message> messages)
{
ExportContext = exportContext;
Messages = messages;
}
public ValueTask<string> FormatMarkdownAsync(string? markdown, bool isJumboAllowed = true) =>
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown ?? "", isJumboAllowed);
}

@ -1,15 +1,27 @@
@using DiscordChatExporter.Core.Exporting.Writers.Html;
@using System.Threading
@using DiscordChatExporter.Core.Exporting
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
@inherits MiniRazor.TemplateBase<PostambleTemplateContext>
@inherits RazorBlade.HtmlTemplate
@{/* Close elements opened by preamble */}
@functions {
// RazorBlade does not provide built-in support for cancellation
// https://github.com/ltrzesniewski/RazorBlade/issues/2
public CancellationToken CancellationToken { get; init; }
public ExportContext ExportContext { get; init; } = default!;
public long MessagesWritten { get; init; }
}
@{
/* Close elements opened by preamble */
}
<!--wmm:ignore-->
</div>
<!--/wmm:ignore-->
<div class="postamble">
<div class="postamble__entry">Exported @Model.MessagesWritten.ToString("N0") message(s)</div>
<div class="postamble__entry">Exported @MessagesWritten.ToString("N0") message(s)</div>
</div>
</body>

@ -1,14 +0,0 @@
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
internal class PostambleTemplateContext
{
public ExportContext ExportContext { get; }
public long MessagesWritten { get; }
public PostambleTemplateContext(ExportContext exportContext, long messagesWritten)
{
ExportContext = exportContext;
MessagesWritten = messagesWritten;
}
}

@ -1,13 +1,24 @@
@using System
@using System.Threading
@using System.Threading.Tasks
@using DiscordChatExporter.Core.Exporting.Writers.Html;
@using DiscordChatExporter.Core.Exporting
@using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
@inherits MiniRazor.TemplateBase<PreambleTemplateContext>
@inherits RazorBlade.HtmlTemplate
@functions {
// RazorBlade does not provide built-in support for cancellation
// https://github.com/ltrzesniewski/RazorBlade/issues/2
public CancellationToken CancellationToken { get; init; }
public ExportContext ExportContext { get; init; } = default!;
public string ThemeName { get; init; } = "Dark";
}
@{
string Themed(string darkVariant, string lightVariant) =>
string.Equals(Model.ThemeName, "Dark", StringComparison.OrdinalIgnoreCase)
string.Equals(ThemeName, "Dark", StringComparison.OrdinalIgnoreCase)
? darkVariant
: lightVariant;
@ -15,20 +26,20 @@
$"https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/whitney-{weight}.woff";
ValueTask<string> ResolveAssetUrlAsync(string url) =>
Model.ExportContext.ResolveAssetUrlAsync(url, CancellationToken);
ExportContext.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset date) =>
Model.ExportContext.FormatDate(date);
ExportContext.FormatDate(date);
ValueTask<string> FormatMarkdownAsync(string markdown) =>
Model.FormatMarkdownAsync(markdown);
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown);
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>@Model.ExportContext.Request.Guild.Name - @Model.ExportContext.Request.Channel.Name</title>
<title>@ExportContext.Request.Guild.Name - @ExportContext.Request.Channel.Name</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
@ -755,7 +766,7 @@
</style>
@{/* Syntax highlighting */}
<link rel="stylesheet" href="@await ResolveAssetUrlAsync($"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-{ThemeName.ToLowerInvariant()}.min.css")">
<script src="@await ResolveAssetUrlAsync("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js")"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
@ -846,31 +857,31 @@
<div class="preamble">
<div class="preamble__guild-icon-container">
<img class="preamble__guild-icon" src="@await ResolveAssetUrlAsync(Model.ExportContext.Request.Guild.IconUrl)" alt="Guild icon" loading="lazy">
<img class="preamble__guild-icon" src="@await ResolveAssetUrlAsync(ExportContext.Request.Guild.IconUrl)" alt="Guild icon" loading="lazy">
</div>
<div class="preamble__entries-container">
<div class="preamble__entry">@Model.ExportContext.Request.Guild.Name</div>
<div class="preamble__entry">@Model.ExportContext.Request.Channel.Category.Name / @Model.ExportContext.Request.Channel.Name</div>
<div class="preamble__entry">@ExportContext.Request.Guild.Name</div>
<div class="preamble__entry">@ExportContext.Request.Channel.Category.Name / @ExportContext.Request.Channel.Name</div>
@if (!string.IsNullOrWhiteSpace(Model.ExportContext.Request.Channel.Topic))
@if (!string.IsNullOrWhiteSpace(ExportContext.Request.Channel.Topic))
{
<div class="preamble__entry preamble__entry--small">@Raw(await FormatMarkdownAsync(Model.ExportContext.Request.Channel.Topic))</div>
<div class="preamble__entry preamble__entry--small">@Html.Raw(await FormatMarkdownAsync(ExportContext.Request.Channel.Topic))</div>
}
@if (Model.ExportContext.Request.After is not null || Model.ExportContext.Request.Before is not null)
@if (ExportContext.Request.After is not null || ExportContext.Request.Before is not null)
{
<div class="preamble__entry preamble__entry--small">
@if (Model.ExportContext.Request.After is not null && Model.ExportContext.Request.Before is not null)
@if (ExportContext.Request.After is not null && ExportContext.Request.Before is not null)
{
@($"Between {FormatDate(Model.ExportContext.Request.After.Value.ToDate())} and {FormatDate(Model.ExportContext.Request.Before.Value.ToDate())}")
@($"Between {FormatDate(ExportContext.Request.After.Value.ToDate())} and {FormatDate(ExportContext.Request.Before.Value.ToDate())}")
}
else if (Model.ExportContext.Request.After is not null)
else if (ExportContext.Request.After is not null)
{
@($"After {FormatDate(Model.ExportContext.Request.After.Value.ToDate())}")
@($"After {FormatDate(ExportContext.Request.After.Value.ToDate())}")
}
else if (Model.ExportContext.Request.Before is not null)
else if (ExportContext.Request.Before is not null)
{
@($"Before {FormatDate(Model.ExportContext.Request.Before.Value.ToDate())}")
@($"Before {FormatDate(ExportContext.Request.Before.Value.ToDate())}")
}
</div>
}

@ -1,20 +0,0 @@
using System.Threading.Tasks;
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
internal class PreambleTemplateContext
{
public ExportContext ExportContext { get; }
public string ThemeName { get; }
public PreambleTemplateContext(ExportContext exportContext, string themeName)
{
ExportContext = exportContext;
ThemeName = themeName;
}
public ValueTask<string> FormatMarkdownAsync(string? markdown, bool isJumboAllowed = true) =>
HtmlMarkdownVisitor.FormatAsync(ExportContext, markdown ?? "", isJumboAllowed);
}

@ -71,15 +71,17 @@ internal class HtmlMessageWriter : MessageWriter
.Minify(html, false)
.MinifiedContent;
public override async ValueTask WritePreambleAsync(CancellationToken cancellationToken = default)
public override async ValueTask WritePreambleAsync(
CancellationToken cancellationToken = default)
{
var templateContext = new PreambleTemplateContext(Context, _themeName);
// We are not writing directly to output because Razor
// does not actually do asynchronous writes to stream.
await _writer.WriteLineAsync(
Minify(
await PreambleTemplate.RenderAsync(templateContext, cancellationToken)
await new PreambleTemplate
{
CancellationToken = cancellationToken,
ExportContext = Context,
ThemeName = _themeName
}.RenderAsync()
)
);
}
@ -88,13 +90,14 @@ internal class HtmlMessageWriter : MessageWriter
IReadOnlyList<Message> messages,
CancellationToken cancellationToken = default)
{
var templateContext = new MessageGroupTemplateContext(Context, messages);
// We are not writing directly to output because Razor
// does not actually do asynchronous writes to stream.
await _writer.WriteLineAsync(
Minify(
await MessageGroupTemplate.RenderAsync(templateContext, cancellationToken)
await new MessageGroupTemplate
{
CancellationToken = cancellationToken,
ExportContext = Context,
Messages = messages
}.RenderAsync()
)
);
}
@ -126,13 +129,14 @@ internal class HtmlMessageWriter : MessageWriter
if (_messageGroup.Any())
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
var templateContext = new PostambleTemplateContext(Context, MessagesWritten);
// We are not writing directly to output because Razor
// does not actually do asynchronous writes to stream.
await _writer.WriteLineAsync(
Minify(
await PostambleTemplate.RenderAsync(templateContext, cancellationToken)
await new PostambleTemplate
{
CancellationToken = cancellationToken,
ExportContext = Context,
MessagesWritten = MessagesWritten
}.RenderAsync()
)
);
}

@ -181,7 +181,7 @@
<LineBreak />
<Run Text="7. Select the" />
<Run FontWeight="SemiBold" Text="Headers" />
<Run Text=" tab on the right" />
<Run Text="tab on the right" />
<LineBreak />
<Run Text="8. Scroll down to the" />
<Run FontWeight="SemiBold" Text="Request Headers" />

Loading…
Cancel
Save