From e26a0660d1259994303dbc0e359e3abfc440c3d5 Mon Sep 17 00:00:00 2001 From: Alexey Golub Date: Tue, 28 Jul 2020 22:43:45 +0300 Subject: [PATCH] Use Razor instead of Scriban --- .../DiscordChatExporter.Domain.csproj | 7 +- .../Exporting/ExportContext.cs | 22 +- .../Exporting/Writers/CsvMessageWriter.cs | 2 +- .../Writers/Html/LayoutTemplate-Begin.cshtml | 101 ++++++++ .../Writers/Html/LayoutTemplate-End.cshtml | 12 + .../Writers/Html/LayoutTemplate.html | 96 -------- .../Writers/Html/LayoutTemplateContext.cs | 18 ++ .../Exporting/Writers/Html/MessageGroup.cs | 4 +- .../Writers/Html/MessageGroupTemplate.cshtml | 229 ++++++++++++++++++ .../Writers/Html/MessageGroupTemplate.html | 184 -------------- .../Html/MessageGroupTemplateContext.cs | 20 ++ .../Exporting/Writers/HtmlMessageWriter.cs | 147 ++--------- .../Writers/PlainTextMessageWriter.cs | 7 +- DiscordChatExporter.props | 1 + 14 files changed, 429 insertions(+), 421 deletions(-) create mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-Begin.cshtml create mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-End.cshtml delete mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate.html create mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplateContext.cs create mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.cshtml delete mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.html create mode 100644 DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplateContext.cs diff --git a/DiscordChatExporter.Domain/DiscordChatExporter.Domain.csproj b/DiscordChatExporter.Domain/DiscordChatExporter.Domain.csproj index 5644bf7..49465ff 100644 --- a/DiscordChatExporter.Domain/DiscordChatExporter.Domain.csproj +++ b/DiscordChatExporter.Domain/DiscordChatExporter.Domain.csproj @@ -3,16 +3,17 @@ - + - - + + + diff --git a/DiscordChatExporter.Domain/Exporting/ExportContext.cs b/DiscordChatExporter.Domain/Exporting/ExportContext.cs index 7c8eed8..f2bd243 100644 --- a/DiscordChatExporter.Domain/Exporting/ExportContext.cs +++ b/DiscordChatExporter.Domain/Exporting/ExportContext.cs @@ -1,14 +1,17 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using DiscordChatExporter.Domain.Discord.Models; +using DiscordChatExporter.Domain.Internal.Extensions; namespace DiscordChatExporter.Domain.Exporting { - internal class ExportContext + public class ExportContext { private readonly MediaDownloader _mediaDownloader; @@ -34,6 +37,8 @@ namespace DiscordChatExporter.Domain.Exporting _mediaDownloader = new MediaDownloader(request.OutputMediaDirPath); } + public string FormatDate(DateTimeOffset date) => date.ToLocalString(Request.DateFormat); + public Member? TryGetMember(string id) => Members.FirstOrDefault(m => m.Id == id); @@ -43,9 +48,9 @@ namespace DiscordChatExporter.Domain.Exporting public Role? TryGetRole(string id) => Roles.FirstOrDefault(r => r.Id == id); - public Color? TryGetUserColor(User user) + public Color? TryGetUserColor(string id) { - var member = TryGetMember(user.Id); + var member = TryGetMember(id); var roles = member?.RoleIds.Join(Roles, i => i, r => r.Id, (_, role) => role); return roles? @@ -55,7 +60,6 @@ namespace DiscordChatExporter.Domain.Exporting .FirstOrDefault(); } - // HACK: ConfigureAwait() is crucial here to enable sync-over-async in HtmlMessageWriter public async ValueTask ResolveMediaUrlAsync(string url) { if (!Request.ShouldDownloadMedia) @@ -63,10 +67,12 @@ namespace DiscordChatExporter.Domain.Exporting try { - var filePath = await _mediaDownloader.DownloadAsync(url).ConfigureAwait(false); + var filePath = await _mediaDownloader.DownloadAsync(url); + + // We want relative path so that the output files can be copied around without breaking + var relativeFilePath = Path.GetRelativePath(Request.OutputBaseDirPath, filePath); - // Return relative path so that the output files can be copied around without breaking - return Path.GetRelativePath(Request.OutputBaseDirPath, filePath); + return $"file:///./{Uri.EscapeDataString(relativeFilePath)}"; } catch (HttpRequestException) { diff --git a/DiscordChatExporter.Domain/Exporting/Writers/CsvMessageWriter.cs b/DiscordChatExporter.Domain/Exporting/Writers/CsvMessageWriter.cs index 210e30a..c4f33fc 100644 --- a/DiscordChatExporter.Domain/Exporting/Writers/CsvMessageWriter.cs +++ b/DiscordChatExporter.Domain/Exporting/Writers/CsvMessageWriter.cs @@ -67,7 +67,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers await _writer.WriteAsync(','); // Message timestamp - await _writer.WriteAsync(CsvEncode(message.Timestamp.ToLocalString(Context.Request.DateFormat))); + await _writer.WriteAsync(CsvEncode(Context.FormatDate(message.Timestamp))); await _writer.WriteAsync(','); // Message content diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-Begin.cshtml b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-Begin.cshtml new file mode 100644 index 0000000..af808f5 --- /dev/null +++ b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-Begin.cshtml @@ -0,0 +1,101 @@ +@using RazorLight +@using DiscordChatExporter.Domain.Exporting.Writers.Html +@using Tyrrrz.Extensions +@inherits TemplatePage + +@{ + string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date); + + ValueTask ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url); + + // Workaround: https://github.com/toddams/RazorLight/issues/359 + string RenderStyleSheet(string name) => Model.GetType().Assembly.GetManifestResourceString($"DiscordChatExporter.Domain.Exporting.Writers.Html.{name}.css"); +} + + + + + + @Model.ExportContext.Request.Guild.Name - @Model.ExportContext.Request.Channel.Name + + + + + + + + + + + + + + +
+
+ Guild icon +
+
+
@Model.ExportContext.Request.Guild.Name
+
@Model.ExportContext.Request.Channel.Category / @Model.ExportContext.Request.Channel.Name
+ + @if (!string.IsNullOrWhiteSpace(Model.ExportContext.Request.Channel.Topic)) + { +
@Model.ExportContext.Request.Channel.Topic
+ } + + @if (Model.ExportContext.Request.After != null || Model.ExportContext.Request.Before != null) + { +
+ @if (Model.ExportContext.Request.After != null && Model.ExportContext.Request.Before != null) + { + @($"Between {FormatDate(Model.ExportContext.Request.After.Value)} and {FormatDate(Model.ExportContext.Request.Before.Value)}") + } + else if (Model.ExportContext.Request.After != null) + { + @($"After {FormatDate(Model.ExportContext.Request.After.Value)}") + } + else if (Model.ExportContext.Request.Before != null) + { + @($"Before {FormatDate(Model.ExportContext.Request.Before.Value)}") + } +
+ } +
+
+ +
\ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-End.cshtml b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-End.cshtml new file mode 100644 index 0000000..69dec67 --- /dev/null +++ b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate-End.cshtml @@ -0,0 +1,12 @@ +@using RazorLight +@using DiscordChatExporter.Domain.Exporting.Writers.Html +@inherits TemplatePage + +
+ +
+
Exported @Model.MessageCount.ToString("N0") message(s)
+
+ + + \ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate.html b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate.html deleted file mode 100644 index 6f11eb6..0000000 --- a/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplate.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - {{~ # Metadata ~}} - {{ Context.Request.Guild.Name | html.escape }} - {{ Context.Request.Channel.Name | html.escape }} - - - - {{~ # Styles ~}} - - - - {{~ # Syntax highlighting ~}} - - - - - {{~ # Local scripts ~}} - - - - -{{~ # Preamble ~}} -
-
- Guild icon -
-
-
{{ Context.Request.Guild.Name | html.escape }}
-
{{ Context.Request.Channel.Category | html.escape }} / {{ Context.Request.Channel.Name | html.escape }}
- - {{~ if Context.Request.Channel.Topic ~}} -
{{ Context.Request.Channel.Topic | html.escape }}
- {{~ end ~}} - - {{~ if Context.Request.After || Context.Request.Before ~}} -
- {{~ if Context.Request.After && Context.Request.Before ~}} - Between {{ Context.Request.After | FormatDate | html.escape }} and {{ Context.Request.Before | FormatDate | html.escape }} - {{~ else if Context.Request.After ~}} - After {{ Context.Request.After | FormatDate | html.escape }} - {{~ else if Context.Request.Before ~}} - Before {{ Context.Request.Before | FormatDate | html.escape }} - {{~ end ~}} -
- {{~ end ~}} -
-
- -{{~ # Log ~}} -
- {{~ %SPLIT% ~}} -
- -{{~ # Postamble ~}} -
-
Exported {{ MessageCount | object.format "N0" }} message(s)
-
- - - \ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplateContext.cs b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplateContext.cs new file mode 100644 index 0000000..1930f4e --- /dev/null +++ b/DiscordChatExporter.Domain/Exporting/Writers/Html/LayoutTemplateContext.cs @@ -0,0 +1,18 @@ +namespace DiscordChatExporter.Domain.Exporting.Writers.Html +{ + public class LayoutTemplateContext + { + public ExportContext ExportContext { get; } + + public string ThemeName { get; } + + public long MessageCount { get; } + + public LayoutTemplateContext(ExportContext exportContext, string themeName, long messageCount) + { + ExportContext = exportContext; + ThemeName = themeName; + MessageCount = messageCount; + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroup.cs b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroup.cs index e747f51..f0ad385 100644 --- a/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroup.cs +++ b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroup.cs @@ -6,7 +6,7 @@ using DiscordChatExporter.Domain.Discord.Models; namespace DiscordChatExporter.Domain.Exporting.Writers.Html { // Used for grouping contiguous messages in HTML export - internal partial class MessageGroup + public partial class MessageGroup { public User Author { get; } @@ -22,7 +22,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers.Html } } - internal partial class MessageGroup + public partial class MessageGroup { public static bool CanJoin(Message message1, Message message2) => string.Equals(message1.Author.Id, message2.Author.Id, StringComparison.Ordinal) && diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.cshtml b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.cshtml new file mode 100644 index 0000000..88357d5 --- /dev/null +++ b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.cshtml @@ -0,0 +1,229 @@ +@using RazorLight +@using DiscordChatExporter.Domain.Exporting.Writers.Html +@inherits TemplatePage + +@{ + string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date); + + string FormatMarkdown(string markdown) => Model.FormatMarkdown(markdown); + + string FormatEmbedMarkdown(string markdown) => Model.FormatMarkdown(markdown, false); + + ValueTask ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url); + + var userMember = Model.ExportContext.TryGetMember(Model.MessageGroup.Author.Id); + var userColor = Model.ExportContext.TryGetUserColor(Model.MessageGroup.Author.Id); + var userNick = userMember?.Nick ?? Model.MessageGroup.Author.Name; + + var userColorStyle = userColor != null + ? $"color: rgb({userColor?.R},{userColor?.G},{userColor?.B})" + : null; +} + +
+
+ Avatar +
+
+ @userNick + + @if (Model.MessageGroup.Author.IsBot) + { + BOT + } + + @FormatDate(Model.MessageGroup.Timestamp) + + @foreach (var message in Model.MessageGroup.Messages) + { + var isPinnedStyle = message.IsPinned ? "chatlog__message--pinned" : null; + +
+
+
@Raw(FormatMarkdown(message.Content)) @if (message.EditedTimestamp != null) {(edited)}
+
+ + @foreach (var attachment in message.Attachments) + { + + } + + @foreach (var embed in message.Embeds) + { +
+ @if (embed.Color != null) + { + var embedColorStyle = $"background-color: rgba({embed.Color?.R},{embed.Color?.G},{embed.Color?.B},{embed.Color?.A})"; +
+ } + else + { +
+ } + +
+
+
+ @if (embed.Author != null) + { +
+ @if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl)) + { + Author icon + } + + @if (!string.IsNullOrWhiteSpace(embed.Author.Name)) + { + + @if (!string.IsNullOrWhiteSpace(embed.Author.Url)) + { + @embed.Author.Name + } + else + { + @embed.Author.Name + } + + } +
+ } + + @if (!string.IsNullOrWhiteSpace(embed.Title)) + { +
+ @if (!string.IsNullOrWhiteSpace(embed.Url)) + { + +
@Raw(FormatEmbedMarkdown(embed.Title))
+
+ } + else + { +
@Raw(FormatEmbedMarkdown(embed.Title))
+ } +
+ } + + @if (!string.IsNullOrWhiteSpace(embed.Description)) + { +
+
@Raw(FormatEmbedMarkdown(embed.Description))
+
+ } + + @if (embed.Fields.Any()) + { +
+ @foreach (var field in embed.Fields) + { + var isInlineStyle = field.IsInline ? "chatlog__embed-field--inline" : null; + +
+ @if (!string.IsNullOrWhiteSpace(field.Name)) + { +
+
@Raw(FormatEmbedMarkdown(field.Name))
+
+ } + + @if (!string.IsNullOrWhiteSpace(field.Value)) + { +
+
@Raw(FormatEmbedMarkdown(field.Value))
+
+ } +
+ } +
+ } +
+ + @if (embed.Thumbnail != null) + { +
+ + Thumbnail + +
+ } +
+ + @if (embed.Image != null) + { +
+ + Image + +
+ } + + @if (embed.Footer != null || embed.Timestamp != null) + { + + } +
+
+ } + + @if (message.Reactions.Any()) + { +
+ @foreach (var reaction in message.Reactions) + { +
+ @reaction.Emoji.Name + @reaction.Count +
+ } +
+ } +
+ } +
+
\ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.html b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.html deleted file mode 100644 index 71d5657..0000000 --- a/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplate.html +++ /dev/null @@ -1,184 +0,0 @@ -
- {{~ # Avatar ~}} -
- Avatar -
-
- {{~ # Author name and timestamp ~}} - {{~ userColor = TryGetUserColor MessageGroup.Author | FormatColorRgb ~}} - {{ (TryGetUserNick MessageGroup.Author ?? MessageGroup.Author.Name) | html.escape }} - - {{~ # Bot tag ~}} - {{~ if MessageGroup.Author.IsBot ~}} - BOT - {{~ end ~}} - - {{ MessageGroup.Timestamp | FormatDate | html.escape }} - - {{~ # Messages ~}} - {{~ for message in MessageGroup.Messages ~}} -
- {{~ # Content ~}} - {{~ if message.Content ~}} -
-
- {{- message.Content | FormatMarkdown -}} - - {{- # Edited timestamp -}} - {{- if message.EditedTimestamp -}} - {{-}}(edited){{-}} - {{- end -}} -
-
- {{~ end ~}} - - {{~ # Attachments ~}} - {{~ for attachment in message.Attachments ~}} - - {{~ end ~}} - - {{~ # Embeds ~}} - {{~ for embed in message.Embeds ~}} -
- {{~ if embed.Color ~}} -
- {{~ else ~}} -
- {{~ end ~}} -
-
-
- {{~ # Author ~}} - {{~ if embed.Author ~}} -
- {{~ if embed.Author.IconUrl ~}} - Author icon - {{~ end ~}} - - {{~ if embed.Author.Name ~}} - - {{~ if embed.Author.Url ~}} - {{ embed.Author.Name | html.escape }} - {{~ else ~}} - {{ embed.Author.Name | html.escape }} - {{~ end ~}} - - {{~ end ~}} -
- {{~ end ~}} - - {{~ # Title ~}} - {{~ if embed.Title ~}} -
- {{~ if embed.Url ~}} -
{{ embed.Title | FormatEmbedMarkdown }}
- {{~ else ~}} -
{{ embed.Title | FormatEmbedMarkdown }}
- {{~ end ~}} -
- {{~ end ~}} - - {{~ # Description ~}} - {{~ if embed.Description ~}} -
{{ embed.Description | FormatEmbedMarkdown }}
- {{~ end ~}} - - {{~ # Fields ~}} - {{~ if embed.Fields | array.size > 0 ~}} -
- {{~ for field in embed.Fields ~}} -
- {{~ if field.Name ~}} -
{{ field.Name | FormatEmbedMarkdown }}
- {{~ end ~}} - {{~ if field.Value ~}} -
{{ field.Value | FormatEmbedMarkdown }}
- {{~ end ~}} -
- {{~ end ~}} -
- {{~ end ~}} -
- - {{~ # Thumbnail ~}} - {{~ if embed.Thumbnail ~}} -
- - Thumbnail - -
- {{~ end ~}} -
- - {{~ # Image ~}} - {{~ if embed.Image ~}} -
- - Image - -
- {{~ end ~}} - - {{~ # Footer ~}} - {{~ if embed.Footer || embed.Timestamp ~}} - - {{~ end ~}} -
-
- {{~ end ~}} - - {{~ # Reactions ~}} - {{~ if message.Reactions | array.size > 0 ~}} -
- {{~ for reaction in message.Reactions ~}} -
- {{ reaction.Emoji.Name }} - {{ reaction.Count }} -
- {{~ end ~}} -
- {{~ end ~}} -
- {{~ end ~}} -
-
\ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplateContext.cs b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplateContext.cs new file mode 100644 index 0000000..08c222b --- /dev/null +++ b/DiscordChatExporter.Domain/Exporting/Writers/Html/MessageGroupTemplateContext.cs @@ -0,0 +1,20 @@ +using DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors; + +namespace DiscordChatExporter.Domain.Exporting.Writers.Html +{ + public class MessageGroupTemplateContext + { + public ExportContext ExportContext { get; } + + public MessageGroup MessageGroup { get; } + + public MessageGroupTemplateContext(ExportContext exportContext, MessageGroup messageGroup) + { + ExportContext = exportContext; + MessageGroup = messageGroup; + } + + public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) => + HtmlMarkdownVisitor.Format(ExportContext, markdown ?? "", isJumboAllowed); + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/HtmlMessageWriter.cs b/DiscordChatExporter.Domain/Exporting/Writers/HtmlMessageWriter.cs index 0ef099b..89a976b 100644 --- a/DiscordChatExporter.Domain/Exporting/Writers/HtmlMessageWriter.cs +++ b/DiscordChatExporter.Domain/Exporting/Writers/HtmlMessageWriter.cs @@ -1,31 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Drawing; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using DiscordChatExporter.Domain.Discord.Models; using DiscordChatExporter.Domain.Exporting.Writers.Html; -using DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors; -using DiscordChatExporter.Domain.Internal.Extensions; -using Scriban; -using Scriban.Runtime; -using Tyrrrz.Extensions; +using RazorLight; namespace DiscordChatExporter.Domain.Exporting.Writers { - internal partial class HtmlMessageWriter : MessageWriter + internal class HtmlMessageWriter : MessageWriter { private readonly TextWriter _writer; private readonly string _themeName; + private readonly RazorLightEngine _templateEngine; private readonly List _messageGroupBuffer = new List(); - private readonly Template _preambleTemplate; - private readonly Template _messageGroupTemplate; - private readonly Template _postambleTemplate; - private long _messageCount; public HtmlMessageWriter(Stream stream, ExportContext context, string themeName) @@ -34,88 +24,28 @@ namespace DiscordChatExporter.Domain.Exporting.Writers _writer = new StreamWriter(stream); _themeName = themeName; - _preambleTemplate = Template.Parse(GetPreambleTemplateCode()); - _messageGroupTemplate = Template.Parse(GetMessageGroupTemplateCode()); - _postambleTemplate = Template.Parse(GetPostambleTemplateCode()); + _templateEngine = new RazorLightEngineBuilder() + .EnableEncoding() + .UseEmbeddedResourcesProject(typeof(HtmlMessageWriter).Assembly, $"{typeof(HtmlMessageWriter).Namespace}.Html") + .Build(); } - private TemplateContext CreateTemplateContext(IReadOnlyDictionary? constants = null) + public override async ValueTask WritePreambleAsync() { - // Template context - var templateContext = new TemplateContext - { - MemberRenamer = m => m.Name, - MemberFilter = m => true, - LoopLimit = int.MaxValue, - StrictVariables = true - }; - - // Model - var scriptObject = new ScriptObject(); - - // Constants - scriptObject.SetValue("Context", Context, true); - scriptObject.SetValue("CoreStyleSheet", GetCoreStyleSheetCode(), true); - scriptObject.SetValue("ThemeStyleSheet", GetThemeStyleSheetCode(_themeName), true); - scriptObject.SetValue("HighlightJsStyleName", $"solarized-{_themeName.ToLowerInvariant()}", true); - - // Additional constants - if (constants != null) - { - foreach (var (member, value) in constants) - scriptObject.SetValue(member, value, true); - } - - // Functions - scriptObject.Import("FormatDate", - new Func(d => d.ToLocalString(Context.Request.DateFormat))); - - scriptObject.Import("FormatColorRgb", - new Func(c => c != null ? $"rgb({c?.R}, {c?.G}, {c?.B})" : null)); - - scriptObject.Import("TryGetUserColor", - new Func(Context.TryGetUserColor)); - - scriptObject.Import("TryGetUserNick", - new Func(u => Context.TryGetMember(u.Id)?.Nick)); - - scriptObject.Import("FormatMarkdown", - new Func(m => FormatMarkdown(m))); - - scriptObject.Import("FormatEmbedMarkdown", - new Func(m => FormatMarkdown(m, false))); - - // HACK: Scriban doesn't support async, so we have to resort to this and be careful about deadlocks. - // TODO: move to Razor. - scriptObject.Import("ResolveUrl", - new Func(u => Context.ResolveMediaUrlAsync(u).GetAwaiter().GetResult())); + var templateContext = new LayoutTemplateContext(Context, _themeName, _messageCount); - // Push model - templateContext.PushGlobal(scriptObject); - - // Push output - templateContext.PushOutput(new TextWriterOutput(_writer)); - - return templateContext; + await _writer.WriteLineAsync( + await _templateEngine.CompileRenderAsync("LayoutTemplate-Begin.cshtml", templateContext) + ); } - private string FormatMarkdown(string? markdown, bool isJumboAllowed = true) => - HtmlMarkdownVisitor.Format(Context, markdown ?? "", isJumboAllowed); - - private async ValueTask WriteCurrentMessageGroupAsync() + private async ValueTask WriteMessageGroupAsync(MessageGroup messageGroup) { - var templateContext = CreateTemplateContext(new Dictionary - { - ["MessageGroup"] = MessageGroup.Join(_messageGroupBuffer) - }); - - await templateContext.EvaluateAsync(_messageGroupTemplate.Page); - } + var templateContext = new MessageGroupTemplateContext(Context, messageGroup); - public override async ValueTask WritePreambleAsync() - { - var templateContext = CreateTemplateContext(); - await templateContext.EvaluateAsync(_preambleTemplate.Page); + await _writer.WriteLineAsync( + await _templateEngine.CompileRenderAsync("MessageGroupTemplate.cshtml", templateContext) + ); } public override async ValueTask WriteMessageAsync(Message message) @@ -128,7 +58,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers // Otherwise, flush the group and render messages else { - await WriteCurrentMessageGroupAsync(); + await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer)); _messageGroupBuffer.Clear(); _messageGroupBuffer.Add(message); @@ -142,14 +72,13 @@ namespace DiscordChatExporter.Domain.Exporting.Writers { // Flush current message group if (_messageGroupBuffer.Any()) - await WriteCurrentMessageGroupAsync(); + await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer)); - var templateContext = CreateTemplateContext(new Dictionary - { - ["MessageCount"] = _messageCount - }); + var templateContext = new LayoutTemplateContext(Context, _themeName, _messageCount); - await templateContext.EvaluateAsync(_postambleTemplate.Page); + await _writer.WriteLineAsync( + await _templateEngine.CompileRenderAsync("LayoutTemplate-End.cshtml", templateContext) + ); } public override async ValueTask DisposeAsync() @@ -158,32 +87,4 @@ namespace DiscordChatExporter.Domain.Exporting.Writers await base.DisposeAsync(); } } - - internal partial class HtmlMessageWriter - { - private static readonly Assembly ResourcesAssembly = typeof(HtmlMessageWriter).Assembly; - private static readonly string ResourcesNamespace = $"{ResourcesAssembly.GetName().Name}.Exporting.Writers.Html"; - - private static string GetCoreStyleSheetCode() => - ResourcesAssembly - .GetManifestResourceString($"{ResourcesNamespace}.Core.css"); - - private static string GetThemeStyleSheetCode(string themeName) => - ResourcesAssembly - .GetManifestResourceString($"{ResourcesNamespace}.{themeName}.css"); - - private static string GetPreambleTemplateCode() => - ResourcesAssembly - .GetManifestResourceString($"{ResourcesNamespace}.LayoutTemplate.html") - .SubstringUntil("{{~ %SPLIT% ~}}"); - - private static string GetMessageGroupTemplateCode() => - ResourcesAssembly - .GetManifestResourceString($"{ResourcesNamespace}.MessageGroupTemplate.html"); - - private static string GetPostambleTemplateCode() => - ResourcesAssembly - .GetManifestResourceString($"{ResourcesNamespace}.LayoutTemplate.html") - .SubstringAfter("{{~ %SPLIT% ~}}"); - } } \ No newline at end of file diff --git a/DiscordChatExporter.Domain/Exporting/Writers/PlainTextMessageWriter.cs b/DiscordChatExporter.Domain/Exporting/Writers/PlainTextMessageWriter.cs index c34f11f..9347722 100644 --- a/DiscordChatExporter.Domain/Exporting/Writers/PlainTextMessageWriter.cs +++ b/DiscordChatExporter.Domain/Exporting/Writers/PlainTextMessageWriter.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using DiscordChatExporter.Domain.Discord.Models; using DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors; -using DiscordChatExporter.Domain.Internal.Extensions; using Tyrrrz.Extensions; namespace DiscordChatExporter.Domain.Exporting.Writers @@ -27,7 +26,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers private async ValueTask WriteMessageHeaderAsync(Message message) { // Timestamp & author - await _writer.WriteAsync($"[{message.Timestamp.ToLocalString(Context.Request.DateFormat)}]"); + await _writer.WriteAsync($"[{Context.FormatDate(message.Timestamp)}]"); await _writer.WriteAsync($" {message.Author.FullName}"); // Whether the message is pinned @@ -120,10 +119,10 @@ namespace DiscordChatExporter.Domain.Exporting.Writers await _writer.WriteLineAsync($"Topic: {Context.Request.Channel.Topic}"); if (Context.Request.After != null) - await _writer.WriteLineAsync($"After: {Context.Request.After.Value.ToLocalString(Context.Request.DateFormat)}"); + await _writer.WriteLineAsync($"After: {Context.FormatDate(Context.Request.After.Value)}"); if (Context.Request.Before != null) - await _writer.WriteLineAsync($"Before: {Context.Request.Before.Value.ToLocalString(Context.Request.DateFormat)}"); + await _writer.WriteLineAsync($"Before: {Context.FormatDate(Context.Request.Before.Value)}"); await _writer.WriteLineAsync('='.Repeat(62)); await _writer.WriteLineAsync(); diff --git a/DiscordChatExporter.props b/DiscordChatExporter.props index b1ba65e..de597d2 100644 --- a/DiscordChatExporter.props +++ b/DiscordChatExporter.props @@ -7,6 +7,7 @@ Copyright (c) Alexey Golub latest enable + true \ No newline at end of file