mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-22 10:55:15 -04:00
Update HTML layout and styles
This commit is contained in:
parent
65cc3954b6
commit
5b4d88ca26
7 changed files with 517 additions and 494 deletions
|
@ -1,60 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
|
||||||
|
|
||||||
// Used for grouping contiguous messages in HTML export
|
|
||||||
internal partial class MessageGroup
|
|
||||||
{
|
|
||||||
public User Author { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset Timestamp { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<Message> Messages { get; }
|
|
||||||
|
|
||||||
public MessageReference? Reference { get; }
|
|
||||||
|
|
||||||
public Message? ReferencedMessage {get; }
|
|
||||||
|
|
||||||
public MessageGroup(
|
|
||||||
User author,
|
|
||||||
DateTimeOffset timestamp,
|
|
||||||
MessageReference? reference,
|
|
||||||
Message? referencedMessage,
|
|
||||||
IReadOnlyList<Message> messages)
|
|
||||||
{
|
|
||||||
Author = author;
|
|
||||||
Timestamp = timestamp;
|
|
||||||
Reference = reference;
|
|
||||||
ReferencedMessage = referencedMessage;
|
|
||||||
Messages = messages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal partial class MessageGroup
|
|
||||||
{
|
|
||||||
public static bool CanJoin(Message message1, Message message2) =>
|
|
||||||
// Must be from the same author
|
|
||||||
message1.Author.Id == message2.Author.Id &&
|
|
||||||
// Author's name must not have changed between messages
|
|
||||||
string.Equals(message1.Author.FullName, message2.Author.FullName, StringComparison.Ordinal) &&
|
|
||||||
// Duration between messages must be 7 minutes or less
|
|
||||||
(message2.Timestamp - message1.Timestamp).Duration().TotalMinutes <= 7 &&
|
|
||||||
// Other message must not be a reply
|
|
||||||
message2.Reference is null;
|
|
||||||
|
|
||||||
public static MessageGroup Join(IReadOnlyList<Message> messages)
|
|
||||||
{
|
|
||||||
var first = messages.First();
|
|
||||||
|
|
||||||
return new MessageGroup(
|
|
||||||
first.Author,
|
|
||||||
first.Timestamp,
|
|
||||||
first.Reference,
|
|
||||||
first.ReferencedMessage,
|
|
||||||
messages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,11 +3,14 @@
|
||||||
@using System.Threading.Tasks
|
@using System.Threading.Tasks
|
||||||
@using DiscordChatExporter.Core.Discord.Data
|
@using DiscordChatExporter.Core.Discord.Data
|
||||||
@using DiscordChatExporter.Core.Exporting.Writers.Html;
|
@using DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
@using DiscordChatExporter.Core.Utils.Extensions
|
||||||
|
|
||||||
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
@inherits MiniRazor.TemplateBase<MessageGroupTemplateContext>
|
@inherits MiniRazor.TemplateBase<MessageGroupTemplateContext>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
|
var firstMessage = Model.Messages.First();
|
||||||
|
|
||||||
ValueTask<string> ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url);
|
ValueTask<string> ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url);
|
||||||
|
|
||||||
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date);
|
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date);
|
||||||
|
@ -16,56 +19,78 @@
|
||||||
|
|
||||||
string FormatEmbedMarkdown(string markdown) => Model.FormatMarkdown(markdown, false);
|
string FormatEmbedMarkdown(string markdown) => Model.FormatMarkdown(markdown, false);
|
||||||
|
|
||||||
var userMember = Model.ExportContext.TryGetMember(Model.MessageGroup.Author.Id);
|
var userMember = Model.ExportContext.TryGetMember(firstMessage.Author.Id);
|
||||||
|
|
||||||
var userColor = Model.ExportContext.TryGetUserColor(Model.MessageGroup.Author.Id);
|
var userColor = Model.ExportContext.TryGetUserColor(firstMessage.Author.Id);
|
||||||
|
|
||||||
var userColorStyle = userColor is not null
|
var userNick = firstMessage.Author.IsBot
|
||||||
? $"color: rgb({userColor?.R},{userColor?.G},{userColor?.B})"
|
? firstMessage.Author.Name
|
||||||
|
: userMember?.Nick ?? firstMessage.Author.Name;
|
||||||
|
|
||||||
|
var referencedUserMember = firstMessage.ReferencedMessage is not null
|
||||||
|
? Model.ExportContext.TryGetMember(firstMessage.ReferencedMessage.Author.Id)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
var userNick = Model.MessageGroup.Author.IsBot
|
var referencedUserColor = firstMessage.ReferencedMessage is not null
|
||||||
? Model.MessageGroup.Author.Name
|
? Model.ExportContext.TryGetUserColor(firstMessage.ReferencedMessage.Author.Id)
|
||||||
: userMember?.Nick ?? Model.MessageGroup.Author.Name;
|
|
||||||
|
|
||||||
var referencedUserMember = Model.MessageGroup.ReferencedMessage is not null
|
|
||||||
? Model.ExportContext.TryGetMember(Model.MessageGroup.ReferencedMessage.Author.Id)
|
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
var referencedUserColor = Model.MessageGroup.ReferencedMessage is not null
|
var referencedUserNick = firstMessage.ReferencedMessage is not null
|
||||||
? Model.ExportContext.TryGetUserColor(Model.MessageGroup.ReferencedMessage.Author.Id)
|
? firstMessage.ReferencedMessage.Author.IsBot
|
||||||
: null;
|
? firstMessage.ReferencedMessage.Author.Name
|
||||||
|
: referencedUserMember?.Nick ?? firstMessage.ReferencedMessage.Author.Name
|
||||||
var referencedUserColorStyle = referencedUserColor is not null
|
|
||||||
? $"color: rgb({referencedUserColor?.R},{referencedUserColor?.G},{referencedUserColor?.B})"
|
|
||||||
: null;
|
|
||||||
|
|
||||||
var referencedUserNick = Model.MessageGroup.ReferencedMessage is not null
|
|
||||||
? Model.MessageGroup.ReferencedMessage.Author.IsBot
|
|
||||||
? Model.MessageGroup.ReferencedMessage.Author.Name
|
|
||||||
: referencedUserMember?.Nick ?? Model.MessageGroup.ReferencedMessage.Author.Name
|
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="chatlog__message-group">
|
<div class="chatlog__message-group">
|
||||||
@{/* Referenced message */}
|
@for (var i = 0; i < Model.Messages.Count; i++)
|
||||||
@if (Model.MessageGroup.Reference is not null)
|
{
|
||||||
|
var isFirst = i == 0;
|
||||||
|
var message = Model.Messages[i];
|
||||||
|
|
||||||
|
<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 (isFirst)
|
||||||
|
{
|
||||||
|
// Reference symbol
|
||||||
|
if (message.Reference is not null)
|
||||||
{
|
{
|
||||||
<div class="chatlog__reference-symbol"></div>
|
<div class="chatlog__reference-symbol"></div>
|
||||||
<div class="chatlog__reference">
|
|
||||||
@if (Model.MessageGroup.ReferencedMessage is not null)
|
|
||||||
{
|
|
||||||
<img class="chatlog__reference-avatar" src="@await ResolveUrlAsync(Model.MessageGroup.ReferencedMessage.Author.AvatarUrl)" alt="Avatar" loading="lazy">
|
|
||||||
<span class="chatlog__reference-name" title="@Model.MessageGroup.ReferencedMessage.Author.FullName" style="@referencedUserColorStyle">@referencedUserNick</span>
|
|
||||||
<div class="chatlog__reference-content">
|
|
||||||
<span class="chatlog__reference-link" onclick="scrollToMessage(event, '@Model.MessageGroup.ReferencedMessage.Id')">
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.MessageGroup.ReferencedMessage.Content))
|
|
||||||
{
|
|
||||||
@Raw(FormatEmbedMarkdown(Model.MessageGroup.ReferencedMessage.Content))
|
|
||||||
}
|
}
|
||||||
else if (Model.MessageGroup.ReferencedMessage.Attachments.Any() || Model.MessageGroup.ReferencedMessage.Embeds.Any())
|
|
||||||
|
// Avatar
|
||||||
|
<img class="chatlog__avatar" src="@await ResolveUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy">
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<em>Click to see attachment</em> <span>🖼️</span>
|
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@{/* Right side */}
|
||||||
|
<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))
|
||||||
|
{
|
||||||
|
@Raw(FormatEmbedMarkdown(message.ReferencedMessage.Content))
|
||||||
|
}
|
||||||
|
else if (message.ReferencedMessage.Attachments.Any() || message.ReferencedMessage.Embeds.Any())
|
||||||
|
{
|
||||||
|
<em>Click to see attachment</em>
|
||||||
|
<span>🖼️</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -73,57 +98,50 @@
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@if (Model.MessageGroup.ReferencedMessage.EditedTimestamp is not null)
|
@if (message.ReferencedMessage.EditedTimestamp is not null)
|
||||||
{
|
{
|
||||||
<span class="chatlog__reference-edited-timestamp" title="@FormatDate(Model.MessageGroup.ReferencedMessage.EditedTimestamp.Value)">(edited)</span>
|
<span class="chatlog__reference-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value)">(edited)</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="chatlog__reference-unknown">
|
<div class="chatlog__reference-unknown">
|
||||||
Original message was deleted or could not be loaded.
|
Original message was deleted or could not be loaded.
|
||||||
</span>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@{/* Avatar */}
|
// Header
|
||||||
<div class="chatlog__author-avatar-container">
|
<div class="chatlog__header">
|
||||||
<img class="chatlog__author-avatar" src="@await ResolveUrlAsync(Model.MessageGroup.Author.AvatarUrl)" alt="Avatar" loading="lazy">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chatlog__messages">
|
|
||||||
@{/* Author name */}
|
@{/* Author name */}
|
||||||
<span class="chatlog__author-name" title="@Model.MessageGroup.Author.FullName" data-user-id="@Model.MessageGroup.Author.Id" style="@userColorStyle">@userNick</span>
|
<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 tag */}
|
@{/* Bot label */}
|
||||||
@if (Model.MessageGroup.Author.IsBot)
|
@if (message.Author.IsBot)
|
||||||
{
|
{
|
||||||
<span class="chatlog__bot-tag">BOT</span>
|
<span class="chatlog__bot-label">BOT</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
@{/* Message timestamp */}
|
@{/* Timestamp */}
|
||||||
<span class="chatlog__timestamp">@FormatDate(Model.MessageGroup.Timestamp)</span>
|
<span class="chatlog__timestamp">@FormatDate(message.Timestamp)</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@{/* Messages in a group */}
|
@{/* Content */}
|
||||||
@foreach (var message in Model.MessageGroup.Messages)
|
|
||||||
{
|
|
||||||
<div class="chatlog__message @(message.IsPinned ? "chatlog__message--pinned" : null)" data-message-id="@message.Id" id="message-@message.Id" title="Message sent: @FormatDate(message.Timestamp)">
|
|
||||||
@if (!string.IsNullOrWhiteSpace(message.Content) || message.EditedTimestamp is not null)
|
@if (!string.IsNullOrWhiteSpace(message.Content) || message.EditedTimestamp is not null)
|
||||||
{
|
{
|
||||||
<div class="chatlog__content">
|
<div class="chatlog__content chatlog__markdown">
|
||||||
<div class="markdown">
|
@{/* Text */}
|
||||||
@{/* Message content */}
|
<span class="chatlog__markdown-preserve">@Raw(FormatMarkdown(message.Content))</span>
|
||||||
<span class="preserve-whitespace">@Raw(FormatMarkdown(message.Content))</span>
|
|
||||||
|
|
||||||
@{/* Edit timestamp */}
|
@{/* Edited timestamp */}
|
||||||
@if (message.EditedTimestamp is not null)
|
@if (message.EditedTimestamp is not null)
|
||||||
{
|
{
|
||||||
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value)">(edited)</span>
|
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value)">(edited)</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@{/* Attachments */}
|
@{/* Attachments */}
|
||||||
|
@ -159,7 +177,7 @@
|
||||||
{
|
{
|
||||||
<div class="chatlog__attachment-generic">
|
<div class="chatlog__attachment-generic">
|
||||||
<svg class="chatlog__attachment-generic-icon">
|
<svg class="chatlog__attachment-generic-icon">
|
||||||
<use href="#icon-attachment" />
|
<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 ResolveUrlAsync(attachment.Url)">
|
||||||
|
@ -202,7 +220,7 @@
|
||||||
@{/* Color pill */}
|
@{/* Color pill */}
|
||||||
@if (embed.Color is not null)
|
@if (embed.Color is not null)
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color?.R,@embed.Color?.G,@embed.Color?.B,@embed.Color?.A)"></div>
|
<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
|
else
|
||||||
{
|
{
|
||||||
|
@ -223,10 +241,10 @@
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||||
{
|
{
|
||||||
<span class="chatlog__embed-author-name">
|
<span class="chatlog__embed-author">
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||||
{
|
{
|
||||||
<a class="chatlog__embed-author-name-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
<a class="chatlog__embed-author-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -244,12 +262,12 @@
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||||
{
|
{
|
||||||
<a class="chatlog__embed-title-link" href="@embed.Url">
|
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -270,7 +288,7 @@
|
||||||
@{/* Color pill */}
|
@{/* Color pill */}
|
||||||
@if (embed.Color is not null)
|
@if (embed.Color is not null)
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color?.R,@embed.Color?.G,@embed.Color?.B,@embed.Color?.A)"></div>
|
<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
|
else
|
||||||
{
|
{
|
||||||
|
@ -291,10 +309,10 @@
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||||
{
|
{
|
||||||
<span class="chatlog__embed-author-name">
|
<span class="chatlog__embed-author">
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||||
{
|
{
|
||||||
<a class="chatlog__embed-author-name-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
<a class="chatlog__embed-author-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -312,12 +330,12 @@
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||||
{
|
{
|
||||||
<a class="chatlog__embed-title-link" href="@embed.Url">
|
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -326,7 +344,7 @@
|
||||||
@if (!string.IsNullOrWhiteSpace(embed.Description))
|
@if (!string.IsNullOrWhiteSpace(embed.Description))
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-description">
|
<div class="chatlog__embed-description">
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Description))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Description))</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,14 +358,14 @@
|
||||||
@if (!string.IsNullOrWhiteSpace(field.Name))
|
@if (!string.IsNullOrWhiteSpace(field.Name))
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-field-name">
|
<div class="chatlog__embed-field-name">
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(field.Name))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(field.Name))</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(field.Value))
|
@if (!string.IsNullOrWhiteSpace(field.Value))
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-field-value">
|
<div class="chatlog__embed-field-value">
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(field.Value))</div>
|
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(field.Value))</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -434,13 +452,14 @@
|
||||||
@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="emoji 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 ResolveUrlAsync(reaction.Emoji.ImageUrl)" loading="lazy">
|
||||||
<span class="chatlog__reaction-count">@reaction.Count</span>
|
<span class="chatlog__reaction-count">@reaction.Count</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,6 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using System.Collections.Generic;
|
||||||
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
|
||||||
|
@ -6,12 +8,12 @@ internal class MessageGroupTemplateContext
|
||||||
{
|
{
|
||||||
public ExportContext ExportContext { get; }
|
public ExportContext ExportContext { get; }
|
||||||
|
|
||||||
public MessageGroup MessageGroup { get; }
|
public IReadOnlyList<Message> Messages { get; }
|
||||||
|
|
||||||
public MessageGroupTemplateContext(ExportContext exportContext, MessageGroup messageGroup)
|
public MessageGroupTemplateContext(ExportContext exportContext, IReadOnlyList<Message> messages)
|
||||||
{
|
{
|
||||||
ExportContext = exportContext;
|
ExportContext = exportContext;
|
||||||
MessageGroup = messageGroup;
|
Messages = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
||||||
|
|
|
@ -32,36 +32,38 @@
|
||||||
@{/* Styling */}
|
@{/* Styling */}
|
||||||
<style>
|
<style>
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: Whitney;
|
|
||||||
src: url(@await ResolveUrlAsync(GetFontUrl(300)));
|
src: url(@await ResolveUrlAsync(GetFontUrl(300)));
|
||||||
|
font-family: Whitney;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: Whitney;
|
|
||||||
src: url(@await ResolveUrlAsync(GetFontUrl(400)));
|
src: url(@await ResolveUrlAsync(GetFontUrl(400)));
|
||||||
|
font-family: Whitney;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: Whitney;
|
|
||||||
src: url(@await ResolveUrlAsync(GetFontUrl(500)));
|
src: url(@await ResolveUrlAsync(GetFontUrl(500)));
|
||||||
|
font-family: Whitney;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: Whitney;
|
|
||||||
src: url(@await ResolveUrlAsync(GetFontUrl(600)));
|
src: url(@await ResolveUrlAsync(GetFontUrl(600)));
|
||||||
|
font-family: Whitney;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: Whitney;
|
|
||||||
src: url(@await ResolveUrlAsync(GetFontUrl(700)));
|
src: url(@await ResolveUrlAsync(GetFontUrl(700)));
|
||||||
|
font-family: Whitney;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
background-color: @Themed("#36393e", "#ffffff");
|
background-color: @Themed("#36393e", "#ffffff");
|
||||||
color: @Themed("#dcddde", "#23262a");
|
color: @Themed("#dcddde", "#23262a");
|
||||||
font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
@ -80,114 +82,15 @@
|
||||||
|
|
||||||
img {
|
img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
image-rendering: high-quality;
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
.markdown {
|
|
||||||
max-width: 100%;
|
|
||||||
line-height: 1.3;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preserve-whitespace {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spoiler-text {
|
|
||||||
background-color: @Themed("rgba(255, 255, 255, 0.1)", "rgba(0, 0, 0, 0.1)");
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spoiler-text--hidden {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: @Themed("#202225", "#b9bbbe");
|
|
||||||
color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spoiler-text--hidden:hover {
|
|
||||||
background-color: @Themed("rgba(32, 34, 37, 0.8)", "rgba(185, 187, 190, 0.8)");
|
|
||||||
}
|
|
||||||
|
|
||||||
.spoiler-text--hidden::selection {
|
|
||||||
color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote {
|
|
||||||
display: flex;
|
|
||||||
margin: 0.05em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-border {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
border: 2px solid @Themed("#4f545c", "#c7ccd1");
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pre {
|
|
||||||
background-color: @Themed("#2f3136", "#f9f9f9");
|
|
||||||
font-family: "Consolas", "Courier New", Courier, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pre--multiline {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
padding: 0.5em;
|
|
||||||
border: 2px solid @Themed("#282b30", "#f3f3f3");
|
|
||||||
border-radius: 5px;
|
|
||||||
color: @Themed("#b9bbbe", "#657b83");
|
|
||||||
}
|
|
||||||
|
|
||||||
@{/* Override Highlight.js styles with a higher specificity selector */}
|
|
||||||
.pre--multiline.hljs {
|
|
||||||
background-color: @Themed("#2f3136", "#f9f9f9");
|
|
||||||
color: @Themed("#b9bbbe", "#657b83");
|
|
||||||
}
|
|
||||||
|
|
||||||
.pre--inline {
|
|
||||||
padding: 2px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mention {
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 0 2px;
|
|
||||||
color: @Themed("#dee0fc", "#505cdc");
|
|
||||||
background-color: @Themed("rgba(88, 101, 242, .3)", "rgba(88, 101, 242, .15)");
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mention:hover {
|
|
||||||
background-color: #5865f2;
|
|
||||||
color: #ffffff
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 0 2px;
|
|
||||||
background-color: @Themed("rgba(255, 255, 255, 0.06)", "rgba(6, 6, 7, 0.08)");
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji {
|
|
||||||
width: 1.325em;
|
|
||||||
height: 1.325em;
|
|
||||||
margin: 0 0.06em;
|
|
||||||
vertical-align: -0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji--small {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji--large {
|
|
||||||
width: 2.8em;
|
|
||||||
height: 2.8em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preamble {
|
.preamble {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin: 0 0.3em 0.6em 0.3em;
|
|
||||||
max-width: 100%;
|
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preamble__guild-icon-container {
|
.preamble__guild-icon-container {
|
||||||
|
@ -201,59 +104,114 @@
|
||||||
|
|
||||||
.preamble__entries-container {
|
.preamble__entries-container {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
margin-left: 0.6em;
|
margin-left: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preamble__entry {
|
.preamble__entry {
|
||||||
font-size: 1.4em;
|
margin-bottom: 0.15rem;
|
||||||
color: @Themed("#ffffff", "#2f3136");
|
color: @Themed("#ffffff", "#2f3136");
|
||||||
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preamble__entry--small {
|
.preamble__entry--small {
|
||||||
font-size: 1em;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog {
|
.chatlog {
|
||||||
max-width: 100%;
|
margin-top: 0.6rem;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid @Themed("rgba(255, 255, 255, 0.1)", "#eceeef");
|
||||||
|
border-bottom: 1px solid @Themed("rgba(255, 255, 255, 0.1)", "#eceeef");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__message-group {
|
.chatlog__message-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message-container {
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message-container--highlighted {
|
||||||
|
background-color: @Themed("rgba(114, 137, 218, 0.2)", "rgba(114, 137, 218, 0.2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message-container--pinned {
|
||||||
|
background-color: @Themed("rgba(249, 168, 37, 0.05)", "rgba(249, 168, 37, 0.05)");
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin: 0 0.6em;
|
|
||||||
padding: 0.9em 0;
|
|
||||||
border-top: 1px solid @Themed("rgba(255, 255, 255, 0.1)", "#eceeef");
|
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
direction: ltr;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message:hover {
|
||||||
|
background-color: @Themed("#32353b", "#fafafa");
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message:hover .chatlog__short-timestamp {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message-aside {
|
||||||
|
grid-column: 1;
|
||||||
|
width: 72px;
|
||||||
|
padding: 0.15rem 0.15rem 0 0.15rem;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-symbol {
|
.chatlog__reference-symbol {
|
||||||
grid-column: 1;
|
height: 12px;
|
||||||
margin: 8px 4px 4px 18px;
|
margin: 4px 4px 4px 36px;
|
||||||
border-left: 2px solid @Themed("#4f545c", "#c7ccd1");
|
border-left: 2px solid @Themed("#4f545c", "#c7ccd1");
|
||||||
border-top: 2px solid @Themed("#4f545c", "#c7ccd1");
|
border-top: 2px solid @Themed("#4f545c", "#c7ccd1");
|
||||||
border-radius: 8px 0 0 0;
|
border-radius: 8px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatlog__avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__short-timestamp {
|
||||||
|
display: none;
|
||||||
|
color: @Themed("#a3a6aa", "#5e6772");
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
direction: ltr;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__message-primary {
|
||||||
|
grid-column: 2;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.chatlog__reference {
|
.chatlog__reference {
|
||||||
display: flex;
|
display: flex;
|
||||||
grid-column: 2;
|
margin-bottom: 0.15rem;
|
||||||
margin-bottom: 0.25em;
|
align-items: center;
|
||||||
font-size: 0.875em;
|
color: @Themed("#b5b6b8", "#5f5f60");
|
||||||
|
font-size: 0.875rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
align-items: center;
|
|
||||||
color: @Themed("#b5b6b8", "#5f5f60");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-avatar {
|
.chatlog__reference-avatar {
|
||||||
border-radius: 50%;
|
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-name {
|
.chatlog__reference-author {
|
||||||
margin-right: 0.3em;
|
margin-right: 0.3rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,13 +220,8 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-content a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__reference-link {
|
.chatlog__reference-link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: @Themed("#b5b6b8", "#5f5f60");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-link * {
|
.chatlog__reference-link * {
|
||||||
|
@ -276,83 +229,75 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatlog__reference-link .hljs {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
.chatlog__reference-link:hover {
|
.chatlog__reference-link:hover {
|
||||||
color: @Themed("#ffffff", "#2f3136");
|
color: @Themed("#ffffff", "#2f3136");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-link:hover *:not(.spoiler-text) {
|
.chatlog__reference-link:hover *:not(.chatlog__markdown-spoiler) {
|
||||||
color: @Themed("#ffffff", "#2f3136");
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reference-edited-timestamp {
|
.chatlog__reference-edited-timestamp {
|
||||||
margin-left: 0.25em;
|
margin-left: 0.25rem;
|
||||||
font-size: 0.8em;
|
color: @Themed("#a3a6aa", "#5e6772");
|
||||||
unicode-bidi: bidi-override;
|
font-size: 0.75rem;
|
||||||
color: @Themed("rgba(255, 255, 255, 0.2)", "#747f8d");
|
font-weight: 500;
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__author-avatar-container {
|
|
||||||
grid-column: 1;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__author-avatar {
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
image-rendering: high-quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__messages {
|
|
||||||
grid-column: 2;
|
|
||||||
min-width: 50%;
|
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__author-name {
|
.chatlog__header {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__author {
|
||||||
font-weight: @Themed("500", "600");
|
font-weight: @Themed("500", "600");
|
||||||
color: @Themed("#ffffff", "#2f3136");
|
color: @Themed("#ffffff", "#2f3136");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatlog__bot-label {
|
||||||
|
position: relative;
|
||||||
|
top: -.1rem;
|
||||||
|
margin-left: 0.3rem;
|
||||||
|
padding: 0.05rem 0.3rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #5865F2;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
.chatlog__timestamp {
|
.chatlog__timestamp {
|
||||||
margin-left: 0.3em;
|
margin-left: 0.3rem;
|
||||||
font-size: 0.75em;
|
color: @Themed("#a3a6aa", "#5e6772");
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
unicode-bidi: bidi-override;
|
unicode-bidi: bidi-override;
|
||||||
color: @Themed("rgba(255, 255, 255, 0.2)", "#747f8d");
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__message {
|
|
||||||
padding: 0.1em 0.3em;
|
|
||||||
margin: 0 -0.3em;
|
|
||||||
background-color: transparent;
|
|
||||||
transition: background-color 1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__message--highlighted {
|
|
||||||
background-color: @Themed("rgba(114, 137, 218, 0.2)", "rgba(114, 137, 218, 0.2)");
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__message--pinned {
|
|
||||||
background-color: @Themed("rgba(249, 168, 37, 0.05)", "rgba(249, 168, 37, 0.05)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__content {
|
.chatlog__content {
|
||||||
font-size: 0.95em;
|
padding-right: 1rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__edited-timestamp {
|
.chatlog__edited-timestamp {
|
||||||
margin-left: 0.15em;
|
margin-left: 0.15rem;
|
||||||
font-size: 0.8em;
|
color: @Themed("#a3a6aa", "#5e6772");
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__attachment {
|
.chatlog__attachment {
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 0.3em;
|
width: fit-content;
|
||||||
|
margin-top: 0.3rem;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -368,18 +313,18 @@
|
||||||
|
|
||||||
.chatlog__attachment-spoiler-caption {
|
.chatlog__attachment-spoiler-caption {
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 999;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
z-index: 999;
|
||||||
padding: 0.4em 0.8em;
|
padding: 0.4rem 0.8rem;
|
||||||
background-color: rgba(0, 0, 0, 0.9);
|
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
color: #dcddde;
|
color: #dcddde;
|
||||||
font-size: 0.9em;
|
font-size: 0.9rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__attachment--hidden .chatlog__attachment-spoiler-caption {
|
.chatlog__attachment--hidden .chatlog__attachment-spoiler-caption {
|
||||||
|
@ -391,9 +336,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__attachment-media {
|
.chatlog__attachment-media {
|
||||||
vertical-align: top;
|
|
||||||
max-width: 45vw;
|
max-width: 45vw;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
|
vertical-align: top;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,13 +347,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__attachment-generic {
|
.chatlog__attachment-generic {
|
||||||
width: 100%;
|
|
||||||
max-width: 520px;
|
max-width: 520px;
|
||||||
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: @Themed("#2f3136", "#f2f3f5");
|
|
||||||
border: 1px solid @Themed("#292b2f", "#ebedef");
|
border: 1px solid @Themed("#292b2f", "#ebedef");
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
background-color: @Themed("#2f3136", "#f2f3f5");
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,19 +379,15 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__edited-timestamp {
|
|
||||||
color: @Themed("rgba(255, 255, 255, 0.2)", "#747f8d");
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatlog__embed {
|
.chatlog__embed {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 0.3em;
|
margin-top: 0.3rem;
|
||||||
max-width: 520px;
|
max-width: 520px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-color-pill {
|
.chatlog__embed-color-pill {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 0.25em;
|
width: 0.25rem;
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
}
|
}
|
||||||
|
@ -458,11 +399,11 @@
|
||||||
.chatlog__embed-content-container {
|
.chatlog__embed-content-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0.5em 0.6em;
|
padding: 0.5rem 0.6rem;
|
||||||
background-color: @Themed("rgba(46, 48, 54, 0.3)", "rgba(249, 249, 249, 0.3)");
|
|
||||||
border: 1px solid @Themed("rgba(46, 48, 54, 0.6)", "rgba(204, 204, 204, 0.3)");
|
border: 1px solid @Themed("rgba(46, 48, 54, 0.6)", "rgba(204, 204, 204, 0.3)");
|
||||||
border-top-right-radius: 3px;
|
border-top-right-radius: 3px;
|
||||||
border-bottom-right-radius: 3px;
|
border-bottom-right-radius: 3px;
|
||||||
|
background-color: @Themed("rgba(46, 48, 54, 0.3)", "rgba(249, 249, 249, 0.3)");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-content {
|
.chatlog__embed-content {
|
||||||
|
@ -476,83 +417,83 @@
|
||||||
|
|
||||||
.chatlog__embed-author {
|
.chatlog__embed-author {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: 0.3rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-author-icon {
|
.chatlog__embed-author-icon {
|
||||||
margin-right: 0.5em;
|
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-author-name {
|
.chatlog__embed-author {
|
||||||
font-size: 0.875em;
|
color: @Themed("#ffffff", "#4f545c")
|
||||||
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
unicode-bidi: bidi-override;
|
unicode-bidi: bidi-override;
|
||||||
color: @Themed("#ffffff", "#4f545c")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-author-name-link {
|
.chatlog__embed-author-link {
|
||||||
color: @Themed("#ffffff", "#4f545c");
|
color: @Themed("#ffffff", "#4f545c");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-title {
|
.chatlog__embed-title {
|
||||||
margin-bottom: 0.2em;
|
margin-bottom: 0.75rem;
|
||||||
font-size: 0.875em;
|
|
||||||
font-weight: 600;
|
|
||||||
color: @Themed("#ffffff", "#4f545c");
|
color: @Themed("#ffffff", "#4f545c");
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-description {
|
.chatlog__embed-description {
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: @Themed("#dcddde", "#2e3338");
|
color: @Themed("#dcddde", "#2e3338");
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-fields {
|
.chatlog__embed-fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0 0.5em;
|
gap: 0 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-field {
|
.chatlog__embed-field {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
max-width: 506px;
|
max-width: 506px;
|
||||||
padding-top: 0.6em;
|
padding-top: 0.6rem;
|
||||||
font-size: 0.875em;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-field--inline {
|
.chatlog__embed-field--inline {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
min-width: 150px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-field-name {
|
.chatlog__embed-field-name {
|
||||||
margin-bottom: 0.2em;
|
margin-bottom: 0.2rem;
|
||||||
font-weight: 600;
|
|
||||||
color: @Themed("#ffffff", "#36393e");
|
color: @Themed("#ffffff", "#36393e");
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-field-value {
|
.chatlog__embed-field-value {
|
||||||
font-weight: 500;
|
|
||||||
color: @Themed("#dcddde", "#2e3338");
|
color: @Themed("#dcddde", "#2e3338");
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-thumbnail {
|
.chatlog__embed-thumbnail {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
margin-left: 1.2em;
|
|
||||||
max-width: 80px;
|
max-width: 80px;
|
||||||
max-height: 80px;
|
max-height: 80px;
|
||||||
|
margin-left: 1.2rem;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-image-container {
|
.chatlog__embed-image-container {
|
||||||
margin-top: 0.6em;
|
margin-top: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-image {
|
.chatlog__embed-image {
|
||||||
|
@ -562,28 +503,28 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-footer {
|
.chatlog__embed-footer {
|
||||||
margin-top: 0.6em;
|
margin-top: 0.6rem;
|
||||||
color: @Themed("#dcddde", "#2e3338");
|
color: @Themed("#dcddde", "#2e3338");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-footer-icon {
|
.chatlog__embed-footer-icon {
|
||||||
margin-right: 0.2em;
|
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
margin-right: 0.2rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-footer-text {
|
.chatlog__embed-footer-text {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 0.75em;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-plainimage {
|
.chatlog__embed-plainimage {
|
||||||
vertical-align: top;
|
|
||||||
max-width: 45vw;
|
max-width: 45vw;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
|
vertical-align: top;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,7 +533,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-youtube-container {
|
.chatlog__embed-youtube-container {
|
||||||
margin-top: 0.6em;
|
margin-top: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__embed-youtube {
|
.chatlog__embed-youtube {
|
||||||
|
@ -616,51 +557,132 @@
|
||||||
|
|
||||||
.chatlog__reaction {
|
.chatlog__reaction {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
margin: 0.35rem 0.1rem 0.1rem 0;
|
||||||
margin: 0.35em 0.1em 0.1em 0;
|
padding: 0.125rem 0.375rem;
|
||||||
padding: .125rem .375rem;
|
border: 1px solid transparent;
|
||||||
background-color: @Themed("#2f3136", "#f2f3f5");
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-color: transparent;
|
background-color: @Themed("#2f3136", "#f2f3f5");
|
||||||
border-width: 1px;
|
align-items: center;
|
||||||
border-style: solid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reaction:hover {
|
.chatlog__reaction:hover {
|
||||||
border-color: @Themed("hsla(0,0%,100%,.2)", "rgba(0, 0, 0, 0.2)");
|
border: 1px solid @Themed("hsla(0,0%,100%,.2)", "rgba(0, 0, 0, 0.2)");
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
background-color: @Themed("transparent", "white");
|
background-color: @Themed("transparent", "white");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reaction-count {
|
.chatlog__reaction-count {
|
||||||
min-width: 9px;
|
min-width: 9px;
|
||||||
margin-left: 0.35em;
|
margin-left: 0.35rem;
|
||||||
font-size: 0.875em;
|
|
||||||
color: @Themed("#b9bbbe", "#4f5660");
|
color: @Themed("#b9bbbe", "#4f5660");
|
||||||
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__reaction:hover .chatlog__reaction-count {
|
.chatlog__reaction:hover .chatlog__reaction-count {
|
||||||
color: @Themed("#dcddde", "#2e3338");
|
color: @Themed("#dcddde", "#2e3338");
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatlog__bot-tag {
|
.chatlog__markdown {
|
||||||
position: relative;
|
max-width: 100%;
|
||||||
top: -.1em;
|
|
||||||
margin-left: 0.3em;
|
|
||||||
padding: 0.05em 0.3em;
|
|
||||||
border-radius: 3px;
|
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
background-color: #5865F2;
|
overflow-wrap: break-word;
|
||||||
color: #ffffff;
|
}
|
||||||
font-size: 0.625em;
|
|
||||||
|
.chatlog__markdown-preserve {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-spoiler {
|
||||||
|
background-color: @Themed("rgba(255, 255, 255, 0.1)", "rgba(0, 0, 0, 0.1)");
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-spoiler--hidden {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: @Themed("#202225", "#b9bbbe");
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-spoiler--hidden:hover {
|
||||||
|
background-color: @Themed("rgba(32, 34, 37, 0.8)", "rgba(185, 187, 190, 0.8)");
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-spoiler--hidden::selection {
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-quote {
|
||||||
|
display: flex;
|
||||||
|
margin: 0.05rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-quote-border {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
border: 2px solid @Themed("#4f545c", "#c7ccd1");
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-pre {
|
||||||
|
background-color: @Themed("#2f3136", "#f9f9f9");
|
||||||
|
font-family: "Consolas", "Courier New", Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-pre--multiline {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 2px solid @Themed("#282b30", "#f3f3f3");
|
||||||
|
border-radius: 5px;
|
||||||
|
color: @Themed("#b9bbbe", "#657b83");
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-pre--multiline.hljs {
|
||||||
|
background-color: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-pre--inline {
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-mention{
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0 2px;
|
||||||
|
background-color: @Themed("rgba(88, 101, 242, .3)", "rgba(88, 101, 242, .15)");
|
||||||
|
color: @Themed("#dee0fc", "#505cdc");
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-mention:hover {
|
||||||
|
background-color: #5865f2;
|
||||||
|
color: #ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__markdown-timestamp{
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0 2px;
|
||||||
|
color: @Themed("#a3a6aa", "#5e6772");
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__emoji {
|
||||||
|
width: 1.325rem;
|
||||||
|
height: 1.325rem;
|
||||||
|
margin: 0 0.06rem;
|
||||||
|
vertical-align: -0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__emoji--small {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatlog__emoji--large {
|
||||||
|
width: 2.8rem;
|
||||||
|
height: 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.postamble {
|
.postamble {
|
||||||
margin: 1.4em 0.3em 0.6em 0.3em;
|
padding: 1.25rem;
|
||||||
padding: 1em;
|
|
||||||
border-top: 1px solid @Themed("rgba(255, 255, 255, 0.1)", "#eceeef");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.postamble__entry {
|
.postamble__entry {
|
||||||
|
@ -669,16 +691,16 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@{/* Syntax highlighting */}
|
@{/* Syntax highlighting */}
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/solarized-@(Model.ThemeName.ToLowerInvariant()).min.css">
|
<link rel="stylesheet" href="@ResolveUrlAsync($"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/solarized-{Model.ThemeName.ToLowerInvariant()}.min.css")">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
|
<script src="@ResolveUrlAsync("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('.pre--multiline').forEach(e => hljs.highlightBlock(e));
|
document.querySelectorAll('.chatlog__markdown-pre--multiline').forEach(e => hljs.highlightBlock(e));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@{/* Lottie animation support */}
|
@{/* Lottie animation support */}
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.8.1/lottie.min.js"></script>
|
<script src="@ResolveUrlAsync("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 => {
|
||||||
|
@ -702,12 +724,12 @@
|
||||||
@{/* Scripts */}
|
@{/* Scripts */}
|
||||||
<script>
|
<script>
|
||||||
function scrollToMessage(event, id) {
|
function scrollToMessage(event, id) {
|
||||||
var element = document.getElementById('message-' + id);
|
var element = document.getElementById('chatlog__message-container-' + id);
|
||||||
|
if (!element)
|
||||||
|
return;
|
||||||
|
|
||||||
if (element) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
element.classList.add('chatlog__message-container--highlighted');
|
||||||
element.classList.add('chatlog__message--highlighted');
|
|
||||||
|
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: element.getBoundingClientRect().top - document.body.getBoundingClientRect().top - (window.innerHeight / 2),
|
top: element.getBoundingClientRect().top - document.body.getBoundingClientRect().top - (window.innerHeight / 2),
|
||||||
|
@ -715,30 +737,33 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
window.setTimeout(function() {
|
window.setTimeout(function() {
|
||||||
element.classList.remove('chatlog__message--highlighted');
|
element.classList.remove('chatlog__message-container--highlighted');
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function showSpoiler(event, element) {
|
function showSpoiler(event, element) {
|
||||||
if (element && element.classList.contains('spoiler-text--hidden')) {
|
if (!element)
|
||||||
event.preventDefault();
|
return;
|
||||||
element.classList.remove('spoiler-text--hidden');
|
|
||||||
}
|
if (element.classList.contains('chatlog__attachment--hidden')) {
|
||||||
if (element && element.classList.contains('chatlog__attachment--hidden')) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
element.classList.remove('chatlog__attachment--hidden');
|
element.classList.remove('chatlog__attachment--hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.classList.contains('chatlog__markdown-spoiler--hidden')) {
|
||||||
|
event.preventDefault();
|
||||||
|
element.classList.remove('chatlog__markdown-spoiler--hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@{/* Icons */}
|
@{/* Icons */}
|
||||||
<svg style="display: none">
|
<svg style="display: none">
|
||||||
<symbol id="icon-attachment" viewBox="0 0 720 960">
|
<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="#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="#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="#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="#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>
|
||||||
</svg>
|
</svg>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -13,7 +14,7 @@ internal class HtmlMessageWriter : MessageWriter
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
private readonly string _themeName;
|
private readonly string _themeName;
|
||||||
|
|
||||||
private readonly List<Message> _messageGroupBuffer = new();
|
private readonly List<Message> _messageGroup = new();
|
||||||
|
|
||||||
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
|
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
|
||||||
: base(stream, context)
|
: base(stream, context)
|
||||||
|
@ -22,6 +23,23 @@ internal class HtmlMessageWriter : MessageWriter
|
||||||
_themeName = themeName;
|
_themeName = themeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanJoinGroup(Message message)
|
||||||
|
{
|
||||||
|
var lastMessage = _messageGroup.LastOrDefault();
|
||||||
|
if (lastMessage is null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
public override async ValueTask WritePreambleAsync(CancellationToken cancellationToken = default)
|
public override async ValueTask WritePreambleAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var templateContext = new PreambleTemplateContext(Context, _themeName);
|
var templateContext = new PreambleTemplateContext(Context, _themeName);
|
||||||
|
@ -34,10 +52,10 @@ internal class HtmlMessageWriter : MessageWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask WriteMessageGroupAsync(
|
private async ValueTask WriteMessageGroupAsync(
|
||||||
MessageGroup messageGroup,
|
IReadOnlyList<Message> messages,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var templateContext = new MessageGroupTemplateContext(Context, messageGroup);
|
var templateContext = new MessageGroupTemplateContext(Context, messages);
|
||||||
|
|
||||||
// We are not writing directly to output because Razor
|
// We are not writing directly to output because Razor
|
||||||
// does not actually do asynchronous writes to stream.
|
// does not actually do asynchronous writes to stream.
|
||||||
|
@ -52,31 +70,26 @@ internal class HtmlMessageWriter : MessageWriter
|
||||||
{
|
{
|
||||||
await base.WriteMessageAsync(message, cancellationToken);
|
await base.WriteMessageAsync(message, cancellationToken);
|
||||||
|
|
||||||
// If message group is empty or the given message can be grouped, buffer the given message
|
// If the message can be grouped, buffer it for now
|
||||||
if (!_messageGroupBuffer.Any() || MessageGroup.CanJoin(_messageGroupBuffer.Last(), message))
|
if (CanJoinGroup( message))
|
||||||
{
|
{
|
||||||
_messageGroupBuffer.Add(message);
|
_messageGroup.Add(message);
|
||||||
}
|
}
|
||||||
// Otherwise, flush the group and render messages
|
// Otherwise, flush the group and render messages
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer), cancellationToken);
|
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
|
||||||
|
|
||||||
_messageGroupBuffer.Clear();
|
_messageGroup.Clear();
|
||||||
_messageGroupBuffer.Add(message);
|
_messageGroup.Add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask WritePostambleAsync(CancellationToken cancellationToken = default)
|
public override async ValueTask WritePostambleAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Flush current message group
|
// Flush current message group
|
||||||
if (_messageGroupBuffer.Any())
|
if (_messageGroup.Any())
|
||||||
{
|
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
|
||||||
await WriteMessageGroupAsync(
|
|
||||||
MessageGroup.Join(_messageGroupBuffer),
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var templateContext = new PostambleTemplateContext(Context, MessagesWritten);
|
var templateContext = new PostambleTemplateContext(Context, MessagesWritten);
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,37 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
{
|
{
|
||||||
var (tagOpen, tagClose) = formatting.Kind switch
|
var (tagOpen, tagClose) = formatting.Kind switch
|
||||||
{
|
{
|
||||||
FormattingKind.Bold => ("<strong>", "</strong>"),
|
FormattingKind.Bold => (
|
||||||
FormattingKind.Italic => ("<em>", "</em>"),
|
"<strong>",
|
||||||
FormattingKind.Underline => ("<u>", "</u>"),
|
"</strong>"
|
||||||
FormattingKind.Strikethrough => ("<s>", "</s>"),
|
),
|
||||||
|
|
||||||
|
FormattingKind.Italic => (
|
||||||
|
"<em>",
|
||||||
|
"</em>"
|
||||||
|
),
|
||||||
|
|
||||||
|
FormattingKind.Underline => (
|
||||||
|
"<u>",
|
||||||
|
"</u>"
|
||||||
|
),
|
||||||
|
|
||||||
|
FormattingKind.Strikethrough => (
|
||||||
|
"<s>",
|
||||||
|
"</s>"
|
||||||
|
),
|
||||||
|
|
||||||
FormattingKind.Spoiler => (
|
FormattingKind.Spoiler => (
|
||||||
"<span class=\"spoiler-text spoiler-text--hidden\" onclick=\"showSpoiler(event, this)\">", "</span>"),
|
"<span class=\"chatlog__markdown-spoiler chatlog__markdown-spoiler--hidden\" onclick=\"showSpoiler(event, this)\">",
|
||||||
|
"</span>"
|
||||||
|
),
|
||||||
|
|
||||||
FormattingKind.Quote => (
|
FormattingKind.Quote => (
|
||||||
"<div class=\"quote\"><div class=\"quote-border\"></div><div class=\"quote-content\">", "</div></div>"),
|
"<div class=\"chatlog__markdown-quote\"><div class=\"chatlog__markdown-quote-border\"></div><div class=\"chatlog__markdown-quote-content\">",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(formatting.Kind))
|
"</div></div>"
|
||||||
|
),
|
||||||
|
|
||||||
|
_ => throw new InvalidOperationException($"Unknown formatting kind '{formatting.Kind}'.")
|
||||||
};
|
};
|
||||||
|
|
||||||
_buffer.Append(tagOpen);
|
_buffer.Append(tagOpen);
|
||||||
|
@ -54,7 +76,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
protected override MarkdownNode VisitInlineCodeBlock(InlineCodeBlockNode inlineCodeBlock)
|
protected override MarkdownNode VisitInlineCodeBlock(InlineCodeBlockNode inlineCodeBlock)
|
||||||
{
|
{
|
||||||
_buffer
|
_buffer
|
||||||
.Append("<span class=\"pre pre--inline\">")
|
.Append("<span class=\"chatlog__markdown-pre chatlog__markdown-pre--inline\">")
|
||||||
.Append(HtmlEncode(inlineCodeBlock.Code))
|
.Append(HtmlEncode(inlineCodeBlock.Code))
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
|
|
||||||
|
@ -68,7 +90,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
: "nohighlight";
|
: "nohighlight";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append($"<div class=\"pre pre--multiline {highlightCssClass}\">")
|
.Append($"<div class=\"chatlog__markdown-pre chatlog__markdown-pre--multiline {highlightCssClass}\">")
|
||||||
.Append(HtmlEncode(multiLineCodeBlock.Code))
|
.Append(HtmlEncode(multiLineCodeBlock.Code))
|
||||||
.Append("</div>");
|
.Append("</div>");
|
||||||
|
|
||||||
|
@ -98,10 +120,10 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
|
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
|
||||||
{
|
{
|
||||||
var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated);
|
var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated);
|
||||||
var jumboClass = _isJumbo ? "emoji--large" : "";
|
var jumboClass = _isJumbo ? "chatlog__emoji--large" : "";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append($"<img loading=\"lazy\" class=\"emoji {jumboClass}\" alt=\"{emoji.Name}\" title=\"{emoji.Code}\" src=\"{emojiImageUrl}\">");
|
.Append($"<img loading=\"lazy\" class=\"chatlog__emoji {jumboClass}\" alt=\"{emoji.Name}\" title=\"{emoji.Code}\" src=\"{emojiImageUrl}\">");
|
||||||
|
|
||||||
return base.VisitEmoji(emoji);
|
return base.VisitEmoji(emoji);
|
||||||
}
|
}
|
||||||
|
@ -111,14 +133,14 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
if (mention.Kind == MentionKind.Everyone)
|
if (mention.Kind == MentionKind.Everyone)
|
||||||
{
|
{
|
||||||
_buffer
|
_buffer
|
||||||
.Append("<span class=\"mention\">")
|
.Append("<span class=\"chatlog__markdown-mention\">")
|
||||||
.Append("@everyone")
|
.Append("@everyone")
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
}
|
}
|
||||||
else if (mention.Kind == MentionKind.Here)
|
else if (mention.Kind == MentionKind.Here)
|
||||||
{
|
{
|
||||||
_buffer
|
_buffer
|
||||||
.Append("<span class=\"mention\">")
|
.Append("<span class=\"chatlog__markdown-mention\">")
|
||||||
.Append("@here")
|
.Append("@here")
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
}
|
}
|
||||||
|
@ -129,7 +151,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
var nick = member?.Nick ?? "Unknown";
|
var nick = member?.Nick ?? "Unknown";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append($"<span class=\"mention\" title=\"{HtmlEncode(fullName)}\">")
|
.Append($"<span class=\"chatlog__markdown-mention\" title=\"{HtmlEncode(fullName)}\">")
|
||||||
.Append('@').Append(HtmlEncode(nick))
|
.Append('@').Append(HtmlEncode(nick))
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
}
|
}
|
||||||
|
@ -140,7 +162,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
var name = channel?.Name ?? "deleted-channel";
|
var name = channel?.Name ?? "deleted-channel";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append("<span class=\"mention\">")
|
.Append("<span class=\"chatlog__markdown-mention\">")
|
||||||
.Append(symbol).Append(HtmlEncode(name))
|
.Append(symbol).Append(HtmlEncode(name))
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
}
|
}
|
||||||
|
@ -151,11 +173,12 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
var color = role?.Color;
|
var color = role?.Color;
|
||||||
|
|
||||||
var style = color is not null
|
var style = color is not null
|
||||||
? $"color: rgb({color?.R}, {color?.G}, {color?.B}); background-color: rgba({color?.R}, {color?.G}, {color?.B}, 0.1);"
|
? $"color: rgb({color.Value.R}, {color.Value.G}, {color.Value.B}); " +
|
||||||
|
$"background-color: rgba({color.Value.R}, {color.Value.G}, {color.Value.B}, 0.1);"
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append($"<span class=\"mention\" style=\"{style}\">")
|
.Append($"<span class=\"chatlog__markdown-mention\" style=\"{style}\">")
|
||||||
.Append('@').Append(HtmlEncode(name))
|
.Append('@').Append(HtmlEncode(name))
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
}
|
}
|
||||||
|
@ -175,7 +198,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
: "Invalid date";
|
: "Invalid date";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append($"<span class=\"timestamp\" title=\"{HtmlEncode(longDateString)}\">")
|
.Append($"<span class=\"chatlog__markdown-timestamp\" title=\"{HtmlEncode(longDateString)}\">")
|
||||||
.Append(HtmlEncode(dateString))
|
.Append(HtmlEncode(dateString))
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,9 @@ internal static partial class MarkdownParser
|
||||||
|
|
||||||
// Capture any character until the end of the line
|
// Capture any character until the end of the line
|
||||||
// Opening 'greater than' character must be followed by whitespace
|
// Opening 'greater than' character must be followed by whitespace
|
||||||
|
// Text content is optional
|
||||||
private static readonly IMatcher<MarkdownNode> SingleLineQuoteNodeMatcher = new RegexMatcher<MarkdownNode>(
|
private static readonly IMatcher<MarkdownNode> SingleLineQuoteNodeMatcher = new RegexMatcher<MarkdownNode>(
|
||||||
new Regex("^>\\s(.+\n?)", DefaultRegexOptions),
|
new Regex("^>\\s(.*\n?)", DefaultRegexOptions),
|
||||||
(s, m) => new FormattingNode(FormattingKind.Quote, Parse(s.Relocate(m.Groups[1])))
|
(s, m) => new FormattingNode(FormattingKind.Quote, Parse(s.Relocate(m.Groups[1])))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ internal static partial class MarkdownParser
|
||||||
// This one is tricky as it ends up producing multiple separate captures which need to be joined
|
// This one is tricky as it ends up producing multiple separate captures which need to be joined
|
||||||
private static readonly IMatcher<MarkdownNode> RepeatedSingleLineQuoteNodeMatcher =
|
private static readonly IMatcher<MarkdownNode> RepeatedSingleLineQuoteNodeMatcher =
|
||||||
new RegexMatcher<MarkdownNode>(
|
new RegexMatcher<MarkdownNode>(
|
||||||
new Regex("(?:^>\\s(.+\n?)){2,}", DefaultRegexOptions),
|
new Regex("(?:^>\\s(.*\n?)){2,}", DefaultRegexOptions),
|
||||||
(_, m) =>
|
(_, m) =>
|
||||||
{
|
{
|
||||||
var content = string.Concat(m.Groups[1].Captures.Select(c => c.Value));
|
var content = string.Concat(m.Groups[1].Captures.Select(c => c.Value));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue