|
|
|
@ -176,157 +176,230 @@
|
|
|
|
|
@{/* Embeds */}
|
|
|
|
|
@foreach (var embed in message.Embeds)
|
|
|
|
|
{
|
|
|
|
|
var youTubeVideo = embed.TryGetYouTubeVideo();
|
|
|
|
|
// Spotify embed
|
|
|
|
|
if (embed.TryGetSpotifyTrack() is { } spotifyTrackEmbed)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed">
|
|
|
|
|
<div class="chatlog__embed-spotify-container">
|
|
|
|
|
<iframe class="chatlog__embed-spotify" src="@spotifyTrackEmbed.Url" width="400" height="80" allowtransparency="true" allow="encrypted-media"></iframe>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
// YouTube embed
|
|
|
|
|
else if (embed.TryGetYouTubeVideo() is { } youTubeVideoEmbed)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed">
|
|
|
|
|
@{/* Color pill */}
|
|
|
|
|
@if (embed.Color is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color?.R,@embed.Color?.G,@embed.Color?.B,@embed.Color?.A)"></div>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<div class="chatlog__embed">
|
|
|
|
|
@{/* 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>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
|
|
|
|
}
|
|
|
|
|
<div class="chatlog__embed-content-container">
|
|
|
|
|
<div class="chatlog__embed-content">
|
|
|
|
|
<div class="chatlog__embed-text">
|
|
|
|
|
@{/* Embed author */}
|
|
|
|
|
@if (embed.Author is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-author">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
|
|
|
|
|
{
|
|
|
|
|
<img class="chatlog__embed-author-icon" src="@await ResolveUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'">
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
|
|
|
|
{
|
|
|
|
|
<span class="chatlog__embed-author-name">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
|
|
|
|
{
|
|
|
|
|
<a class="chatlog__embed-author-name-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
@embed.Author.Name
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<div class="chatlog__embed-content-container">
|
|
|
|
|
<div class="chatlog__embed-content">
|
|
|
|
|
<div class="chatlog__embed-text">
|
|
|
|
|
@{/* Embed author */}
|
|
|
|
|
@if (embed.Author is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-author">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
|
|
|
|
|
{
|
|
|
|
|
<img class="chatlog__embed-author-icon" src="@await ResolveUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'">
|
|
|
|
|
}
|
|
|
|
|
@{/* Embed title */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-title">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
|
|
|
|
{
|
|
|
|
|
<a class="chatlog__embed-title-link" href="@embed.Url">
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
|
|
|
|
</a>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
|
|
|
|
{
|
|
|
|
|
<span class="chatlog__embed-author-name">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
|
|
|
|
{
|
|
|
|
|
<a class="chatlog__embed-author-name-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
@embed.Author.Name
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
@{/* Video player */}
|
|
|
|
|
<div class="chatlog__embed-youtube-container">
|
|
|
|
|
<iframe class="chatlog__embed-youtube" src="@youTubeVideoEmbed.Url" width="400" height="225"></iframe>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
// Generic embed
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed">
|
|
|
|
|
@{/* Color pill */}
|
|
|
|
|
@if (embed.Color is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color?.R,@embed.Color?.G,@embed.Color?.B,@embed.Color?.A)"></div>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed title */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-title">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
|
|
|
|
{
|
|
|
|
|
<a class="chatlog__embed-title-link" href="@embed.Url">
|
|
|
|
|
<div class="chatlog__embed-content-container">
|
|
|
|
|
<div class="chatlog__embed-content">
|
|
|
|
|
<div class="chatlog__embed-text">
|
|
|
|
|
@{/* Embed author */}
|
|
|
|
|
@if (embed.Author is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-author">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
|
|
|
|
|
{
|
|
|
|
|
<img class="chatlog__embed-author-icon" src="@await ResolveUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'">
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
|
|
|
|
{
|
|
|
|
|
<span class="chatlog__embed-author-name">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
|
|
|
|
{
|
|
|
|
|
<a class="chatlog__embed-author-name-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
@embed.Author.Name
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed title */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-title">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
|
|
|
|
{
|
|
|
|
|
<a class="chatlog__embed-title-link" href="@embed.Url">
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
|
|
|
|
</a>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
|
|
|
|
</a>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed description (with special casing for YouTube videos) */}
|
|
|
|
|
@if (youTubeVideo is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-youtube-container">
|
|
|
|
|
<iframe class="chatlog__embed-youtube" src="@youTubeVideo.Url" width="400" height="225"></iframe>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
else if (!string.IsNullOrWhiteSpace(embed.Description))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-description">
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Description))</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
@{/* Embed description */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Description))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-description">
|
|
|
|
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Description))</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed fields */}
|
|
|
|
|
@if (embed.Fields.Any())
|
|
|
|
|
@{/* Embed fields */}
|
|
|
|
|
@if (embed.Fields.Any())
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-fields">
|
|
|
|
|
@foreach (var field in embed.Fields)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-field @(field.IsInline ? "chatlog__embed-field--inline" : null)">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(field.Name))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-field-name">
|
|
|
|
|
<div class="markdown preserve-whitespace">@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>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@{/* Embed content */}
|
|
|
|
|
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-fields">
|
|
|
|
|
@foreach (var field in embed.Fields)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-field @(field.IsInline ? "chatlog__embed-field--inline" : null)">
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(field.Name))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-field-name">
|
|
|
|
|
<div class="markdown preserve-whitespace">@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>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
<div class="chatlog__embed-thumbnail-container">
|
|
|
|
|
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
|
|
|
|
|
<img class="chatlog__embed-thumbnail" src="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)" alt="Thumbnail" loading="lazy">
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@{/* Embed content (not shown for YouTube videos) */}
|
|
|
|
|
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url) && youTubeVideo is null)
|
|
|
|
|
@{/* Embed image */}
|
|
|
|
|
@if (embed.Image is not null && !string.IsNullOrWhiteSpace(embed.Image.Url))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-thumbnail-container">
|
|
|
|
|
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
|
|
|
|
|
<img class="chatlog__embed-thumbnail" src="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)" alt="Thumbnail" loading="lazy">
|
|
|
|
|
<div class="chatlog__embed-image-container">
|
|
|
|
|
<a class="chatlog__embed-image-link" href="@await ResolveUrlAsync(embed.Image.ProxyUrl ?? embed.Image.Url)">
|
|
|
|
|
<img class="chatlog__embed-image" src="@await ResolveUrlAsync(embed.Image.ProxyUrl ?? embed.Image.Url)" alt="Image" loading="lazy">
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@{/* Embed image */}
|
|
|
|
|
@if (embed.Image is not null && !string.IsNullOrWhiteSpace(embed.Image.Url))
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-image-container">
|
|
|
|
|
<a class="chatlog__embed-image-link" href="@await ResolveUrlAsync(embed.Image.ProxyUrl ?? embed.Image.Url)">
|
|
|
|
|
<img class="chatlog__embed-image" src="@await ResolveUrlAsync(embed.Image.ProxyUrl ?? embed.Image.Url)" alt="Image" loading="lazy">
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed footer & icon */}
|
|
|
|
|
@if (embed.Footer is not null || embed.Timestamp is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-footer">
|
|
|
|
|
@{/* Footer icon */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Footer?.IconUrl))
|
|
|
|
|
{
|
|
|
|
|
<img class="chatlog__embed-footer-icon" src="@await ResolveUrlAsync(embed.Footer.IconProxyUrl ?? embed.Footer.IconUrl)" alt="Footer icon" loading="lazy">
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<span class="chatlog__embed-footer-text">
|
|
|
|
|
@{/* Footer text */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
|
|
|
|
|
@{/* Embed footer & icon */}
|
|
|
|
|
@if (embed.Footer is not null || embed.Timestamp is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="chatlog__embed-footer">
|
|
|
|
|
@{/* Footer icon */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Footer?.IconUrl))
|
|
|
|
|
{
|
|
|
|
|
@embed.Footer.Text
|
|
|
|
|
<img class="chatlog__embed-footer-icon" src="@await ResolveUrlAsync(embed.Footer.IconProxyUrl ?? embed.Footer.IconUrl)" alt="Footer icon" loading="lazy">
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text) && embed.Timestamp is not null)
|
|
|
|
|
{
|
|
|
|
|
@(" • ")
|
|
|
|
|
}
|
|
|
|
|
<span class="chatlog__embed-footer-text">
|
|
|
|
|
@{/* Footer text */}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
|
|
|
|
|
{
|
|
|
|
|
@embed.Footer.Text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed timestamp */}
|
|
|
|
|
@if (embed.Timestamp is not null)
|
|
|
|
|
{
|
|
|
|
|
@FormatDate(embed.Timestamp.Value)
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
@if (!string.IsNullOrWhiteSpace(embed.Footer?.Text) && embed.Timestamp is not null)
|
|
|
|
|
{
|
|
|
|
|
@(" • ")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Embed timestamp */}
|
|
|
|
|
@if (embed.Timestamp is not null)
|
|
|
|
|
{
|
|
|
|
|
@FormatDate(embed.Timestamp.Value)
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@{/* Message reactions */}
|
|
|
|
|