Compare commits
10 commits
dev
...
wip/user-e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cfe269f235 | ||
![]() |
509d001a35 | ||
![]() |
5fd1aa72f1 | ||
![]() |
e09f2bac1a | ||
![]() |
a92f01e7e1 | ||
![]() |
3509d94309 | ||
![]() |
cfcccf7654 | ||
![]() |
53c3fc9edc | ||
![]() |
6293c138a4 | ||
![]() |
72c68d2d5a |
20 changed files with 136 additions and 60 deletions
|
@ -1,7 +1,6 @@
|
|||
using Iceshrimp.Backend.Core.Configuration;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Shared.Schemas.Web;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
@ -10,16 +9,17 @@ namespace Iceshrimp.Backend.Controllers.Web.Renderers;
|
|||
|
||||
public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseContext db)
|
||||
{
|
||||
public async Task<UserResponse> RenderOne(User user, UserRendererDto? data = null)
|
||||
private UserResponse Render(User user, UserRendererDto data)
|
||||
{
|
||||
var instance = user.IsLocalUser
|
||||
? null
|
||||
: (data?.InstanceData ?? await GetInstanceData([user])).FirstOrDefault(p => p.Host == user.Host);
|
||||
var instance = user.IsRemoteUser ? data.InstanceData.FirstOrDefault(p => p.Host == user.Host) : null;
|
||||
|
||||
//TODO: populate the below two lines for local users
|
||||
var instanceName = user.IsLocalUser ? config.Value.AccountDomain : instance?.Name;
|
||||
var instanceIcon = user.IsLocalUser ? null : instance?.FaviconUrl;
|
||||
|
||||
if (!data.Emojis.TryGetValue(user.Id, out var emoji))
|
||||
throw new Exception("DTO didn't contain emoji for user");
|
||||
|
||||
return new UserResponse
|
||||
{
|
||||
Id = user.Id,
|
||||
|
@ -30,10 +30,20 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
|
|||
BannerUrl = user.BannerUrl,
|
||||
InstanceName = instanceName,
|
||||
InstanceIconUrl = instanceIcon,
|
||||
Emojis = emoji,
|
||||
MovedTo = user.MovedToUri
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<UserResponse> RenderOne(User user)
|
||||
{
|
||||
var instanceData = await GetInstanceData([user]);
|
||||
var emojis = await GetEmojis([user]);
|
||||
var data = new UserRendererDto { Emojis = emojis, InstanceData = instanceData };
|
||||
|
||||
return Render(user, data);
|
||||
}
|
||||
|
||||
private async Task<List<Instance>> GetInstanceData(IEnumerable<User> users)
|
||||
{
|
||||
var hosts = users.Select(p => p.Host).Where(p => p != null).Distinct().Cast<string>();
|
||||
|
@ -43,12 +53,40 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
|
|||
public async Task<IEnumerable<UserResponse>> RenderMany(IEnumerable<User> users)
|
||||
{
|
||||
var userList = users.ToList();
|
||||
var data = new UserRendererDto { InstanceData = await GetInstanceData(userList) };
|
||||
return await userList.Select(p => RenderOne(p, data)).AwaitAllAsync();
|
||||
var data = new UserRendererDto
|
||||
{
|
||||
InstanceData = await GetInstanceData(userList), Emojis = await GetEmojis(userList)
|
||||
};
|
||||
|
||||
return userList.Select(p => Render(p, data));
|
||||
}
|
||||
|
||||
public class UserRendererDto
|
||||
private async Task<Dictionary<string, List<EmojiResponse>>> GetEmojis(ICollection<User> users)
|
||||
{
|
||||
public List<Instance>? InstanceData;
|
||||
var ids = users.SelectMany(p => p.Emojis).ToList();
|
||||
if (ids.Count == 0) return users.ToDictionary<User, string, List<EmojiResponse>>(p => p.Id, _ => []);
|
||||
|
||||
var emoji = await db.Emojis
|
||||
.Where(p => ids.Contains(p.Id))
|
||||
.Select(p => new EmojiResponse
|
||||
{
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
Uri = p.Uri,
|
||||
Aliases = p.Aliases,
|
||||
Category = p.Category,
|
||||
PublicUrl = p.PublicUrl,
|
||||
License = p.License,
|
||||
Sensitive = p.Sensitive
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return users.ToDictionary(p => p.Id, p => emoji.Where(e => p.Emojis.Contains(e.Id)).ToList());
|
||||
}
|
||||
|
||||
private class UserRendererDto
|
||||
{
|
||||
public required List<Instance> InstanceData;
|
||||
public required Dictionary<string, List<EmojiResponse>> Emojis;
|
||||
}
|
||||
}
|
|
@ -5,13 +5,14 @@
|
|||
@code {
|
||||
[Parameter] [EditorRequired] public required string? Text { get; set; }
|
||||
[Parameter] public List<EmojiResponse> Emoji { get; set; } = [];
|
||||
[Parameter] public bool Simple { get; set; } = false;
|
||||
private MarkupString TextBody { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Text != null)
|
||||
{
|
||||
TextBody = await MfmRenderer.RenderString(Text, Emoji);
|
||||
TextBody = await MfmRenderer.RenderString(Text, Emoji, Simple);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +20,7 @@
|
|||
{
|
||||
if (Text != null)
|
||||
{
|
||||
TextBody = await MfmRenderer.RenderString(Text, Emoji);
|
||||
TextBody = await MfmRenderer.RenderString(Text, Emoji, Simple);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,6 +46,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
::deep {
|
||||
.emoji.simple {
|
||||
> img {
|
||||
height: 1.25em;
|
||||
vertical-align: -0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::deep {
|
||||
.emoji.simple {
|
||||
> img:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::deep {
|
||||
.quote-node {
|
||||
/*Copying the appearance of -js*/
|
||||
|
|
|
@ -7,14 +7,7 @@
|
|||
<div class="renote-info">
|
||||
<span class="user">
|
||||
<Icon Name="Icons.Repeat"/> Renoted by
|
||||
@if (NoteResponse.User.DisplayName != null)
|
||||
{
|
||||
@NoteResponse.User.DisplayName
|
||||
}
|
||||
else
|
||||
{
|
||||
@NoteResponse.User.Username
|
||||
}
|
||||
<UserDisplayName User="@NoteResponse.User"/>
|
||||
</span>
|
||||
<span class="metadata">
|
||||
<NoteMetadata Visibility="NoteResponse.Visibility" InstanceName="@null" CreatedAt="DateTime.Parse(NoteResponse.CreatedAt)"/>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
@if (NoteBase.Cw != null)
|
||||
{
|
||||
<div class="cw">
|
||||
<span class="cw-field">@NoteBase.Cw</span><button class="cw-button" @onclick="ToggleCw" @onclick:stopPropagation="true">Toggle CW</button>
|
||||
<span class="cw-field"><MfmText Text="@NoteBase.Cw" Emoji="@NoteBase.Emoji" Simple="@true"/></span><button class="cw-button" @onclick="ToggleCw" @onclick:stopPropagation="true">Toggle CW</button>
|
||||
</div>
|
||||
<div hidden="@_cwToggle" class="note-body @(_cwToggle ? "hidden" : "") @(Indented ? "indent" : "")" @ref="Body">
|
||||
<span>
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
@inject ComposeService ComposeService
|
||||
@inject SessionService Session;
|
||||
<div class="note-header">
|
||||
<NoteUserInfo
|
||||
AvatarUrl="@Note.User.AvatarUrl"
|
||||
DisplayName="@Note.User.DisplayName"
|
||||
Username="@Note.User.Username"
|
||||
Host="@Note.User.Host"
|
||||
Indented="Indented"/>
|
||||
<NoteUserInfo User="@Note.User" Indented="Indented"/>
|
||||
<NoteMetadata
|
||||
Visibility="@Note.Visibility"
|
||||
InstanceName="@Note.User.InstanceName"
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
@using Iceshrimp.Shared.Schemas.Web
|
||||
@inject NavigationManager Nav
|
||||
|
||||
@if (Indented == false)
|
||||
{
|
||||
<img @onclick="OpenProfile" class="user-avatar" src="@AvatarUrl" alt="@(DisplayName ?? Username)" role="link"/>
|
||||
<img @onclick="OpenProfile" class="user-avatar" src="@(User.AvatarUrl ?? $"/identicon/{User.Id}")" alt="@(User.DisplayName ?? User.Username)" role="link"/>
|
||||
}
|
||||
<div class="name-section">
|
||||
<span class="display-name">@(DisplayName ?? Username)</span>
|
||||
<span class="identifier">@@@Username@(Host != null ? $"@{Host}" : "")</span>
|
||||
<span class="display-name"><UserDisplayName User="@User"/></span>
|
||||
<span class="identifier">@@@User.Username@(User.Host != null ? $"@{User.Host}" : "")</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] [EditorRequired] public required string AvatarUrl { get; set; }
|
||||
[Parameter] [EditorRequired] public required string? DisplayName { get; set; }
|
||||
[Parameter] [EditorRequired] public required string Username { get; set; }
|
||||
[Parameter] [EditorRequired] public required bool Indented { get; set; }
|
||||
[Parameter] [EditorRequired] public required string? Host { get; set; }
|
||||
[Parameter] [EditorRequired] public required UserResponse User { get; set; }
|
||||
[Parameter] [EditorRequired] public required bool Indented { get; set; }
|
||||
|
||||
private void OpenProfile()
|
||||
{
|
||||
var path = $"@{Username}";
|
||||
if (Host?.Length > 0)
|
||||
var path = $"@{User.Username}";
|
||||
if (User.Host?.Length > 0)
|
||||
{
|
||||
path += $"@{Host}";
|
||||
path += $"@{User.Host}";
|
||||
}
|
||||
|
||||
Nav.NavigateTo($"/{path}");
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div @onclick="() => OpenProfile(el.Username, el.Host)" class="detail-entry">
|
||||
<img class="icon" src="@el.AvatarUrl"/>
|
||||
<div class="name-section">
|
||||
<div class="displayname">@(el.DisplayName ?? el.Username)</div>
|
||||
<div class="displayname"><UserDisplayName User="@el"/></div>
|
||||
<div class="username">@@@el.Username@(el.Host != null ? $"@{el.Host}" : "")</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div @onclick="() => OpenProfile(el.Username, el.Host)" class="detail-entry">
|
||||
<img class="icon" src="@el.AvatarUrl"/>
|
||||
<div class="name-section">
|
||||
<div class="displayname">@(el.DisplayName ?? el.Username)</div>
|
||||
<div class="displayname"><UserDisplayName User="@el"/></div>
|
||||
<div class="username">@@@el.Username@(el.Host != null ? $"@{el.Host}" : "")</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<div class="notification-body">
|
||||
@if (NotificationResponse is { User: not null })
|
||||
{
|
||||
<span @onclick="OpenProfile" class="display-name">@(NotificationResponse.User.DisplayName ?? NotificationResponse.User.Username)</span>
|
||||
<span @onclick="OpenProfile" class="display-name"><UserDisplayName User="@NotificationResponse.User"/></span>
|
||||
}
|
||||
|
||||
@if (NotificationResponse is { Note: not null, Type: "like", User: not null })
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
@if (UserProfile.Bio != null)
|
||||
{
|
||||
<div class="bio">
|
||||
<MfmText Text="@UserProfile.Bio"/>
|
||||
<MfmText Text="@UserProfile.Bio" Emoji="@Emojis"/>
|
||||
</div>
|
||||
}
|
||||
<div class="data">
|
||||
|
@ -37,7 +37,7 @@
|
|||
<div class="fields">
|
||||
@foreach (var field in UserProfile.Fields)
|
||||
{
|
||||
<ProfileInfoField Field="field"/>
|
||||
<ProfileInfoField Field="field" Emojis="@Emojis"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -63,4 +63,5 @@
|
|||
|
||||
@code {
|
||||
[Parameter] [EditorRequired] public required UserProfileResponse UserProfile { get; set; }
|
||||
[Parameter] [EditorRequired] public required List<EmojiResponse> Emojis { get; set; }
|
||||
}
|
|
@ -6,13 +6,14 @@
|
|||
{
|
||||
<Icon class="verified" Name="Icons.SealCheck" Size="1.3em"/>
|
||||
}
|
||||
@Field.Name
|
||||
<MfmText Text="@Field.Name.Split('\n')[0]" Emoji="@Emojis" Simple="@true"></MfmText>
|
||||
</span>
|
||||
<span class="field-value">
|
||||
<MfmText Text="@Field.Value"></MfmText>
|
||||
<MfmText Text="@Field.Value.Split('\n')[0]" Emoji="@Emojis" Simple="@true"></MfmText>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] [EditorRequired] public required UserProfileField Field { get; set; }
|
||||
[Parameter] [EditorRequired] public required UserProfileField Field { get; set; }
|
||||
[Parameter] [EditorRequired] public required List<EmojiResponse> Emojis { get; set; }
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
<div @onclick="() => OpenProfile(el.Username, el.Host)" class="detail-entry">
|
||||
<img class="icon" src="@el.AvatarUrl"/>
|
||||
<div class="name-section">
|
||||
<div class="displayname">@(el.DisplayName ?? el.Username)</div>
|
||||
<div class="displayname"><UserDisplayName User="@el"/></div>
|
||||
<div class="username">@@@el.Username@(el.Host != null ? $"@{el.Host}" : "")</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
13
Iceshrimp.Frontend/Components/UserDisplayName.razor
Normal file
13
Iceshrimp.Frontend/Components/UserDisplayName.razor
Normal file
|
@ -0,0 +1,13 @@
|
|||
@using Iceshrimp.Shared.Schemas.Web
|
||||
@if (User.DisplayName != null && User.Emojis.Count != 0)
|
||||
{
|
||||
<MfmText Text="@User.DisplayName.Split('\n')[0]" Emoji="@User.Emojis" Simple="@true"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
@(User.DisplayName ?? User.Username)
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] [EditorRequired] public required UserResponse User { get; set; }
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
</div>
|
||||
<div class="name-section">
|
||||
<div class="name">
|
||||
@(User.DisplayName ?? User.Username)
|
||||
<UserDisplayName User="@User"/>
|
||||
</div>
|
||||
<div class="identifier">
|
||||
@@@User.Username
|
||||
|
@ -49,7 +49,7 @@
|
|||
@if (UserProfile.Bio != null)
|
||||
{
|
||||
<div class="bio">
|
||||
<MfmText Text="@UserProfile.Bio"/>
|
||||
<MfmText Text="@UserProfile.Bio" Emoji="@User.Emojis"/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,18 @@ namespace Iceshrimp.Frontend.Core.Miscellaneous;
|
|||
|
||||
public static class MfmRenderer
|
||||
{
|
||||
public static async Task<MarkupString> RenderString(string text, List<EmojiResponse> emoji)
|
||||
public static async Task<MarkupString> RenderString(string text, List<EmojiResponse> emoji, bool simple = false)
|
||||
{
|
||||
var res = Mfm.parse(text);
|
||||
var res = simple ? Mfm.parseSimple(text) : Mfm.parse(text);
|
||||
var context = BrowsingContext.New();
|
||||
var document = await context.OpenNewAsync();
|
||||
var renderedMfm = RenderMultipleNodes(res, document, emoji);
|
||||
var renderedMfm = RenderMultipleNodes(res, document, emoji, simple);
|
||||
var html = renderedMfm.ToHtml();
|
||||
return new MarkupString(html);
|
||||
}
|
||||
|
||||
private static INode RenderMultipleNodes(
|
||||
IEnumerable<MfmNodeTypes.MfmNode> nodes, IDocument document, List<EmojiResponse> emoji
|
||||
IEnumerable<MfmNodeTypes.MfmNode> nodes, IDocument document, List<EmojiResponse> emoji, bool simple
|
||||
)
|
||||
{
|
||||
var el = document.CreateElement("span");
|
||||
|
@ -29,7 +29,7 @@ public static class MfmRenderer
|
|||
{
|
||||
try
|
||||
{
|
||||
el.AppendNodes(RenderNode(node, document, emoji));
|
||||
el.AppendNodes(RenderNode(node, document, emoji, simple));
|
||||
}
|
||||
catch (NotImplementedException e)
|
||||
{
|
||||
|
@ -42,7 +42,7 @@ public static class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode RenderNode(MfmNodeTypes.MfmNode node, IDocument document, List<EmojiResponse> emoji)
|
||||
private static INode RenderNode(MfmNodeTypes.MfmNode node, IDocument document, List<EmojiResponse> emoji, bool simple)
|
||||
{
|
||||
// Hard wrap makes this impossible to read
|
||||
// @formatter:off
|
||||
|
@ -55,7 +55,7 @@ public static class MfmRenderer
|
|||
MfmNodeTypes.MfmSearchNode mfmSearchNode => throw new NotImplementedException($"{mfmSearchNode.GetType()}"),
|
||||
MfmNodeTypes.MfmBlockNode mfmBlockNode => throw new NotImplementedException($"{mfmBlockNode.GetType()}"),
|
||||
MfmNodeTypes.MfmBoldNode mfmBoldNode => MfmBoldNode(mfmBoldNode, document),
|
||||
MfmNodeTypes.MfmEmojiCodeNode mfmEmojiCodeNode => MfmEmojiCodeNode(mfmEmojiCodeNode, document, emoji),
|
||||
MfmNodeTypes.MfmEmojiCodeNode mfmEmojiCodeNode => MfmEmojiCodeNode(mfmEmojiCodeNode, document, emoji, simple),
|
||||
MfmNodeTypes.MfmFnNode mfmFnNode => throw new NotImplementedException($"{mfmFnNode.GetType()}"),
|
||||
MfmNodeTypes.MfmHashtagNode mfmHashtagNode => MfmHashtagNode(mfmHashtagNode, document),
|
||||
MfmNodeTypes.MfmInlineCodeNode mfmInlineCodeNode => MfmInlineCodeNode(mfmInlineCodeNode, document),
|
||||
|
@ -79,7 +79,7 @@ public static class MfmRenderer
|
|||
{
|
||||
try
|
||||
{
|
||||
rendered.AppendNodes(RenderNode(childNode, document, emoji));
|
||||
rendered.AppendNodes(RenderNode(childNode, document, emoji, simple));
|
||||
}
|
||||
catch (NotImplementedException e)
|
||||
{
|
||||
|
@ -157,11 +157,11 @@ public static class MfmRenderer
|
|||
}
|
||||
|
||||
private static INode MfmEmojiCodeNode(
|
||||
MfmNodeTypes.MfmEmojiCodeNode node, IDocument document, List<EmojiResponse> emojiList
|
||||
MfmNodeTypes.MfmEmojiCodeNode node, IDocument document, List<EmojiResponse> emojiList, bool simple
|
||||
)
|
||||
{
|
||||
var el = document.CreateElement("span");
|
||||
el.ClassName = "emoji";
|
||||
el.ClassName = simple ? "emoji simple" : "emoji";
|
||||
|
||||
var emoji = emojiList.Find(p => p.Name == node.Name);
|
||||
if (emoji is null)
|
||||
|
@ -173,6 +173,7 @@ public static class MfmRenderer
|
|||
var image = document.CreateElement("img");
|
||||
image.SetAttribute("src", emoji.PublicUrl);
|
||||
image.SetAttribute("alt", node.Name);
|
||||
image.SetAttribute("title", $":{emoji.Name}:");
|
||||
el.AppendChild(image);
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
Token = res.Token!,
|
||||
Host = res.User.Host,
|
||||
IsAdmin = res.IsAdmin ?? false,
|
||||
Emojis = res.User.Emojis,
|
||||
MovedTo = res.User.MovedTo
|
||||
});
|
||||
SessionService.SetSession(res.User.Id);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
<div class="name-section">
|
||||
<div class="name">
|
||||
@(UserResponse.DisplayName ?? UserResponse.Username)
|
||||
<UserDisplayName User="@UserResponse"/>
|
||||
</div>
|
||||
<div class="identifier">
|
||||
@@@UserResponse.Username
|
||||
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
<FollowButton User="UserResponse" UserProfile="Profile"/>
|
||||
</div>
|
||||
<ProfileInfo UserProfile="Profile"/>
|
||||
<ProfileInfo Emojis="@UserResponse.Emojis" UserProfile="Profile"/>
|
||||
</div>
|
||||
@if (UserNotes.Count > 0)
|
||||
{
|
||||
|
|
|
@ -188,6 +188,7 @@ module private MfmParser =
|
|||
// References
|
||||
let node, nodeRef = createParserForwardedToRef ()
|
||||
let inlineNode, inlineNodeRef = createParserForwardedToRef ()
|
||||
let simple, simpleRef = createParserForwardedToRef ()
|
||||
|
||||
let seqFlatten items =
|
||||
seq {
|
||||
|
@ -375,6 +376,11 @@ module private MfmParser =
|
|||
fnNode
|
||||
charNode ]
|
||||
|
||||
let simpleNodeSeq =
|
||||
[ plainNode
|
||||
emojiCodeNode
|
||||
charNode ]
|
||||
|
||||
//TODO: still missing: FnNode
|
||||
|
||||
let blockNodeSeq =
|
||||
|
@ -387,9 +393,13 @@ module private MfmParser =
|
|||
|
||||
do inlineNodeRef.Value <- choice <| (seqAttempt inlineNodeSeq) |>> fun v -> v :?> MfmInlineNode
|
||||
|
||||
do simpleRef.Value <- choice <| seqAttempt simpleNodeSeq
|
||||
|
||||
// Final parse command
|
||||
let parse = spaces >>. manyTill node eof .>> spaces
|
||||
|
||||
let parseSimple = spaces >>. manyTill simple eof .>> spaces
|
||||
|
||||
open MfmParser
|
||||
|
||||
module Mfm =
|
||||
|
@ -397,3 +407,8 @@ module Mfm =
|
|||
match runParserOnString parse 0 "" str with
|
||||
| Success(result, _, _) -> aggregateText result
|
||||
| Failure(s, _, _) -> failwith $"Failed to parse MFM: {s}"
|
||||
|
||||
let parseSimple str =
|
||||
match runParserOnString parseSimple 0 "" str with
|
||||
| Success(result, _, _) -> aggregateText result
|
||||
| Failure(s, _, _) -> failwith $"Failed to parse MFM: {s}"
|
||||
|
|
|
@ -14,5 +14,7 @@ public class UserResponse
|
|||
public required string? InstanceName { get; set; }
|
||||
public required string? InstanceIconUrl { get; set; }
|
||||
|
||||
public List<EmojiResponse> Emojis { get; set; } = [];
|
||||
|
||||
[JI(Condition = WhenWritingNull)] public string? MovedTo { get; set; }
|
||||
}
|
Loading…
Add table
Reference in a new issue