mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-22 10:55:15 -04:00
Refactor
This commit is contained in:
parent
8612d2c84a
commit
a80ee2943f
6 changed files with 445 additions and 440 deletions
|
@ -1,6 +1,4 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
|
||||
// https://discord.com/developers/docs/resources/channel#message-object-message-types
|
||||
public enum MessageKind
|
||||
|
@ -18,10 +16,6 @@ public enum MessageKind
|
|||
|
||||
public static class MessageKindExtensions
|
||||
{
|
||||
public static bool IsSystemMessage(this MessageKind c) =>
|
||||
public static bool IsSystemNotification(this MessageKind c) =>
|
||||
c is not MessageKind.Default and not MessageKind.Reply;
|
||||
|
||||
public static string ToCssIdFormat(this MessageKind c) =>
|
||||
string.Join("-", Regex.Split(c.ToString(), @"(?<!^)(?=[A-Z])")).ToLowerInvariant();
|
||||
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Gress" Version="2.0.1" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="MiniRazor.CodeGen" Version="2.2.1" />
|
||||
<PackageReference Include="MiniRazor.CodeGen" Version="2.2.2" />
|
||||
<PackageReference Include="Polly" Version="7.2.3" />
|
||||
<PackageReference Include="Superpower" Version="3.0.0" />
|
||||
<PackageReference Include="WebMarkupMin.Core" Version="2.12.0" />
|
||||
|
|
|
@ -46,129 +46,126 @@
|
|||
<div class="chatlog__message-group">
|
||||
@foreach (var (message, i) in Model.Messages.WithIndex())
|
||||
{
|
||||
var isSystemMessage = message.Kind.IsSystemMessage();
|
||||
|
||||
var isFirst = i == 0;
|
||||
|
||||
<div id="chatlog__message-container-@message.Id" class="chatlog__message-container @(message.IsPinned ? "chatlog__message-container--pinned" : null)" data-message-id="@message.Id">
|
||||
<div class="chatlog__message">
|
||||
@{/* Left side */}
|
||||
<div class="chatlog__message-aside">
|
||||
@if (isSystemMessage)
|
||||
{
|
||||
<svg class=chatlog__system-message-icon><use href="#@message.Kind.ToCssIdFormat()-icon"></use></svg>
|
||||
}
|
||||
else if(isFirst)
|
||||
{
|
||||
// Reference symbol
|
||||
if (message.Reference is not null)
|
||||
{
|
||||
<div class="chatlog__reference-symbol"></div>
|
||||
}
|
||||
@{/* System notification */}
|
||||
@if (message.Kind.IsSystemNotification())
|
||||
{
|
||||
// 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);
|
||||
userNick = message.Author.IsBot
|
||||
? message.Author.Name
|
||||
: userMember?.Nick ?? message.Author.Name;
|
||||
|
||||
// Avatar
|
||||
<img class="chatlog__avatar" src="@await ResolveUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy">
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div>
|
||||
}
|
||||
</div>
|
||||
<div class="chatlog__message-aside">
|
||||
<svg class=chatlog__system-notification-icon><use href="#@message.Kind.ToString().ToDashCase().ToLowerInvariant()-icon"></use></svg>
|
||||
</div>
|
||||
|
||||
@{/* Right side */}
|
||||
<div class="chatlog__message-primary">
|
||||
@if (isSystemMessage)
|
||||
{
|
||||
|
||||
// system messages 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);
|
||||
|
||||
userNick = message.Author.IsBot
|
||||
? message.Author.Name
|
||||
: userMember?.Nick ?? message.Author.Name;
|
||||
|
||||
<div class="chatlog__header">
|
||||
@{/* Author name */}
|
||||
<span class="chatlog__author" style="@(userColor is not null ? $"color: rgb({userColor.Value.R}, {userColor.Value.G}, {userColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@userNick</span>
|
||||
|
||||
@{/* System message content */}
|
||||
@if(message.Kind == MessageKind.ChannelPinnedMessage)
|
||||
{
|
||||
<span class="chatlog__system-message"> pinned</span><span class="chatlog__system-message-reference-link chatlog__reference-link" onclick="scrollToMessage(event, @message.Reference.MessageId)"> a message</span><span class="chatlog__system-message"> to this channel.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="chatlog__system-message"> <!--wmm:ignore-->@Raw(FormatMarkdown(Char.ToLowerInvariant(message.Content[0]) + message.Content.Substring(1)))<!--/wmm:ignore--></span>
|
||||
}
|
||||
|
||||
@{/* Timestamp */}
|
||||
<span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
|
||||
</div>
|
||||
}
|
||||
else if (isFirst)
|
||||
{
|
||||
// Reference
|
||||
if (message.Reference is not null)
|
||||
{
|
||||
<div class="chatlog__reference">
|
||||
@if (message.ReferencedMessage is not null)
|
||||
{
|
||||
<img class="chatlog__reference-avatar" src="@await ResolveUrlAsync(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-content">
|
||||
<span class="chatlog__reference-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')">
|
||||
@if (!string.IsNullOrWhiteSpace(message.ReferencedMessage.Content) && !message.ReferencedMessage.IsContentHidden())
|
||||
{
|
||||
<!--wmm:ignore-->@Raw(FormatEmbedMarkdown(message.ReferencedMessage.Content))<!--/wmm:ignore-->
|
||||
}
|
||||
else if (message.ReferencedMessage.Attachments.Any() || message.ReferencedMessage.Embeds.Any())
|
||||
{
|
||||
<em>Click to see attachment</em>
|
||||
<span>🖼️</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<em>Click to see original message</em>
|
||||
}
|
||||
</span>
|
||||
|
||||
@if (message.ReferencedMessage.EditedTimestamp is not null)
|
||||
{
|
||||
<span class="chatlog__reference-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value)">(edited)</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__reference-unknown">
|
||||
Original message was deleted or could not be loaded.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
// Header
|
||||
<div class="chatlog__message-primary">
|
||||
<div class="chatlog__header">
|
||||
@{/* Author name */}
|
||||
<span class="chatlog__author" style="@(userColor is not null ? $"color: rgb({userColor.Value.R}, {userColor.Value.G}, {userColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@userNick</span>
|
||||
|
||||
@{/* Bot label */}
|
||||
@if (message.Author.IsBot)
|
||||
@{/* System notification content */}
|
||||
@if(message.Kind == MessageKind.ChannelPinnedMessage)
|
||||
{
|
||||
<span class="chatlog__bot-label">BOT</span>
|
||||
<span class="chatlog__system-notification"> pinned</span><span class="chatlog__system-message-reference-link chatlog__reference-link" onclick="scrollToMessage(event, '@message.Reference?.MessageId')"> a message</span><span class="chatlog__system-message"> to this channel.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="chatlog__system-notification">@(char.ToLowerInvariant(message.Content[0]) + message.Content[1..])</span>
|
||||
}
|
||||
|
||||
@{/* Timestamp */}
|
||||
<span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
// Regular message
|
||||
else
|
||||
{
|
||||
<div class="chatlog__message-aside">
|
||||
@if (isFirst)
|
||||
{
|
||||
// Reference symbol
|
||||
if (message.Reference is not null)
|
||||
{
|
||||
<div class="chatlog__reference-symbol"></div>
|
||||
}
|
||||
|
||||
@{/* Content */}
|
||||
@if (!isSystemMessage)
|
||||
{
|
||||
// Avatar
|
||||
<img class="chatlog__avatar" src="@await ResolveUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy">
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="chatlog__message-primary">
|
||||
@if (isFirst)
|
||||
{
|
||||
// Reference
|
||||
if (message.Reference is not null)
|
||||
{
|
||||
<div class="chatlog__reference">
|
||||
@if (message.ReferencedMessage is not null)
|
||||
{
|
||||
<img class="chatlog__reference-avatar" src="@await ResolveUrlAsync(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-content">
|
||||
<span class="chatlog__reference-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')">
|
||||
@if (!string.IsNullOrWhiteSpace(message.ReferencedMessage.Content) && !message.ReferencedMessage.IsContentHidden())
|
||||
{
|
||||
<!--wmm:ignore-->@Raw(FormatEmbedMarkdown(message.ReferencedMessage.Content))<!--/wmm:ignore-->
|
||||
}
|
||||
else if (message.ReferencedMessage.Attachments.Any() || message.ReferencedMessage.Embeds.Any())
|
||||
{
|
||||
<em>Click to see attachment</em>
|
||||
<span>🖼️</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<em>Click to see original message</em>
|
||||
}
|
||||
</span>
|
||||
|
||||
@if (message.ReferencedMessage.EditedTimestamp is not null)
|
||||
{
|
||||
<span class="chatlog__reference-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value)">(edited)</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__reference-unknown">
|
||||
Original message was deleted or could not be loaded.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
// Header
|
||||
<div class="chatlog__header">
|
||||
@{/* Author name */}
|
||||
<span class="chatlog__author" style="@(userColor is not null ? $"color: rgb({userColor.Value.R}, {userColor.Value.G}, {userColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@userNick</span>
|
||||
|
||||
@{/* Bot label */}
|
||||
@if (message.Author.IsBot)
|
||||
{
|
||||
<span class="chatlog__bot-label">BOT</span>
|
||||
}
|
||||
|
||||
@{/* Timestamp */}
|
||||
<span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Content */}
|
||||
@if (!string.IsNullOrWhiteSpace(message.Content) || message.EditedTimestamp is not null)
|
||||
{
|
||||
<div class="chatlog__content chatlog__markdown">
|
||||
|
@ -185,340 +182,340 @@
|
|||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@{/* Attachments */}
|
||||
@foreach (var attachment in message.Attachments)
|
||||
{
|
||||
<div class="chatlog__attachment @(attachment.IsSpoiler ? "chatlog__attachment--hidden" : null)" onclick="@(attachment.IsSpoiler ? "showSpoiler(event, this)" : null)">
|
||||
@{/* Spoiler caption */}
|
||||
@if (attachment.IsSpoiler)
|
||||
{
|
||||
<div class="chatlog__attachment-spoiler-caption">SPOILER</div>
|
||||
}
|
||||
|
||||
@{/* Attachment preview */}
|
||||
@if (attachment.IsImage)
|
||||
{
|
||||
<a href="@await ResolveUrlAsync(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">
|
||||
</a>
|
||||
}
|
||||
else if (attachment.IsVideo)
|
||||
{
|
||||
<video class="chatlog__attachment-media" controls>
|
||||
<source src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Video attachment")" title="Video: @attachment.FileName (@attachment.FileSize)">
|
||||
</video>
|
||||
}
|
||||
else if (attachment.IsAudio)
|
||||
{
|
||||
<audio class="chatlog__attachment-media" controls>
|
||||
<source src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Audio attachment")" title="Audio: @attachment.FileName (@attachment.FileSize)">
|
||||
</audio>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__attachment-generic">
|
||||
<svg class="chatlog__attachment-generic-icon">
|
||||
<use href="#attachment-icon"/>
|
||||
</svg>
|
||||
<div class="chatlog__attachment-generic-name">
|
||||
<a href="@await ResolveUrlAsync(attachment.Url)">
|
||||
@attachment.FileName
|
||||
</a>
|
||||
</div>
|
||||
<div class="chatlog__attachment-generic-size">
|
||||
@attachment.FileSize
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embeds */}
|
||||
@foreach (var embed in message.Embeds)
|
||||
{
|
||||
// Spotify embed
|
||||
if (embed.TryGetSpotifyTrack() is { } spotifyTrackEmbed)
|
||||
@{/* Attachments */}
|
||||
@foreach (var attachment in message.Attachments)
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
<div class="chatlog__embed-spotify-container">
|
||||
<iframe class="chatlog__embed-spotify" src="@spotifyTrackEmbed.Url" width="400" height="80" allowtransparency="true" allow="encrypted-media"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
// YouTube embed
|
||||
else if (embed.TryGetYouTubeVideo() is { } youTubeVideoEmbed)
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
@{/* Color pill */}
|
||||
@if (embed.Color is not null)
|
||||
<div class="chatlog__attachment @(attachment.IsSpoiler ? "chatlog__attachment--hidden" : null)" onclick="@(attachment.IsSpoiler ? "showSpoiler(event, this)" : null)">
|
||||
@{/* Spoiler caption */}
|
||||
@if (attachment.IsSpoiler)
|
||||
{
|
||||
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color.Value.R, @embed.Color.Value.G, @embed.Color.Value.B, @embed.Color.Value.A)"></div>
|
||||
<div class="chatlog__attachment-spoiler-caption">SPOILER</div>
|
||||
}
|
||||
|
||||
@{/* Attachment preview */}
|
||||
@if (attachment.IsImage)
|
||||
{
|
||||
<a href="@await ResolveUrlAsync(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">
|
||||
</a>
|
||||
}
|
||||
else if (attachment.IsVideo)
|
||||
{
|
||||
<video class="chatlog__attachment-media" controls>
|
||||
<source src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Video attachment")" title="Video: @attachment.FileName (@attachment.FileSize)">
|
||||
</video>
|
||||
}
|
||||
else if (attachment.IsAudio)
|
||||
{
|
||||
<audio class="chatlog__attachment-media" controls>
|
||||
<source src="@await ResolveUrlAsync(attachment.Url)" alt="@(attachment.Description ?? "Audio attachment")" title="Audio: @attachment.FileName (@attachment.FileSize)">
|
||||
</audio>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
||||
<div class="chatlog__attachment-generic">
|
||||
<svg class="chatlog__attachment-generic-icon">
|
||||
<use href="#attachment-icon"/>
|
||||
</svg>
|
||||
<div class="chatlog__attachment-generic-name">
|
||||
<a href="@await ResolveUrlAsync(attachment.Url)">
|
||||
@attachment.FileName
|
||||
</a>
|
||||
</div>
|
||||
<div class="chatlog__attachment-generic-size">
|
||||
@attachment.FileSize
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="chatlog__embed-content-container">
|
||||
<div class="chatlog__embed-content">
|
||||
<div class="chatlog__embed-text">
|
||||
@{/* Embed author */}
|
||||
@if (embed.Author is not null)
|
||||
{
|
||||
<div class="chatlog__embed-author-container">
|
||||
@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'">
|
||||
}
|
||||
@{/* Embeds */}
|
||||
@foreach (var embed in message.Embeds)
|
||||
{
|
||||
// Spotify embed
|
||||
if (embed.TryGetSpotifyTrack() is { } spotifyTrackEmbed)
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
<div class="chatlog__embed-spotify-container">
|
||||
<iframe class="chatlog__embed-spotify" src="@spotifyTrackEmbed.Url" width="400" height="80" allowtransparency="true" allow="encrypted-media"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
// YouTube embed
|
||||
else if (embed.TryGetYouTubeVideo() is { } youTubeVideoEmbed)
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
@{/* Color pill */}
|
||||
@if (embed.Color is not null)
|
||||
{
|
||||
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color.Value.R, @embed.Color.Value.G, @embed.Color.Value.B, @embed.Color.Value.A)"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||
<div class="chatlog__embed-content-container">
|
||||
<div class="chatlog__embed-content">
|
||||
<div class="chatlog__embed-text">
|
||||
@{/* Embed author */}
|
||||
@if (embed.Author is not null)
|
||||
{
|
||||
<div class="chatlog__embed-author-container">
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
|
||||
{
|
||||
<a class="chatlog__embed-author-link" href="@embed.Author.Url">
|
||||
<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'">
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||
{
|
||||
<a class="chatlog__embed-author-link" href="@embed.Author.Url">
|
||||
<div class="chatlog__embed-author">@embed.Author.Name</div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-author">@embed.Author.Name</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed title */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
||||
{
|
||||
<div class="chatlog__embed-title">
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||
{
|
||||
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Title))<!--/wmm:ignore--></div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-author">@embed.Author.Name</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed title */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
||||
{
|
||||
<div class="chatlog__embed-title">
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||
{
|
||||
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Title))<!--/wmm:ignore--></div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Title))<!--/wmm:ignore--></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Video player */}
|
||||
<div class="chatlog__embed-youtube-container">
|
||||
<iframe class="chatlog__embed-youtube" src="@youTubeVideoEmbed.Url" width="400" height="225"></iframe>
|
||||
@{/* Video player */}
|
||||
<div class="chatlog__embed-youtube-container">
|
||||
<iframe class="chatlog__embed-youtube" src="@youTubeVideoEmbed.Url" width="400" height="225"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
// Generic image embed
|
||||
else if (embed.Kind == EmbedKind.Image && !string.IsNullOrWhiteSpace(embed.Url))
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
<a href="@await ResolveUrlAsync(embed.Url)">
|
||||
<img class="chatlog__embed-generic-image" src="@await ResolveUrlAsync(embed.Url)" alt="Embedded image" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
// Generic gifv embed
|
||||
else if (embed.Kind == EmbedKind.Gifv && !string.IsNullOrWhiteSpace(embed.Video?.Url))
|
||||
{
|
||||
<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()">
|
||||
<source src="@await ResolveUrlAsync(embed.Video.ProxyUrl ?? embed.Video.Url)" alt="Embedded video">
|
||||
</video>
|
||||
</div>
|
||||
}
|
||||
// Rich embed
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
@{/* Color pill */}
|
||||
@if (embed.Color is not null)
|
||||
{
|
||||
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color.Value.R, @embed.Color.Value.G, @embed.Color.Value.B, @embed.Color.Value.A)"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
||||
}
|
||||
}
|
||||
// Generic image embed
|
||||
else if (embed.Kind == EmbedKind.Image && !string.IsNullOrWhiteSpace(embed.Url))
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
<a href="@await ResolveUrlAsync(embed.Url)">
|
||||
<img class="chatlog__embed-generic-image" src="@await ResolveUrlAsync(embed.Url)" alt="Embedded image" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
// Generic gifv embed
|
||||
else if (embed.Kind == EmbedKind.Gifv && !string.IsNullOrWhiteSpace(embed.Video?.Url))
|
||||
{
|
||||
<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()">
|
||||
<source src="@await ResolveUrlAsync(embed.Video.ProxyUrl ?? embed.Video.Url)" alt="Embedded video">
|
||||
</video>
|
||||
</div>
|
||||
}
|
||||
// Rich embed
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed">
|
||||
@{/* Color pill */}
|
||||
@if (embed.Color is not null)
|
||||
{
|
||||
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color.Value.R, @embed.Color.Value.G, @embed.Color.Value.B, @embed.Color.Value.A)"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
||||
}
|
||||
|
||||
<div class="chatlog__embed-content-container">
|
||||
<div class="chatlog__embed-content">
|
||||
<div class="chatlog__embed-text">
|
||||
@{/* Embed author */}
|
||||
@if (embed.Author is not null)
|
||||
{
|
||||
<div class="chatlog__embed-author-container">
|
||||
@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'">
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||
<div class="chatlog__embed-content-container">
|
||||
<div class="chatlog__embed-content">
|
||||
<div class="chatlog__embed-text">
|
||||
@{/* Embed author */}
|
||||
@if (embed.Author is not null)
|
||||
{
|
||||
<div class="chatlog__embed-author-container">
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
|
||||
{
|
||||
<a class="chatlog__embed-author-link" href="@embed.Author.Url">
|
||||
<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'">
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||
{
|
||||
<a class="chatlog__embed-author-link" href="@embed.Author.Url">
|
||||
<div class="chatlog__embed-author">@embed.Author.Name</div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-author">@embed.Author.Name</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed title */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
||||
{
|
||||
<div class="chatlog__embed-title">
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||
{
|
||||
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Title))<!--/wmm:ignore--></div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__embed-author">@embed.Author.Name</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed title */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
||||
{
|
||||
<div class="chatlog__embed-title">
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||
{
|
||||
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Title))<!--/wmm:ignore--></div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Title))<!--/wmm:ignore--></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed description */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Description))
|
||||
@{/* Embed description */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Description))
|
||||
{
|
||||
<div class="chatlog__embed-description">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Description))<!--/wmm:ignore--></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed fields */}
|
||||
@if (embed.Fields.Any())
|
||||
{
|
||||
<div class="chatlog__embed-fields">
|
||||
@foreach (var field in embed.Fields)
|
||||
{
|
||||
<div class="chatlog__embed-field @(field.IsInline ? "chatlog__embed-field--inline" : null)">
|
||||
@if (!string.IsNullOrWhiteSpace(field.Name))
|
||||
{
|
||||
<div class="chatlog__embed-field-name">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(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(FormatEmbedMarkdown(field.Value))<!--/wmm:ignore--></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@{/* Embed content */}
|
||||
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url))
|
||||
{
|
||||
<div class="chatlog__embed-description">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(embed.Description))<!--/wmm:ignore--></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed fields */}
|
||||
@if (embed.Fields.Any())
|
||||
{
|
||||
<div class="chatlog__embed-fields">
|
||||
@foreach (var field in embed.Fields)
|
||||
{
|
||||
<div class="chatlog__embed-field @(field.IsInline ? "chatlog__embed-field--inline" : null)">
|
||||
@if (!string.IsNullOrWhiteSpace(field.Name))
|
||||
{
|
||||
<div class="chatlog__embed-field-name">
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve"><!--wmm:ignore-->@Raw(FormatEmbedMarkdown(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(FormatEmbedMarkdown(field.Value))<!--/wmm:ignore--></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="chatlog__embed-thumbnail-container">
|
||||
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
|
||||
<img class="chatlog__embed-thumbnail" src="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)" alt="Thumbnail" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@{/* Embed content */}
|
||||
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url))
|
||||
@{/* Embed images */}
|
||||
@if (embed.Images.Any())
|
||||
{
|
||||
<div class="chatlog__embed-thumbnail-container">
|
||||
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
|
||||
<img class="chatlog__embed-thumbnail" src="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)" alt="Thumbnail" loading="lazy">
|
||||
</a>
|
||||
<div class="chatlog__embed-images @(embed.Images.Count == 1 ? "chatlog__embed-images--single" : null)">
|
||||
@foreach (var image in embed.Images)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(image.Url))
|
||||
{
|
||||
<div class="chatlog__embed-image-container">
|
||||
<a class="chatlog__embed-image-link" href="@await ResolveUrlAsync(image.ProxyUrl ?? image.Url)">
|
||||
<img class="chatlog__embed-image" src="@await ResolveUrlAsync(image.ProxyUrl ?? image.Url)" alt="Image" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed footer & icon */}
|
||||
@if (embed.Footer is not null || embed.Timestamp is not null)
|
||||
{
|
||||
<div class="chatlog__embed-footer">
|
||||
@{/* Footer icon */}
|
||||
@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">
|
||||
}
|
||||
|
||||
<span class="chatlog__embed-footer-text">
|
||||
@{/* Footer text */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
|
||||
{
|
||||
@embed.Footer.Text
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text) && embed.Timestamp is not null)
|
||||
{
|
||||
@(" • ")
|
||||
}
|
||||
|
||||
@{/* Embed timestamp */}
|
||||
@if (embed.Timestamp is not null)
|
||||
{
|
||||
@FormatDate(embed.Timestamp.Value)
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@{/* Embed images */}
|
||||
@if (embed.Images.Any())
|
||||
{
|
||||
<div class="chatlog__embed-images @(embed.Images.Count == 1 ? "chatlog__embed-images--single" : null)">
|
||||
@foreach (var image in embed.Images)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(image.Url))
|
||||
{
|
||||
<div class="chatlog__embed-image-container">
|
||||
<a class="chatlog__embed-image-link" href="@await ResolveUrlAsync(image.ProxyUrl ?? image.Url)">
|
||||
<img class="chatlog__embed-image" src="@await ResolveUrlAsync(image.ProxyUrl ?? image.Url)" alt="Image" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Embed footer & icon */}
|
||||
@if (embed.Footer is not null || embed.Timestamp is not null)
|
||||
{
|
||||
<div class="chatlog__embed-footer">
|
||||
@{/* Footer icon */}
|
||||
@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">
|
||||
}
|
||||
|
||||
<span class="chatlog__embed-footer-text">
|
||||
@{/* Footer text */}
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
|
||||
{
|
||||
@embed.Footer.Text
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text) && embed.Timestamp is not null)
|
||||
{
|
||||
@(" • ")
|
||||
}
|
||||
|
||||
@{/* Embed timestamp */}
|
||||
@if (embed.Timestamp is not null)
|
||||
{
|
||||
@FormatDate(embed.Timestamp.Value)
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@{/* Stickers */}
|
||||
@foreach (var sticker in message.Stickers)
|
||||
{
|
||||
<div class="chatlog__sticker" title="@sticker.Name">
|
||||
@if (sticker.Format is StickerFormat.Png or StickerFormat.PngAnimated)
|
||||
{
|
||||
<img class="chatlog__sticker--media" src="@await ResolveUrlAsync(sticker.SourceUrl)" alt="Sticker">
|
||||
}
|
||||
else if (sticker.Format == StickerFormat.Lottie)
|
||||
{
|
||||
<div class="chatlog__sticker--media" data-source="@await ResolveUrlAsync(sticker.SourceUrl)"></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@{/* Stickers */}
|
||||
@foreach (var sticker in message.Stickers)
|
||||
{
|
||||
<div class="chatlog__sticker" title="@sticker.Name">
|
||||
@if (sticker.Format is StickerFormat.Png or StickerFormat.PngAnimated)
|
||||
{
|
||||
<img class="chatlog__sticker--media" src="@await ResolveUrlAsync(sticker.SourceUrl)" alt="Sticker">
|
||||
}
|
||||
else if (sticker.Format == StickerFormat.Lottie)
|
||||
{
|
||||
<div class="chatlog__sticker--media" data-source="@await ResolveUrlAsync(sticker.SourceUrl)"></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Message reactions */}
|
||||
@if (message.Reactions.Any())
|
||||
{
|
||||
<div class="chatlog__reactions">
|
||||
@foreach (var reaction in message.Reactions)
|
||||
{
|
||||
<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">
|
||||
<span class="chatlog__reaction-count">@reaction.Count</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@{/* Message reactions */}
|
||||
@if (message.Reactions.Any())
|
||||
{
|
||||
<div class="chatlog__reactions">
|
||||
@foreach (var reaction in message.Reactions)
|
||||
{
|
||||
<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">
|
||||
<span class="chatlog__reaction-count">@reaction.Count</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -254,19 +254,18 @@
|
|||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
.chatlog__system-message{
|
||||
color: #96989D
|
||||
.chatlog__system-notification {
|
||||
color: @Themed("rgb(150, 152, 157)", "rgb(94, 103, 114)")
|
||||
}
|
||||
|
||||
.chatlog__system-message-reference-link{
|
||||
.chatlog__system-notification-reference-link {
|
||||
font-weight: 500;
|
||||
color: #ffffff
|
||||
}
|
||||
|
||||
.chatlog__system-message-icon{
|
||||
.chatlog__system-notification-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.chatlog__header {
|
||||
|
@ -804,22 +803,19 @@
|
|||
<svg style="display: none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<symbol id="attachment-icon" viewBox="0 0 720 960">
|
||||
<path fill="#f4f5fb" d="M50,935a25,25,0,0,1-25-25V50A25,25,0,0,1,50,25H519.6L695,201.32V910a25,25,0,0,1-25,25Z"/>
|
||||
<path fill="#7789c4" d="M509.21,50,670,211.63V910H50V50H509.21M530,0H50A50,50,0,0,0,0,50V910a50,50,0,0,0,50,50H670a50,50,0,0,0,50-50h0V191Z"/>
|
||||
<path fill="#f4f5fb" d="M530,215a25,25,0,0,1-25-25V50a25,25,0,0,1,16.23-23.41L693.41,198.77A25,25,0,0,1,670,215Z"/>
|
||||
<path fill="#7789c4" d="M530,70.71,649.29,190H530V70.71M530,0a50,50,0,0,0-50,50V190a50,50,0,0,0,50,50H670a50,50,0,0,0,50-50Z"/>
|
||||
<path fill="#f4f5fb" d="M50,935a25,25,0,0,1-25-25V50A25,25,0,0,1,50,25H519.6L695,201.32V910a25,25,0,0,1-25,25Z" />
|
||||
<path fill="#7789c4" d="M509.21,50,670,211.63V910H50V50H509.21M530,0H50A50,50,0,0,0,0,50V910a50,50,0,0,0,50,50H670a50,50,0,0,0,50-50h0V191Z" />
|
||||
<path fill="#f4f5fb" d="M530,215a25,25,0,0,1-25-25V50a25,25,0,0,1,16.23-23.41L693.41,198.77A25,25,0,0,1,670,215Z" />
|
||||
<path fill="#7789c4" d="M530,70.71,649.29,190H530V70.71M530,0a50,50,0,0,0-50,50V190a50,50,0,0,0,50,50H670a50,50,0,0,0,50-50Z" />
|
||||
</symbol>
|
||||
<symbol id="channel-pinned-message-icon" fill="none" viewBox="0 0 17 17">
|
||||
<path d="m16.908 8.39684-8.29587-8.295827-1.18584 1.184157 1.18584 1.18584-4.14834 4.1475v.00167l-1.18583-1.18583-1.185 1.18583 3.55583 3.55502-4.740831 4.74 1.185001 1.185 4.74083-4.74 3.55581 3.555 1.185-1.185-1.185-1.185 4.1475-4.14836h.0009l1.185 1.185z" fill="#b9bbbe"/>
|
||||
<symbol id="channel-pinned-message-icon" viewBox="0 0 18 18">
|
||||
<path fill="#b9bbbe" d="m16.908 8.39684-8.29587-8.295827-1.18584 1.184157 1.18584 1.18584-4.14834 4.1475v.00167l-1.18583-1.18583-1.185 1.18583 3.55583 3.55502-4.740831 4.74 1.185001 1.185 4.74083-4.74 3.55581 3.555 1.185-1.185-1.185-1.185 4.1475-4.14836h.0009l1.185 1.185z" />
|
||||
</symbol>
|
||||
<symbol id="call-icon" viewBox="0 0 18 18">
|
||||
<path fill="#3ba55c" fill-rule="evenodd" d="M17.7163041 15.36645368c-.0190957.02699568-1.9039523 2.6680735-2.9957762 2.63320406-3.0676659-.09785935-6.6733809-3.07188394-9.15694343-5.548738C3.08002193 9.9740657.09772497 6.3791404 0 3.3061316v-.024746C0 2.2060575 2.61386252.3152347 2.64082114.2972376c.7110335-.4971705 1.4917101-.3149497 1.80959713.1372281.19320342.2744561 2.19712724 3.2811005 2.42290565 3.6489167.09884826.1608492.14714912.3554431.14714912.5702838 0 .2744561-.07975258.5770327-.23701117.8751101-.1527655.2902036-.65262318 1.1664385-.89862055 1.594995.2673396.3768148.94804468 1.26429792 2.351016 2.66357424 1.39173858 1.39027775 2.28923588 2.07641807 2.67002628 2.34187563.4302146-.2452108 1.3086162-.74238132 1.5972981-.89423205.5447887-.28682915 1.0907006-.31944893 1.4568885-.08661115.3459689.2182151 3.3383754 2.21027167 3.6225641 2.41611376.2695862.19234426.4144887.5399137.4144887.91672846 0 .2969525-.089862.61190215-.2808189.88523346"/>
|
||||
<path fill="#3ba55c" fill-rule="evenodd" d="M17.7163041 15.36645368c-.0190957.02699568-1.9039523 2.6680735-2.9957762 2.63320406-3.0676659-.09785935-6.6733809-3.07188394-9.15694343-5.548738C3.08002193 9.9740657.09772497 6.3791404 0 3.3061316v-.024746C0 2.2060575 2.61386252.3152347 2.64082114.2972376c.7110335-.4971705 1.4917101-.3149497 1.80959713.1372281.19320342.2744561 2.19712724 3.2811005 2.42290565 3.6489167.09884826.1608492.14714912.3554431.14714912.5702838 0 .2744561-.07975258.5770327-.23701117.8751101-.1527655.2902036-.65262318 1.1664385-.89862055 1.594995.2673396.3768148.94804468 1.26429792 2.351016 2.66357424 1.39173858 1.39027775 2.28923588 2.07641807 2.67002628 2.34187563.4302146-.2452108 1.3086162-.74238132 1.5972981-.89423205.5447887-.28682915 1.0907006-.31944893 1.4568885-.08661115.3459689.2182151 3.3383754 2.21027167 3.6225641 2.41611376.2695862.19234426.4144887.5399137.4144887.91672846 0 .2969525-.089862.61190215-.2808189.88523346" />
|
||||
</symbol>
|
||||
<symbol id="guild-member-join-icon" viewBox="0 0 18 18">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m18 0h-18v18h18z"/>
|
||||
<path d="m0 8h14.2l-3.6-3.6 1.4-1.4 6 6-6 6-1.4-1.4 3.6-3.6h-14.2" fill="#3ba55c"/>
|
||||
</g>
|
||||
<path fill="#3ba55c" d="m0 8h14.2l-3.6-3.6 1.4-1.4 6 6-6 6-1.4-1.4 3.6-3.6h-14.2" />
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
@ -27,29 +27,43 @@ internal class HtmlMessageWriter : MessageWriter
|
|||
|
||||
private bool CanJoinGroup(Message message)
|
||||
{
|
||||
// First message in the group can always join
|
||||
if(_messageGroup.LastOrDefault() is not { } lastMessage)
|
||||
{
|
||||
// If the group is empty, any message can join it
|
||||
if (_messageGroup.LastOrDefault() is not { } lastMessage)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Group system messages with other system messages, regardless of author
|
||||
if (message.Kind.IsSystemMessage())
|
||||
// Reply messages cannot join existing groups because they need to appear first
|
||||
if (message.Kind == MessageKind.Reply)
|
||||
return false;
|
||||
|
||||
// Grouping for system notifications
|
||||
if (message.Kind.IsSystemNotification())
|
||||
{
|
||||
return lastMessage.Kind.IsSystemMessage();
|
||||
// Can only be grouped with other system notifications
|
||||
if (!lastMessage.Kind.IsSystemNotification())
|
||||
return false;
|
||||
}
|
||||
// Grouping for normal messages
|
||||
else
|
||||
{
|
||||
// Can only be grouped with other normal messages
|
||||
if (lastMessage.Kind.IsSystemNotification())
|
||||
return false;
|
||||
|
||||
// Messages must be within 7 minutes of each other
|
||||
if ((message.Timestamp - lastMessage.Timestamp).Duration().TotalMinutes > 7)
|
||||
return false;
|
||||
|
||||
// Messages must be from the same author
|
||||
if (message.Author.Id != lastMessage.Author.Id)
|
||||
return false;
|
||||
|
||||
// If the user changed their name after the last message, their new messages
|
||||
// cannot join an existing group.
|
||||
if (!string.Equals(message.Author.FullName, lastMessage.Author.FullName, StringComparison.Ordinal))
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
// Must be a non system message
|
||||
!message.Kind.IsSystemMessage() &&
|
||||
// Must be from the same author
|
||||
lastMessage.Author.Id == message.Author.Id &&
|
||||
// Author's name must not have changed between messages
|
||||
string.Equals(lastMessage.Author.FullName, message.Author.FullName, StringComparison.Ordinal) &&
|
||||
// Duration between messages must be 7 minutes or less
|
||||
(message.Timestamp - lastMessage.Timestamp).Duration().TotalMinutes <= 7 &&
|
||||
// Other message must not be a reply
|
||||
message.Reference is null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use <!--wmm:ignore--> to preserve blocks of code inside the templates
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Utils.Extensions;
|
||||
|
||||
|
@ -25,6 +26,9 @@ public static class StringExtensions
|
|||
}
|
||||
}
|
||||
|
||||
public static string ToDashCase(this string str) =>
|
||||
Regex.Replace(str, @"(\p{Ll})(\p{Lu})", "$1-$2");
|
||||
|
||||
public static StringBuilder AppendIfNotEmpty(this StringBuilder builder, char value) =>
|
||||
builder.Length > 0
|
||||
? builder.Append(value)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue