Files
decePubClient/Helpers/CustomEmojis.cs
T

47 lines
2.0 KiB
C#
Raw Normal View History

using System.Net;
using System.Text.RegularExpressions;
using decePubClient.Models.Mastodon;
using Microsoft.AspNetCore.Components;
namespace decePubClient.Helpers
{
/// <summary>
/// A post's or an account's custom emoji (`:shortcode:` among its <c>emojis</c>) shown as their pictures. The shortcode
/// stays as the picture's alt text, so it is what a screen reader reads and what is copied. Only shortcodes the
/// server listed for that post or account are replaced, and only with an http(s) picture.
/// </summary>
public static partial class CustomEmojis
{
[GeneratedRegex(@":([A-Za-z0-9_+-]{1,64}):")]
private static partial Regex Shortcode();
[GeneratedRegex(@"(<[^>]*>)")]
private static partial Regex Tag();
/// <summary>Plain text (a name, a content warning): escaped, then its shortcodes as pictures.</summary>
public static MarkupString Text(string text, IReadOnlyCollection<CustomEmoji> emojis) =>
new(Replace(WebUtility.HtmlEncode(text ?? string.Empty), emojis));
/// <summary>HTML the server sanitized: the shortcodes in its text, never inside a tag.</summary>
public static string Html(string html, IReadOnlyCollection<CustomEmoji> emojis) =>
emojis.Count == 0 || string.IsNullOrEmpty(html)
? html
: string.Concat(Tag().Split(html).Select(part => part.StartsWith('<') ? part : Replace(part, emojis)));
static string Replace(string text, IReadOnlyCollection<CustomEmoji> emojis)
{
if (emojis.Count == 0 || !text.Contains(':'))
return text;
var known = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var emoji in emojis)
if (!string.IsNullOrEmpty(emoji.Shortcode) && Uri.TryCreate(emoji.Url, UriKind.Absolute, out var url)
&& url.Scheme is "https" or "http")
known.TryAdd(emoji.Shortcode, url.AbsoluteUri);
return Shortcode().Replace(text, match => known.TryGetValue(match.Groups[1].Value, out var url)
? $"<img class=\"custom-emoji\" src=\"{WebUtility.HtmlEncode(url)}\" alt=\"{match.Value}\" title=\"{match.Value}\" draggable=\"false\" />"
: match.Value);
}
}
}