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 DiscordChatExporter.Core.Discord.Data
|
||||
@using DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||
@using DiscordChatExporter.Core.Utils.Extensions
|
||||
|
||||
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||
@inherits MiniRazor.TemplateBase<MessageGroupTemplateContext>
|
||||
|
||||
@{
|
||||
var firstMessage = Model.Messages.First();
|
||||
|
||||
ValueTask<string> ResolveUrlAsync(string url) => Model.ExportContext.ResolveMediaUrlAsync(url);
|
||||
|
||||
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date);
|
||||
|
@ -16,56 +19,78 @@
|
|||
|
||||
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
|
||||
? $"color: rgb({userColor?.R},{userColor?.G},{userColor?.B})"
|
||||
var userNick = firstMessage.Author.IsBot
|
||||
? firstMessage.Author.Name
|
||||
: userMember?.Nick ?? firstMessage.Author.Name;
|
||||
|
||||
var referencedUserMember = firstMessage.ReferencedMessage is not null
|
||||
? Model.ExportContext.TryGetMember(firstMessage.ReferencedMessage.Author.Id)
|
||||
: null;
|
||||
|
||||
var userNick = Model.MessageGroup.Author.IsBot
|
||||
? Model.MessageGroup.Author.Name
|
||||
: userMember?.Nick ?? Model.MessageGroup.Author.Name;
|
||||
|
||||
var referencedUserMember = Model.MessageGroup.ReferencedMessage is not null
|
||||
? Model.ExportContext.TryGetMember(Model.MessageGroup.ReferencedMessage.Author.Id)
|
||||
var referencedUserColor = firstMessage.ReferencedMessage is not null
|
||||
? Model.ExportContext.TryGetUserColor(firstMessage.ReferencedMessage.Author.Id)
|
||||
: null;
|
||||
|
||||
var referencedUserColor = Model.MessageGroup.ReferencedMessage is not null
|
||||
? Model.ExportContext.TryGetUserColor(Model.MessageGroup.ReferencedMessage.Author.Id)
|
||||
: null;
|
||||
|
||||
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
|
||||
var referencedUserNick = firstMessage.ReferencedMessage is not null
|
||||
? firstMessage.ReferencedMessage.Author.IsBot
|
||||
? firstMessage.ReferencedMessage.Author.Name
|
||||
: referencedUserMember?.Nick ?? firstMessage.ReferencedMessage.Author.Name
|
||||
: null;
|
||||
}
|
||||
|
||||
<div class="chatlog__message-group">
|
||||
@{/* Referenced message */}
|
||||
@if (Model.MessageGroup.Reference is not null)
|
||||
@for (var i = 0; i < Model.Messages.Count; i++)
|
||||
{
|
||||
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">
|
||||
@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
|
||||
{
|
||||
|
@ -73,57 +98,50 @@
|
|||
}
|
||||
</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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="chatlog__reference-unknown">
|
||||
<div class="chatlog__reference-unknown">
|
||||
Original message was deleted or could not be loaded.
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Avatar */}
|
||||
<div class="chatlog__author-avatar-container">
|
||||
<img class="chatlog__author-avatar" src="@await ResolveUrlAsync(Model.MessageGroup.Author.AvatarUrl)" alt="Avatar" loading="lazy">
|
||||
</div>
|
||||
|
||||
<div class="chatlog__messages">
|
||||
// Header
|
||||
<div class="chatlog__header">
|
||||
@{/* 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 */}
|
||||
@if (Model.MessageGroup.Author.IsBot)
|
||||
@{/* Bot label */}
|
||||
@if (message.Author.IsBot)
|
||||
{
|
||||
<span class="chatlog__bot-tag">BOT</span>
|
||||
<span class="chatlog__bot-label">BOT</span>
|
||||
}
|
||||
|
||||
@{/* Message timestamp */}
|
||||
<span class="chatlog__timestamp">@FormatDate(Model.MessageGroup.Timestamp)</span>
|
||||
@{/* Timestamp */}
|
||||
<span class="chatlog__timestamp">@FormatDate(message.Timestamp)</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Messages in a group */}
|
||||
@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)">
|
||||
@{/* Content */}
|
||||
@if (!string.IsNullOrWhiteSpace(message.Content) || message.EditedTimestamp is not null)
|
||||
{
|
||||
<div class="chatlog__content">
|
||||
<div class="markdown">
|
||||
@{/* Message content */}
|
||||
<span class="preserve-whitespace">@Raw(FormatMarkdown(message.Content))</span>
|
||||
<div class="chatlog__content chatlog__markdown">
|
||||
@{/* Text */}
|
||||
<span class="chatlog__markdown-preserve">@Raw(FormatMarkdown(message.Content))</span>
|
||||
|
||||
@{/* Edit timestamp */}
|
||||
@{/* Edited timestamp */}
|
||||
@if (message.EditedTimestamp is not null)
|
||||
{
|
||||
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value)">(edited)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@{/* Attachments */}
|
||||
|
@ -159,7 +177,7 @@
|
|||
{
|
||||
<div class="chatlog__attachment-generic">
|
||||
<svg class="chatlog__attachment-generic-icon">
|
||||
<use href="#icon-attachment" />
|
||||
<use href="#attachment-icon"/>
|
||||
</svg>
|
||||
<div class="chatlog__attachment-generic-name">
|
||||
<a href="@await ResolveUrlAsync(attachment.Url)">
|
||||
|
@ -202,7 +220,7 @@
|
|||
@{/* Color pill */}
|
||||
@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
|
||||
{
|
||||
|
@ -223,10 +241,10 @@
|
|||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||
{
|
||||
<span class="chatlog__embed-author-name">
|
||||
<span class="chatlog__embed-author">
|
||||
@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
|
||||
{
|
||||
|
@ -244,12 +262,12 @@
|
|||
@if (!string.IsNullOrWhiteSpace(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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -270,7 +288,7 @@
|
|||
@{/* Color pill */}
|
||||
@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
|
||||
{
|
||||
|
@ -291,10 +309,10 @@
|
|||
|
||||
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||
{
|
||||
<span class="chatlog__embed-author-name">
|
||||
<span class="chatlog__embed-author">
|
||||
@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
|
||||
{
|
||||
|
@ -312,12 +330,12 @@
|
|||
@if (!string.IsNullOrWhiteSpace(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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||
<div class="chatlog__markdown chatlog__markdown-preserve">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -326,7 +344,7 @@
|
|||
@if (!string.IsNullOrWhiteSpace(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>
|
||||
}
|
||||
|
||||
|
@ -340,14 +358,14 @@
|
|||
@if (!string.IsNullOrWhiteSpace(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>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(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>
|
||||
|
@ -434,13 +452,14 @@
|
|||
@foreach (var reaction in message.Reactions)
|
||||
{
|
||||
<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>
|
||||
</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;
|
||||
|
||||
|
@ -6,12 +8,12 @@ internal class MessageGroupTemplateContext
|
|||
{
|
||||
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;
|
||||
MessageGroup = messageGroup;
|
||||
Messages = messages;
|
||||
}
|
||||
|
||||
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
||||
|
|
|
@ -32,36 +32,38 @@
|
|||
@{/* Styling */}
|
||||
<style>
|
||||
@@font-face {
|
||||
font-family: Whitney;
|
||||
src: url(@await ResolveUrlAsync(GetFontUrl(300)));
|
||||
font-family: Whitney;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@font-face {
|
||||
font-family: Whitney;
|
||||
src: url(@await ResolveUrlAsync(GetFontUrl(400)));
|
||||
font-family: Whitney;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@@font-face {
|
||||
font-family: Whitney;
|
||||
src: url(@await ResolveUrlAsync(GetFontUrl(500)));
|
||||
font-family: Whitney;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@font-face {
|
||||
font-family: Whitney;
|
||||
src: url(@await ResolveUrlAsync(GetFontUrl(600)));
|
||||
font-family: Whitney;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@font-face {
|
||||
font-family: Whitney;
|
||||
src: url(@await ResolveUrlAsync(GetFontUrl(700)));
|
||||
font-family: Whitney;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: @Themed("#36393e", "#ffffff");
|
||||
color: @Themed("#dcddde", "#23262a");
|
||||
font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
|
@ -80,114 +82,15 @@
|
|||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.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;
|
||||
image-rendering: high-quality;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
.preamble {
|
||||
display: grid;
|
||||
margin: 0 0.3em 0.6em 0.3em;
|
||||
max-width: 100%;
|
||||
grid-template-columns: auto 1fr;
|
||||
max-width: 100%;
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
.preamble__guild-icon-container {
|
||||
|
@ -201,59 +104,114 @@
|
|||
|
||||
.preamble__entries-container {
|
||||
grid-column: 2;
|
||||
margin-left: 0.6em;
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
.preamble__entry {
|
||||
font-size: 1.4em;
|
||||
margin-bottom: 0.15rem;
|
||||
color: @Themed("#ffffff", "#2f3136");
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.preamble__entry--small {
|
||||
font-size: 1em;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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;
|
||||
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;
|
||||
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 {
|
||||
grid-column: 1;
|
||||
margin: 8px 4px 4px 18px;
|
||||
height: 12px;
|
||||
margin: 4px 4px 4px 36px;
|
||||
border-left: 2px solid @Themed("#4f545c", "#c7ccd1");
|
||||
border-top: 2px solid @Themed("#4f545c", "#c7ccd1");
|
||||
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 {
|
||||
display: flex;
|
||||
grid-column: 2;
|
||||
margin-bottom: 0.25em;
|
||||
font-size: 0.875em;
|
||||
margin-bottom: 0.15rem;
|
||||
align-items: center;
|
||||
color: @Themed("#b5b6b8", "#5f5f60");
|
||||
font-size: 0.875rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
align-items: center;
|
||||
color: @Themed("#b5b6b8", "#5f5f60");
|
||||
}
|
||||
|
||||
.chatlog__reference-avatar {
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 0.25em;
|
||||
margin-right: 0.25rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.chatlog__reference-name {
|
||||
margin-right: 0.3em;
|
||||
.chatlog__reference-author {
|
||||
margin-right: 0.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
@ -262,13 +220,8 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chatlog__reference-content a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chatlog__reference-link {
|
||||
cursor: pointer;
|
||||
color: @Themed("#b5b6b8", "#5f5f60");
|
||||
}
|
||||
|
||||
.chatlog__reference-link * {
|
||||
|
@ -276,83 +229,75 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chatlog__reference-link .hljs {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.chatlog__reference-link:hover {
|
||||
color: @Themed("#ffffff", "#2f3136");
|
||||
}
|
||||
|
||||
.chatlog__reference-link:hover *:not(.spoiler-text) {
|
||||
color: @Themed("#ffffff", "#2f3136");
|
||||
.chatlog__reference-link:hover *:not(.chatlog__markdown-spoiler) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.chatlog__reference-edited-timestamp {
|
||||
margin-left: 0.25em;
|
||||
font-size: 0.8em;
|
||||
unicode-bidi: bidi-override;
|
||||
color: @Themed("rgba(255, 255, 255, 0.2)", "#747f8d");
|
||||
}
|
||||
|
||||
.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%;
|
||||
margin-left: 0.25rem;
|
||||
color: @Themed("#a3a6aa", "#5e6772");
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
.chatlog__author-name {
|
||||
.chatlog__header {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.chatlog__author {
|
||||
font-weight: @Themed("500", "600");
|
||||
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 {
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.75em;
|
||||
margin-left: 0.3rem;
|
||||
color: @Themed("#a3a6aa", "#5e6772");
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
direction: ltr;
|
||||
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 {
|
||||
font-size: 0.95em;
|
||||
padding-right: 1rem;
|
||||
font-size: 0.95rem;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.chatlog__edited-timestamp {
|
||||
margin-left: 0.15em;
|
||||
font-size: 0.8em;
|
||||
margin-left: 0.15rem;
|
||||
color: @Themed("#a3a6aa", "#5e6772");
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chatlog__attachment {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-top: 0.3em;
|
||||
width: fit-content;
|
||||
margin-top: 0.3rem;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -368,18 +313,18 @@
|
|||
|
||||
.chatlog__attachment-spoiler-caption {
|
||||
display: none;
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 0.4em 0.8em;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
z-index: 999;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 20px;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
color: #dcddde;
|
||||
font-size: 0.9em;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
letter-spacing: 0.05rem;
|
||||
}
|
||||
|
||||
.chatlog__attachment--hidden .chatlog__attachment-spoiler-caption {
|
||||
|
@ -391,9 +336,9 @@
|
|||
}
|
||||
|
||||
.chatlog__attachment-media {
|
||||
vertical-align: top;
|
||||
max-width: 45vw;
|
||||
max-height: 500px;
|
||||
vertical-align: top;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
@ -402,13 +347,13 @@
|
|||
}
|
||||
|
||||
.chatlog__attachment-generic {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 10px;
|
||||
background-color: @Themed("#2f3136", "#f2f3f5");
|
||||
border: 1px solid @Themed("#292b2f", "#ebedef");
|
||||
border-radius: 3px;
|
||||
background-color: @Themed("#2f3136", "#f2f3f5");
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
@ -434,19 +379,15 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chatlog__edited-timestamp {
|
||||
color: @Themed("rgba(255, 255, 255, 0.2)", "#747f8d");
|
||||
}
|
||||
|
||||
.chatlog__embed {
|
||||
display: flex;
|
||||
margin-top: 0.3em;
|
||||
margin-top: 0.3rem;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.chatlog__embed-color-pill {
|
||||
flex-shrink: 0;
|
||||
width: 0.25em;
|
||||
width: 0.25rem;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
@ -458,11 +399,11 @@
|
|||
.chatlog__embed-content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5em 0.6em;
|
||||
background-color: @Themed("rgba(46, 48, 54, 0.3)", "rgba(249, 249, 249, 0.3)");
|
||||
padding: 0.5rem 0.6rem;
|
||||
border: 1px solid @Themed("rgba(46, 48, 54, 0.6)", "rgba(204, 204, 204, 0.3)");
|
||||
border-top-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 {
|
||||
|
@ -476,83 +417,83 @@
|
|||
|
||||
.chatlog__embed-author {
|
||||
display: flex;
|
||||
margin-bottom: 0.3em;
|
||||
margin-bottom: 0.3rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chatlog__embed-author-icon {
|
||||
margin-right: 0.5em;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 0.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.chatlog__embed-author-name {
|
||||
font-size: 0.875em;
|
||||
.chatlog__embed-author {
|
||||
color: @Themed("#ffffff", "#4f545c")
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
color: @Themed("#ffffff", "#4f545c")
|
||||
}
|
||||
|
||||
.chatlog__embed-author-name-link {
|
||||
.chatlog__embed-author-link {
|
||||
color: @Themed("#ffffff", "#4f545c");
|
||||
}
|
||||
|
||||
.chatlog__embed-title {
|
||||
margin-bottom: 0.2em;
|
||||
font-size: 0.875em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
color: @Themed("#ffffff", "#4f545c");
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chatlog__embed-description {
|
||||
font-weight: 500;
|
||||
font-size: 0.85em;
|
||||
color: @Themed("#dcddde", "#2e3338");
|
||||
font-weight: 500;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.chatlog__embed-fields {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 0.5em;
|
||||
gap: 0 0.5rem;
|
||||
}
|
||||
|
||||
.chatlog__embed-field {
|
||||
flex: 0;
|
||||
min-width: 100%;
|
||||
max-width: 506px;
|
||||
padding-top: 0.6em;
|
||||
font-size: 0.875em;
|
||||
padding-top: 0.6rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.chatlog__embed-field--inline {
|
||||
flex: 1;
|
||||
flex-basis: auto;
|
||||
min-width: 150px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.chatlog__embed-field-name {
|
||||
margin-bottom: 0.2em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.2rem;
|
||||
color: @Themed("#ffffff", "#36393e");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chatlog__embed-field-value {
|
||||
font-weight: 500;
|
||||
color: @Themed("#dcddde", "#2e3338");
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chatlog__embed-thumbnail {
|
||||
flex: 0;
|
||||
margin-left: 1.2em;
|
||||
max-width: 80px;
|
||||
max-height: 80px;
|
||||
margin-left: 1.2rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chatlog__embed-image-container {
|
||||
margin-top: 0.6em;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.chatlog__embed-image {
|
||||
|
@ -562,28 +503,28 @@
|
|||
}
|
||||
|
||||
.chatlog__embed-footer {
|
||||
margin-top: 0.6em;
|
||||
margin-top: 0.6rem;
|
||||
color: @Themed("#dcddde", "#2e3338");
|
||||
}
|
||||
|
||||
.chatlog__embed-footer-icon {
|
||||
margin-right: 0.2em;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.chatlog__embed-footer-text {
|
||||
vertical-align: middle;
|
||||
font-size: 0.75em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chatlog__embed-plainimage {
|
||||
vertical-align: top;
|
||||
max-width: 45vw;
|
||||
max-height: 500px;
|
||||
vertical-align: top;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
@ -592,7 +533,7 @@
|
|||
}
|
||||
|
||||
.chatlog__embed-youtube-container {
|
||||
margin-top: 0.6em;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.chatlog__embed-youtube {
|
||||
|
@ -616,51 +557,132 @@
|
|||
|
||||
.chatlog__reaction {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.35em 0.1em 0.1em 0;
|
||||
padding: .125rem .375rem;
|
||||
background-color: @Themed("#2f3136", "#f2f3f5");
|
||||
margin: 0.35rem 0.1rem 0.1rem 0;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
border-color: transparent;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
background-color: @Themed("#2f3136", "#f2f3f5");
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chatlog__reaction:hover {
|
||||
border-color: @Themed("hsla(0,0%,100%,.2)", "rgba(0, 0, 0, 0.2)");
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border: 1px solid @Themed("hsla(0,0%,100%,.2)", "rgba(0, 0, 0, 0.2)");
|
||||
background-color: @Themed("transparent", "white");
|
||||
}
|
||||
|
||||
.chatlog__reaction-count {
|
||||
min-width: 9px;
|
||||
margin-left: 0.35em;
|
||||
font-size: 0.875em;
|
||||
margin-left: 0.35rem;
|
||||
color: @Themed("#b9bbbe", "#4f5660");
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.chatlog__reaction:hover .chatlog__reaction-count {
|
||||
color: @Themed("#dcddde", "#2e3338");
|
||||
}
|
||||
|
||||
.chatlog__bot-tag {
|
||||
position: relative;
|
||||
top: -.1em;
|
||||
margin-left: 0.3em;
|
||||
padding: 0.05em 0.3em;
|
||||
border-radius: 3px;
|
||||
.chatlog__markdown {
|
||||
max-width: 100%;
|
||||
line-height: 1.3;
|
||||
background-color: #5865F2;
|
||||
color: #ffffff;
|
||||
font-size: 0.625em;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
margin: 1.4em 0.3em 0.6em 0.3em;
|
||||
padding: 1em;
|
||||
border-top: 1px solid @Themed("rgba(255, 255, 255, 0.1)", "#eceeef");
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.postamble__entry {
|
||||
|
@ -669,16 +691,16 @@
|
|||
</style>
|
||||
|
||||
@{/* Syntax highlighting */}
|
||||
<link rel="stylesheet" href="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>
|
||||
<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="@ResolveUrlAsync("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js")"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.pre--multiline').forEach(e => hljs.highlightBlock(e));
|
||||
document.querySelectorAll('.chatlog__markdown-pre--multiline').forEach(e => hljs.highlightBlock(e));
|
||||
});
|
||||
</script>
|
||||
|
||||
@{/* 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>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.chatlog__sticker--media[data-source]').forEach(e => {
|
||||
|
@ -702,12 +724,12 @@
|
|||
@{/* Scripts */}
|
||||
<script>
|
||||
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();
|
||||
|
||||
element.classList.add('chatlog__message--highlighted');
|
||||
element.classList.add('chatlog__message-container--highlighted');
|
||||
|
||||
window.scrollTo({
|
||||
top: element.getBoundingClientRect().top - document.body.getBoundingClientRect().top - (window.innerHeight / 2),
|
||||
|
@ -715,30 +737,33 @@
|
|||
});
|
||||
|
||||
window.setTimeout(function() {
|
||||
element.classList.remove('chatlog__message--highlighted');
|
||||
element.classList.remove('chatlog__message-container--highlighted');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function showSpoiler(event, element) {
|
||||
if (element && element.classList.contains('spoiler-text--hidden')) {
|
||||
event.preventDefault();
|
||||
element.classList.remove('spoiler-text--hidden');
|
||||
}
|
||||
if (element && element.classList.contains('chatlog__attachment--hidden')) {
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
if (element.classList.contains('chatlog__attachment--hidden')) {
|
||||
event.preventDefault();
|
||||
element.classList.remove('chatlog__attachment--hidden');
|
||||
}
|
||||
|
||||
if (element.classList.contains('chatlog__markdown-spoiler--hidden')) {
|
||||
event.preventDefault();
|
||||
element.classList.remove('chatlog__markdown-spoiler--hidden');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@{/* Icons */}
|
||||
<svg style="display: none">
|
||||
<symbol id="icon-attachment" 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" />
|
||||
<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"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
</head>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -13,7 +14,7 @@ internal class HtmlMessageWriter : MessageWriter
|
|||
private readonly TextWriter _writer;
|
||||
private readonly string _themeName;
|
||||
|
||||
private readonly List<Message> _messageGroupBuffer = new();
|
||||
private readonly List<Message> _messageGroup = new();
|
||||
|
||||
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
|
||||
: base(stream, context)
|
||||
|
@ -22,6 +23,23 @@ internal class HtmlMessageWriter : MessageWriter
|
|||
_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)
|
||||
{
|
||||
var templateContext = new PreambleTemplateContext(Context, _themeName);
|
||||
|
@ -34,10 +52,10 @@ internal class HtmlMessageWriter : MessageWriter
|
|||
}
|
||||
|
||||
private async ValueTask WriteMessageGroupAsync(
|
||||
MessageGroup messageGroup,
|
||||
IReadOnlyList<Message> messages,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var templateContext = new MessageGroupTemplateContext(Context, messageGroup);
|
||||
var templateContext = new MessageGroupTemplateContext(Context, messages);
|
||||
|
||||
// We are not writing directly to output because Razor
|
||||
// does not actually do asynchronous writes to stream.
|
||||
|
@ -52,31 +70,26 @@ internal class HtmlMessageWriter : MessageWriter
|
|||
{
|
||||
await base.WriteMessageAsync(message, cancellationToken);
|
||||
|
||||
// If message group is empty or the given message can be grouped, buffer the given message
|
||||
if (!_messageGroupBuffer.Any() || MessageGroup.CanJoin(_messageGroupBuffer.Last(), message))
|
||||
// If the message can be grouped, buffer it for now
|
||||
if (CanJoinGroup( message))
|
||||
{
|
||||
_messageGroupBuffer.Add(message);
|
||||
_messageGroup.Add(message);
|
||||
}
|
||||
// Otherwise, flush the group and render messages
|
||||
else
|
||||
{
|
||||
await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer), cancellationToken);
|
||||
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
|
||||
|
||||
_messageGroupBuffer.Clear();
|
||||
_messageGroupBuffer.Add(message);
|
||||
_messageGroup.Clear();
|
||||
_messageGroup.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask WritePostambleAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Flush current message group
|
||||
if (_messageGroupBuffer.Any())
|
||||
{
|
||||
await WriteMessageGroupAsync(
|
||||
MessageGroup.Join(_messageGroupBuffer),
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
if (_messageGroup.Any())
|
||||
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
|
||||
|
||||
var templateContext = new PostambleTemplateContext(Context, MessagesWritten);
|
||||
|
||||
|
|
|
@ -33,15 +33,37 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
{
|
||||
var (tagOpen, tagClose) = formatting.Kind switch
|
||||
{
|
||||
FormattingKind.Bold => ("<strong>", "</strong>"),
|
||||
FormattingKind.Italic => ("<em>", "</em>"),
|
||||
FormattingKind.Underline => ("<u>", "</u>"),
|
||||
FormattingKind.Strikethrough => ("<s>", "</s>"),
|
||||
FormattingKind.Bold => (
|
||||
"<strong>",
|
||||
"</strong>"
|
||||
),
|
||||
|
||||
FormattingKind.Italic => (
|
||||
"<em>",
|
||||
"</em>"
|
||||
),
|
||||
|
||||
FormattingKind.Underline => (
|
||||
"<u>",
|
||||
"</u>"
|
||||
),
|
||||
|
||||
FormattingKind.Strikethrough => (
|
||||
"<s>",
|
||||
"</s>"
|
||||
),
|
||||
|
||||
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 => (
|
||||
"<div class=\"quote\"><div class=\"quote-border\"></div><div class=\"quote-content\">", "</div></div>"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(formatting.Kind))
|
||||
"<div class=\"chatlog__markdown-quote\"><div class=\"chatlog__markdown-quote-border\"></div><div class=\"chatlog__markdown-quote-content\">",
|
||||
"</div></div>"
|
||||
),
|
||||
|
||||
_ => throw new InvalidOperationException($"Unknown formatting kind '{formatting.Kind}'.")
|
||||
};
|
||||
|
||||
_buffer.Append(tagOpen);
|
||||
|
@ -54,7 +76,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
protected override MarkdownNode VisitInlineCodeBlock(InlineCodeBlockNode inlineCodeBlock)
|
||||
{
|
||||
_buffer
|
||||
.Append("<span class=\"pre pre--inline\">")
|
||||
.Append("<span class=\"chatlog__markdown-pre chatlog__markdown-pre--inline\">")
|
||||
.Append(HtmlEncode(inlineCodeBlock.Code))
|
||||
.Append("</span>");
|
||||
|
||||
|
@ -68,7 +90,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
: "nohighlight";
|
||||
|
||||
_buffer
|
||||
.Append($"<div class=\"pre pre--multiline {highlightCssClass}\">")
|
||||
.Append($"<div class=\"chatlog__markdown-pre chatlog__markdown-pre--multiline {highlightCssClass}\">")
|
||||
.Append(HtmlEncode(multiLineCodeBlock.Code))
|
||||
.Append("</div>");
|
||||
|
||||
|
@ -98,10 +120,10 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
|
||||
{
|
||||
var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated);
|
||||
var jumboClass = _isJumbo ? "emoji--large" : "";
|
||||
var jumboClass = _isJumbo ? "chatlog__emoji--large" : "";
|
||||
|
||||
_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);
|
||||
}
|
||||
|
@ -111,14 +133,14 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
if (mention.Kind == MentionKind.Everyone)
|
||||
{
|
||||
_buffer
|
||||
.Append("<span class=\"mention\">")
|
||||
.Append("<span class=\"chatlog__markdown-mention\">")
|
||||
.Append("@everyone")
|
||||
.Append("</span>");
|
||||
}
|
||||
else if (mention.Kind == MentionKind.Here)
|
||||
{
|
||||
_buffer
|
||||
.Append("<span class=\"mention\">")
|
||||
.Append("<span class=\"chatlog__markdown-mention\">")
|
||||
.Append("@here")
|
||||
.Append("</span>");
|
||||
}
|
||||
|
@ -129,7 +151,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
var nick = member?.Nick ?? "Unknown";
|
||||
|
||||
_buffer
|
||||
.Append($"<span class=\"mention\" title=\"{HtmlEncode(fullName)}\">")
|
||||
.Append($"<span class=\"chatlog__markdown-mention\" title=\"{HtmlEncode(fullName)}\">")
|
||||
.Append('@').Append(HtmlEncode(nick))
|
||||
.Append("</span>");
|
||||
}
|
||||
|
@ -140,7 +162,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
var name = channel?.Name ?? "deleted-channel";
|
||||
|
||||
_buffer
|
||||
.Append("<span class=\"mention\">")
|
||||
.Append("<span class=\"chatlog__markdown-mention\">")
|
||||
.Append(symbol).Append(HtmlEncode(name))
|
||||
.Append("</span>");
|
||||
}
|
||||
|
@ -151,11 +173,12 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
var color = role?.Color;
|
||||
|
||||
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
|
||||
.Append($"<span class=\"mention\" style=\"{style}\">")
|
||||
.Append($"<span class=\"chatlog__markdown-mention\" style=\"{style}\">")
|
||||
.Append('@').Append(HtmlEncode(name))
|
||||
.Append("</span>");
|
||||
}
|
||||
|
@ -175,7 +198,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|||
: "Invalid date";
|
||||
|
||||
_buffer
|
||||
.Append($"<span class=\"timestamp\" title=\"{HtmlEncode(longDateString)}\">")
|
||||
.Append($"<span class=\"chatlog__markdown-timestamp\" title=\"{HtmlEncode(longDateString)}\">")
|
||||
.Append(HtmlEncode(dateString))
|
||||
.Append("</span>");
|
||||
|
||||
|
|
|
@ -77,8 +77,9 @@ internal static partial class MarkdownParser
|
|||
|
||||
// Capture any character until the end of the line
|
||||
// Opening 'greater than' character must be followed by whitespace
|
||||
// Text content is optional
|
||||
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])))
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
private static readonly IMatcher<MarkdownNode> RepeatedSingleLineQuoteNodeMatcher =
|
||||
new RegexMatcher<MarkdownNode>(
|
||||
new Regex("(?:^>\\s(.+\n?)){2,}", DefaultRegexOptions),
|
||||
new Regex("(?:^>\\s(.*\n?)){2,}", DefaultRegexOptions),
|
||||
(_, m) =>
|
||||
{
|
||||
var content = string.Concat(m.Groups[1].Captures.Select(c => c.Value));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue