Compare commits
No commits in common. "dev" and "blazor" have entirely different histories.
36 changed files with 394 additions and 682 deletions
|
@ -2,11 +2,6 @@
|
|||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
|
@ -14,7 +14,7 @@ public readonly record struct MfmRenderData(MarkupString Html, List<MfmInlineMed
|
|||
[UsedImplicitly]
|
||||
public class MfmRenderer(MfmConverter converter, FlagService flags) : ISingletonService
|
||||
{
|
||||
public MfmRenderData? Render(
|
||||
public async Task<MfmRenderData?> RenderAsync(
|
||||
string? text, string? host, List<Note.MentionedUser> mentions, List<Emoji> emoji, string rootElement,
|
||||
List<PreviewAttachment>? media = null
|
||||
)
|
||||
|
@ -27,14 +27,14 @@ public class MfmRenderer(MfmConverter converter, FlagService flags) : ISingleton
|
|||
flags.SupportsInlineMedia.Value = true;
|
||||
|
||||
var mfmInlineMedia = media?.Select(m => new MfmInlineMedia(MfmInlineMedia.GetType(m.MimeType), m.Url, m.Alt)).ToList();
|
||||
var serialized = converter.ToHtml(parsed, mentions, host, emoji: emoji, rootElement: rootElement, media: mfmInlineMedia);
|
||||
var serialized = await converter.ToHtmlAsync(parsed, mentions, host, emoji: emoji, rootElement: rootElement, media: mfmInlineMedia);
|
||||
|
||||
return new MfmRenderData(new MarkupString(serialized.Html), serialized.InlineMedia);
|
||||
}
|
||||
|
||||
public MarkupString? RenderSimple(string? text, string? host, List<Note.MentionedUser> mentions, List<Emoji> emoji, string rootElement)
|
||||
public async Task<MarkupString?> RenderSimpleAsync(string? text, string? host, List<Note.MentionedUser> mentions, List<Emoji> emoji, string rootElement)
|
||||
{
|
||||
var rendered = Render(text, host, mentions, emoji, rootElement);
|
||||
var rendered = await RenderAsync(text, host, mentions, emoji, rootElement);
|
||||
return rendered?.Html;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,31 +30,31 @@ public class NoteRenderer(
|
|||
var attachments = await GetAttachmentsAsync(allNotes);
|
||||
var polls = await GetPollsAsync(allNotes);
|
||||
|
||||
return Render(note, users, mentions, emoji, attachments, polls);
|
||||
return await RenderAsync(note, users, mentions, emoji, attachments, polls);
|
||||
}
|
||||
|
||||
private PreviewNote Render(
|
||||
private async Task<PreviewNote> RenderAsync(
|
||||
Note note, List<PreviewUser> users, Dictionary<string, List<Note.MentionedUser>> mentions,
|
||||
Dictionary<string, List<Emoji>> emoji, Dictionary<string, List<PreviewAttachment>?> attachments,
|
||||
Dictionary<string, PreviewPoll> polls
|
||||
)
|
||||
{
|
||||
var renderedText = mfm.Render(note.Text, note.User.Host, mentions[note.Id], emoji[note.Id], "span", attachments[note.Id]);
|
||||
var renderedText = await mfm.RenderAsync(note.Text, note.User.Host, mentions[note.Id], emoji[note.Id], "span", attachments[note.Id]);
|
||||
var inlineMediaUrls = renderedText?.InlineMedia.Select(m => m.Src).ToArray() ?? [];
|
||||
|
||||
var res = new PreviewNote
|
||||
{
|
||||
User = users.First(p => p.Id == note.User.Id),
|
||||
Text = renderedText?.Html,
|
||||
Cw = note.Cw,
|
||||
RawText = note.Text,
|
||||
Uri = note.Uri ?? note.GetPublicUri(instance.Value),
|
||||
QuoteUrl = note.Renote?.Url ?? note.Renote?.Uri ?? note.Renote?.GetPublicUriOrNull(instance.Value),
|
||||
User = users.First(p => p.Id == note.User.Id),
|
||||
Text = renderedText?.Html,
|
||||
Cw = note.Cw,
|
||||
RawText = note.Text,
|
||||
Uri = note.Uri ?? note.GetPublicUri(instance.Value),
|
||||
QuoteUrl = note.Renote?.Url ?? note.Renote?.Uri ?? note.Renote?.GetPublicUriOrNull(instance.Value),
|
||||
QuoteInaccessible = note.Renote?.VisibilityIsPublicOrHome == false,
|
||||
Attachments = attachments[note.Id]?.Where(p => !inlineMediaUrls.Contains(p.Url)).ToList(),
|
||||
Poll = polls.GetValueOrDefault(note.Id),
|
||||
CreatedAt = note.CreatedAt.ToDisplayStringTz(),
|
||||
UpdatedAt = note.UpdatedAt?.ToDisplayStringTz()
|
||||
Attachments = attachments[note.Id]?.Where(p => !inlineMediaUrls.Contains(p.Url)).ToList(),
|
||||
Poll = polls.GetValueOrDefault(note.Id),
|
||||
CreatedAt = note.CreatedAt.ToDisplayStringTz(),
|
||||
UpdatedAt = note.UpdatedAt?.ToDisplayStringTz()
|
||||
};
|
||||
|
||||
return res;
|
||||
|
@ -143,6 +143,8 @@ public class NoteRenderer(
|
|||
var emoji = await GetEmojiAsync(allNotes);
|
||||
var attachments = await GetAttachmentsAsync(allNotes);
|
||||
var polls = await GetPollsAsync(allNotes);
|
||||
return notes.Select(p => Render(p, users, mentions, emoji, attachments, polls)).ToList();
|
||||
return await notes.Select(p => RenderAsync(p, users, mentions, emoji, attachments, polls))
|
||||
.AwaitAllAsync()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ public class UserRenderer(
|
|||
{
|
||||
if (user == null) return null;
|
||||
var emoji = await GetEmojiAsync([user]);
|
||||
return Render(user, emoji);
|
||||
return await RenderAsync(user, emoji);
|
||||
}
|
||||
|
||||
private PreviewUser Render(User user, Dictionary<string, List<Emoji>> emoji)
|
||||
private async Task<PreviewUser> RenderAsync(User user, Dictionary<string, List<Emoji>> emoji)
|
||||
{
|
||||
var mentions = user.UserProfile?.Mentions ?? [];
|
||||
|
||||
|
@ -37,8 +37,8 @@ public class UserRenderer(
|
|||
AvatarUrl = user.GetAvatarUrl(instance.Value),
|
||||
BannerUrl = user.GetBannerUrl(instance.Value),
|
||||
RawDisplayName = user.DisplayName,
|
||||
DisplayName = mfm.RenderSimple(user.DisplayName, user.Host, mentions, emoji[user.Id], "span"),
|
||||
Bio = mfm.RenderSimple(user.UserProfile?.Description, user.Host, mentions, emoji[user.Id], "span"),
|
||||
DisplayName = await mfm.RenderSimpleAsync(user.DisplayName, user.Host, mentions, emoji[user.Id], "span"),
|
||||
Bio = await mfm.RenderSimpleAsync(user.UserProfile?.Description, user.Host, mentions, emoji[user.Id], "span"),
|
||||
MovedToUri = user.MovedToUri
|
||||
};
|
||||
// @formatter:on
|
||||
|
@ -64,6 +64,6 @@ public class UserRenderer(
|
|||
public async Task<List<PreviewUser>> RenderManyAsync(List<User> users)
|
||||
{
|
||||
var emoji = await GetEmojiAsync(users);
|
||||
return users.Select(p => Render(p, emoji)).ToList();
|
||||
return await users.Select(p => RenderAsync(p, emoji)).AwaitAllAsync().ToListAsync();
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ public class AnnouncementController(DatabaseContext db, MfmConverter mfmConverte
|
|||
})
|
||||
.ToListAsync();
|
||||
|
||||
res.ForEach(p => p.Content = mfmConverter.ToHtml(p.Content, [], null).Html);
|
||||
await res.Select(async p => p.Content = (await mfmConverter.ToHtmlAsync(p.Content, [], null)).Html).AwaitAllAsync();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System.Net;
|
||||
using System.Net.Mime;
|
||||
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
||||
using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
using Iceshrimp.Backend.Controllers.Shared.Attributes;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
|
@ -9,7 +8,6 @@ using Iceshrimp.Backend.Core.Extensions;
|
|||
using Iceshrimp.Backend.Core.Helpers;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
|
@ -159,44 +157,4 @@ public class AuthController(DatabaseContext db, MetaService meta) : ControllerBa
|
|||
|
||||
return new object();
|
||||
}
|
||||
|
||||
[Authenticate]
|
||||
[HttpGet("/api/oauth_tokens.json")]
|
||||
[ProducesResults(HttpStatusCode.OK)]
|
||||
public async Task<List<PleromaOauthTokenEntity>> GetOauthTokens()
|
||||
{
|
||||
var user = HttpContext.GetUserOrFail();
|
||||
var oauthTokens = await db.OauthTokens
|
||||
.Where(p => p.User == user)
|
||||
.Include(oauthToken => oauthToken.App)
|
||||
.ToListAsync();
|
||||
|
||||
List<PleromaOauthTokenEntity> result = [];
|
||||
foreach (var token in oauthTokens)
|
||||
{
|
||||
result.Add(new PleromaOauthTokenEntity()
|
||||
{
|
||||
Id = token.Id,
|
||||
AppName = token.App.Name,
|
||||
ValidUntil = token.CreatedAt + TimeSpan.FromDays(365 * 100)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Authenticate]
|
||||
[HttpDelete("/api/oauth_tokens/{id}")]
|
||||
[ProducesResults(HttpStatusCode.Created)]
|
||||
[ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.Forbidden)]
|
||||
public async Task RevokeOauthTokenPleroma(string id)
|
||||
{
|
||||
var token = await db.OauthTokens.FirstOrDefaultAsync(p => p.Id == id) ??
|
||||
throw GracefulException.Forbidden("You are not authorized to revoke this token");
|
||||
|
||||
db.Remove(token);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Response.StatusCode = 201;
|
||||
}
|
||||
}
|
|
@ -153,8 +153,8 @@ public class NoteRenderer(
|
|||
{
|
||||
if (text != null || quoteUri != null || quoteInaccessible || replyInaccessible)
|
||||
{
|
||||
(content, inlineMedia) = mfmConverter.ToHtml(text ?? "", mentionedUsers, note.UserHost, quoteUri,
|
||||
quoteInaccessible, replyInaccessible, media: inlineMedia);
|
||||
(content, inlineMedia) = await mfmConverter.ToHtmlAsync(text ?? "", mentionedUsers, note.UserHost, quoteUri,
|
||||
quoteInaccessible, replyInaccessible, media: inlineMedia);
|
||||
|
||||
attachments.RemoveAll(attachment => inlineMedia.Any(inline => inline.Src == (attachment.RemoteUrl ?? attachment.Url)));
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ public class NoteRenderer(
|
|||
_ => MfmInlineMedia.MediaType.Other
|
||||
}, p.RemoteUrl ?? p.Url, p.Description)).ToList();
|
||||
|
||||
(var content, inlineMedia) = mfmConverter.ToHtml(edit.Text ?? "", mentionedUsers, note.UserHost, media: inlineMedia);
|
||||
(var content, inlineMedia) = await mfmConverter.ToHtmlAsync(edit.Text ?? "", mentionedUsers, note.UserHost, media: inlineMedia);
|
||||
files.RemoveAll(attachment => inlineMedia.Any(inline => inline.Src == (attachment.RemoteUrl ?? attachment.Url)));
|
||||
|
||||
var entry = new StatusEdit
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||
using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
using Iceshrimp.Backend.Core.Configuration;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
@ -15,8 +13,7 @@ public class UserRenderer(
|
|||
IOptions<Config.InstanceSection> config,
|
||||
IOptionsSnapshot<Config.SecuritySection> security,
|
||||
MfmConverter mfmConverter,
|
||||
DatabaseContext db,
|
||||
FlagService flags
|
||||
DatabaseContext db
|
||||
) : IScopedService
|
||||
{
|
||||
private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png";
|
||||
|
@ -34,15 +31,18 @@ public class UserRenderer(
|
|||
|
||||
var profileEmoji = data?.Emoji.Where(p => user.Emojis.Contains(p.Id)).ToList() ?? await GetEmojiAsync([user]);
|
||||
var mentions = profile?.Mentions ?? [];
|
||||
var fields = profile?.Fields
|
||||
.Select(p => new Field
|
||||
{
|
||||
Name = p.Name,
|
||||
Value = (mfmConverter.ToHtml(p.Value, mentions, user.Host)).Html,
|
||||
VerifiedAt = p.IsVerified.HasValue && p.IsVerified.Value
|
||||
? DateTime.Now.ToStringIso8601Like()
|
||||
: null
|
||||
});
|
||||
var fields = profile != null
|
||||
? await profile.Fields
|
||||
.Select(async p => new Field
|
||||
{
|
||||
Name = p.Name,
|
||||
Value = (await mfmConverter.ToHtmlAsync(p.Value, mentions, user.Host)).Html,
|
||||
VerifiedAt = p.IsVerified.HasValue && p.IsVerified.Value
|
||||
? DateTime.Now.ToStringIso8601Like()
|
||||
: null
|
||||
})
|
||||
.AwaitAllAsync()
|
||||
: null;
|
||||
|
||||
var fieldsSource = source
|
||||
? profile?.Fields.Select(p => new Field { Name = p.Name, Value = p.Value }).ToList() ?? []
|
||||
|
@ -51,25 +51,6 @@ public class UserRenderer(
|
|||
var avatarAlt = data?.AvatarAlt.GetValueOrDefault(user.Id);
|
||||
var bannerAlt = data?.BannerAlt.GetValueOrDefault(user.Id);
|
||||
|
||||
string? favicon;
|
||||
string? softwareName;
|
||||
string? softwareVersion;
|
||||
if (user.IsRemoteUser)
|
||||
{
|
||||
var instInfo = await db.Instances
|
||||
.Where(p => p.Host == user.Host)
|
||||
.FirstOrDefaultAsync();
|
||||
favicon = instInfo?.FaviconUrl;
|
||||
softwareName = instInfo?.SoftwareName;
|
||||
softwareVersion = instInfo?.SoftwareVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
favicon = config.Value.WebDomain + "/_content/Iceshrimp.Assets.Branding/favicon.png";
|
||||
softwareName = "iceshrimp";
|
||||
softwareVersion = config.Value.Version;
|
||||
}
|
||||
|
||||
var res = new AccountEntity
|
||||
{
|
||||
Id = user.Id,
|
||||
|
@ -84,7 +65,7 @@ public class UserRenderer(
|
|||
FollowersCount = user.FollowersCount,
|
||||
FollowingCount = user.FollowingCount,
|
||||
StatusesCount = user.NotesCount,
|
||||
Note = mfmConverter.ToHtml(profile?.Description ?? "", mentions, user.Host).Html,
|
||||
Note = (await mfmConverter.ToHtmlAsync(profile?.Description ?? "", mentions, user.Host)).Html,
|
||||
Url = profile?.Url ?? user.Uri ?? user.GetPublicUrl(config.Value),
|
||||
Uri = user.Uri ?? user.GetPublicUri(config.Value),
|
||||
AvatarStaticUrl = user.GetAvatarUrl(config.Value), //TODO
|
||||
|
@ -96,30 +77,7 @@ public class UserRenderer(
|
|||
IsBot = user.IsBot,
|
||||
IsDiscoverable = user.IsExplorable,
|
||||
Fields = fields?.ToList() ?? [],
|
||||
Emoji = profileEmoji,
|
||||
Pleroma = flags?.IsPleroma.Value == true
|
||||
? new PleromaUserExtensions
|
||||
{
|
||||
IsAdmin = user.IsAdmin,
|
||||
IsModerator = user.IsModerator,
|
||||
Favicon = favicon!
|
||||
} : null,
|
||||
Akkoma = flags?.IsPleroma.Value == true
|
||||
? new AkkomaUserExtensions
|
||||
{
|
||||
Instance = new AkkomaInstanceEntity
|
||||
{
|
||||
Name = user.Host ?? config.Value.AccountDomain,
|
||||
NodeInfo = new AkkomaNodeInfoEntity
|
||||
{
|
||||
Software = new AkkomaNodeInfoSoftwareEntity
|
||||
{
|
||||
Name = softwareName,
|
||||
Version = softwareVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
} : null
|
||||
Emoji = profileEmoji
|
||||
};
|
||||
|
||||
if (localUser is null && security.Value.PublicPreview == Enums.PublicPreview.RestrictedNoMedia) //TODO
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
using Iceshrimp.Shared.Helpers;
|
||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
|
@ -6,32 +5,30 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
|||
|
||||
public class AccountEntity : IIdentifiable
|
||||
{
|
||||
[J("username")] public required string Username { get; set; }
|
||||
[J("acct")] public required string Acct { get; set; }
|
||||
[J("fqn")] public required string FullyQualifiedName { get; set; }
|
||||
[J("display_name")] public required string DisplayName { get; set; }
|
||||
[J("locked")] public required bool IsLocked { get; set; }
|
||||
[J("created_at")] public required string CreatedAt { get; set; }
|
||||
[J("followers_count")] public required long FollowersCount { get; set; }
|
||||
[J("following_count")] public required long FollowingCount { get; set; }
|
||||
[J("statuses_count")] public required long StatusesCount { get; set; }
|
||||
[J("note")] public required string Note { get; set; }
|
||||
[J("url")] public required string Url { get; set; }
|
||||
[J("uri")] public required string Uri { get; set; }
|
||||
[J("avatar")] public required string AvatarUrl { get; set; }
|
||||
[J("avatar_static")] public required string AvatarStaticUrl { get; set; }
|
||||
[J("header")] public required string HeaderUrl { get; set; }
|
||||
[J("header_static")] public required string HeaderStaticUrl { get; set; }
|
||||
[J("moved")] public required AccountEntity? MovedToAccount { get; set; }
|
||||
[J("bot")] public required bool IsBot { get; set; }
|
||||
[J("discoverable")] public required bool IsDiscoverable { get; set; }
|
||||
[J("fields")] public required List<Field> Fields { get; set; }
|
||||
[J("source")] public AccountSource? Source { get; set; }
|
||||
[J("emojis")] public required List<EmojiEntity> Emoji { get; set; }
|
||||
[J("id")] public required string Id { get; set; }
|
||||
[J("last_status_at")] public string? LastStatusAt { get; set; }
|
||||
[J("pleroma")] public required PleromaUserExtensions? Pleroma { get; set; }
|
||||
[J("akkoma")] public required AkkomaUserExtensions? Akkoma { get; set; }
|
||||
[J("username")] public required string Username { get; set; }
|
||||
[J("acct")] public required string Acct { get; set; }
|
||||
[J("fqn")] public required string FullyQualifiedName { get; set; }
|
||||
[J("display_name")] public required string DisplayName { get; set; }
|
||||
[J("locked")] public required bool IsLocked { get; set; }
|
||||
[J("created_at")] public required string CreatedAt { get; set; }
|
||||
[J("followers_count")] public required long FollowersCount { get; set; }
|
||||
[J("following_count")] public required long FollowingCount { get; set; }
|
||||
[J("statuses_count")] public required long StatusesCount { get; set; }
|
||||
[J("note")] public required string Note { get; set; }
|
||||
[J("url")] public required string Url { get; set; }
|
||||
[J("uri")] public required string Uri { get; set; }
|
||||
[J("avatar")] public required string AvatarUrl { get; set; }
|
||||
[J("avatar_static")] public required string AvatarStaticUrl { get; set; }
|
||||
[J("header")] public required string HeaderUrl { get; set; }
|
||||
[J("header_static")] public required string HeaderStaticUrl { get; set; }
|
||||
[J("moved")] public required AccountEntity? MovedToAccount { get; set; }
|
||||
[J("bot")] public required bool IsBot { get; set; }
|
||||
[J("discoverable")] public required bool IsDiscoverable { get; set; }
|
||||
[J("fields")] public required List<Field> Fields { get; set; }
|
||||
[J("source")] public AccountSource? Source { get; set; }
|
||||
[J("emojis")] public required List<EmojiEntity> Emoji { get; set; }
|
||||
[J("id")] public required string Id { get; set; }
|
||||
[J("last_status_at")] public string? LastStatusAt { get; set; }
|
||||
|
||||
[J("avatar_description")] public required string AvatarDescription { get; set; }
|
||||
[J("header_description")] public required string HeaderDescription { get; set; }
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Net.Mime;
|
||||
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
||||
using Iceshrimp.Backend.Controllers.Pleroma.Schemas;
|
||||
using Iceshrimp.Backend.Controllers.Shared.Attributes;
|
||||
using Iceshrimp.Backend.Controllers.Web.Renderers;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NoteRenderer = Iceshrimp.Backend.Controllers.Mastodon.Renderers.NoteRenderer;
|
||||
using UserRenderer = Iceshrimp.Backend.Controllers.Mastodon.Renderers.UserRenderer;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma;
|
||||
|
||||
[MastodonApiController]
|
||||
[Authenticate]
|
||||
[EnableCors("mastodon")]
|
||||
[EnableRateLimiting("sliding")]
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
public class AdminController(
|
||||
DatabaseContext db,
|
||||
ReportRenderer reportRenderer,
|
||||
NoteRenderer noteRenderer,
|
||||
UserRenderer userRenderer
|
||||
) : ControllerBase
|
||||
{
|
||||
[HttpGet("/api/v1/pleroma/admin/reports")]
|
||||
[Authenticate("admin:read:reports")]
|
||||
[ProducesResults(HttpStatusCode.OK)]
|
||||
public async Task<ReportsQuery> GetReports()
|
||||
{
|
||||
var user = HttpContext.GetUserOrFail();
|
||||
var reports = await db.Reports
|
||||
.IncludeCommonProperties()
|
||||
.ToListAsync();
|
||||
|
||||
var rendered = await reportRenderer.RenderManyAsync(reports);
|
||||
|
||||
var reportsList = new List<Reports>();
|
||||
foreach (var r in rendered)
|
||||
{
|
||||
var reActor = await db.Users
|
||||
.IncludeCommonProperties()
|
||||
.Where(p => p.Id == r.Reporter.Id)
|
||||
.RenderAllForMastodonAsync(userRenderer, user);
|
||||
|
||||
var reTarget = await db.Users
|
||||
.IncludeCommonProperties()
|
||||
.Where(p => p.Id == r.TargetUser.Id)
|
||||
.RenderAllForMastodonAsync(userRenderer, user);
|
||||
|
||||
foreach (var n in r.Notes)
|
||||
{
|
||||
var note = await db.Notes
|
||||
.IncludeCommonProperties()
|
||||
.Where(p => p.Id == n.Id)
|
||||
.RenderAllForMastodonAsync(noteRenderer, user);
|
||||
|
||||
reportsList.Add(new Reports()
|
||||
{
|
||||
Account = reTarget.FirstOrDefault()!,
|
||||
Actor = reActor.FirstOrDefault()!,
|
||||
Id = r.Id,
|
||||
CreatedAt = r.CreatedAt,
|
||||
State = r.Resolved ? "resolved" : "open",
|
||||
Content = r.Comment,
|
||||
Statuses = note,
|
||||
Notes = [] // unsupported
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var resps = new ReportsQuery()
|
||||
{
|
||||
Total = reportsList.Count,
|
||||
Reports = reportsList
|
||||
};
|
||||
|
||||
return resps;
|
||||
}
|
||||
|
||||
[HttpPatch("/api/v1/pleroma/admin/reports")]
|
||||
[Authenticate("admin:read:reports")]
|
||||
[ProducesResults(HttpStatusCode.OK)]
|
||||
[ProducesErrors(HttpStatusCode.NotFound)]
|
||||
// ReSharper disable once AsyncVoidMethod
|
||||
public async Task<ReportsQuery>? SetReportState(ReportsQuery query)
|
||||
{
|
||||
foreach (var list in query.Reports)
|
||||
{
|
||||
var report = await db.Reports.Where(p => p.Id == list.Id).FirstOrDefaultAsync()
|
||||
?? throw GracefulException.NotFound("Report not found");
|
||||
|
||||
report.Resolved = list.State is "resolved" or "closed";
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
|
||||
public class AkkomaInstanceEntity
|
||||
{
|
||||
[J("name")] public required string Name { get; set; }
|
||||
[J("nodeinfo")] public required AkkomaNodeInfoEntity NodeInfo { get; set; }
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
|
||||
public class AkkomaNodeInfoEntity
|
||||
{
|
||||
[J("software")] public required AkkomaNodeInfoSoftwareEntity Software { get; set; }
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
|
||||
public class AkkomaNodeInfoSoftwareEntity
|
||||
{
|
||||
[J("name")] public required string? Name { get; set; }
|
||||
[J("version")] public required string? Version { get; set; }
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
|
||||
[Keyless]
|
||||
public class AkkomaUserExtensions
|
||||
{
|
||||
[J("instance")] public required AkkomaInstanceEntity Instance { get; set; }
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using System.Runtime.InteropServices.JavaScript;
|
||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
|
||||
public class PleromaOauthTokenEntity
|
||||
{
|
||||
[J("id")] public required string Id { get; set; }
|
||||
[J("valid_until")] public required DateTime ValidUntil { get; set; }
|
||||
[J("app_name")] public required string? AppName { get; set; }
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
|
||||
|
||||
[Keyless]
|
||||
public class PleromaUserExtensions
|
||||
{
|
||||
[J("is_admin")] public required bool IsAdmin { get; set; }
|
||||
[J("is_moderator")] public required bool IsModerator { get; set; }
|
||||
[J("favicon")] public required string Favicon { get; set; }
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||
|
||||
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas;
|
||||
|
||||
public class ReportsQuery
|
||||
{
|
||||
[J("total")] public int Total { get; set; }
|
||||
[J("reports")] public required List<Reports> Reports { get; set; }
|
||||
}
|
||||
|
||||
public class Reports
|
||||
{
|
||||
[J("account")] public AccountEntity? Account { get; set; }
|
||||
[J("actor")] public AccountEntity? Actor { get; set; }
|
||||
[J("id")] public required string Id { get; set; }
|
||||
[J("created_at")] public DateTime? CreatedAt { get; set; }
|
||||
[J("state")] public required string State { get; set; }
|
||||
[J("content")] public string? Content { get; set; }
|
||||
[J("statuses")] public IEnumerable<StatusEntity>? Statuses { get; set; }
|
||||
[J("notes")] public string[]? Notes { get; set; }
|
||||
}
|
|
@ -182,7 +182,7 @@ public class NoteRenderer(
|
|||
To = to,
|
||||
Tags = tags,
|
||||
Attachments = attachments,
|
||||
Content = text != null ? mfmConverter.ToHtml(text, mentions, note.UserHost, media: inlineMedia).Html : null,
|
||||
Content = text != null ? (await mfmConverter.ToHtmlAsync(text, mentions, note.UserHost, media: inlineMedia)).Html : null,
|
||||
Summary = note.Cw,
|
||||
Source = rawText != null
|
||||
? new ASNoteSource { Content = rawText, MediaType = "text/x.misskeymarkdown" }
|
||||
|
@ -214,7 +214,7 @@ public class NoteRenderer(
|
|||
To = to,
|
||||
Tags = tags,
|
||||
Attachments = attachments,
|
||||
Content = text != null ? mfmConverter.ToHtml(text, mentions, note.UserHost, media: inlineMedia).Html : null,
|
||||
Content = text != null ? (await mfmConverter.ToHtmlAsync(text, mentions, note.UserHost, media: inlineMedia)).Html : null,
|
||||
Summary = note.Cw,
|
||||
Source = rawText != null
|
||||
? new ASNoteSource { Content = rawText, MediaType = "text/x.misskeymarkdown" }
|
||||
|
|
|
@ -5,7 +5,6 @@ using Iceshrimp.Backend.Core.Database.Tables;
|
|||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||
using Iceshrimp.MfmSharp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
@ -85,7 +84,7 @@ public class UserRenderer(
|
|||
.ToList();
|
||||
|
||||
var summary = profile?.Description != null
|
||||
? mfmConverter.ToHtml(profile.Description, profile.Mentions, user.Host).Html
|
||||
? (await mfmConverter.ToHtmlAsync(profile.Description, profile.Mentions, user.Host)).Html
|
||||
: null;
|
||||
|
||||
var pronouns = profile?.Pronouns != null ? new LDLocalizedString { Values = profile.Pronouns! } : null;
|
||||
|
|
|
@ -2,14 +2,13 @@ using System.Text;
|
|||
using System.Text.RegularExpressions;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Iceshrimp.Backend.Core.Configuration;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
|
||||
using Iceshrimp.MfmSharp;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
using Iceshrimp.MfmSharp.Helpers;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MfmHtmlParser = Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing.HtmlParser;
|
||||
using HtmlParser = AngleSharp.Html.Parser.HtmlParser;
|
||||
|
@ -48,15 +47,7 @@ public class MfmConverter(
|
|||
FlagService flags
|
||||
) : ISingletonService
|
||||
{
|
||||
private static readonly HtmlParser Parser = new();
|
||||
|
||||
private static readonly Lazy<IHtmlDocument> OwnerDocument =
|
||||
new(() => Parser.ParseDocument(ReadOnlyMemory<char>.Empty));
|
||||
|
||||
private static IElement CreateElement(string name) => OwnerDocument.Value.CreateElement(name);
|
||||
private static IText CreateTextNode(string data) => OwnerDocument.Value.CreateTextNode(data);
|
||||
|
||||
public static HtmlMfmData FromHtml(
|
||||
public static async Task<HtmlMfmData> FromHtmlAsync(
|
||||
string? html, List<Note.MentionedUser>? mentions = null, List<string>? hashtags = null
|
||||
)
|
||||
{
|
||||
|
@ -73,7 +64,7 @@ public class MfmConverter(
|
|||
// Ensure compatibility with AP servers that send CRLF or CR instead of LF-style newlines
|
||||
html = html.ReplaceLineEndings("\n");
|
||||
|
||||
var dom = Parser.ParseDocument(html);
|
||||
var dom = await new HtmlParser().ParseDocumentAsync(html);
|
||||
if (dom.Body == null) return new HtmlMfmData("", media);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
@ -82,7 +73,7 @@ public class MfmConverter(
|
|||
return new HtmlMfmData(sb.ToString().Trim(), media);
|
||||
}
|
||||
|
||||
public static List<string> ExtractMentionsFromHtml(string? html)
|
||||
public static async Task<List<string>> ExtractMentionsFromHtmlAsync(string? html)
|
||||
{
|
||||
if (html == null) return [];
|
||||
|
||||
|
@ -90,7 +81,7 @@ public class MfmConverter(
|
|||
var regex = new Regex(@"<br\s?\/?>\r?\n", RegexOptions.IgnoreCase);
|
||||
html = regex.Replace(html, "\n");
|
||||
|
||||
var dom = Parser.ParseDocument(html);
|
||||
var dom = await new HtmlParser().ParseDocumentAsync(html);
|
||||
if (dom.Body == null) return [];
|
||||
|
||||
var parser = new HtmlMentionsExtractor();
|
||||
|
@ -100,26 +91,28 @@ public class MfmConverter(
|
|||
return parser.Mentions;
|
||||
}
|
||||
|
||||
public MfmHtmlData ToHtml(
|
||||
public async Task<MfmHtmlData> ToHtmlAsync(
|
||||
IMfmNode[] nodes, List<Note.MentionedUser> mentions, string? host, string? quoteUri = null,
|
||||
bool quoteInaccessible = false, bool replyInaccessible = false, string rootElement = "p",
|
||||
List<Emoji>? emoji = null, List<MfmInlineMedia>? media = null
|
||||
)
|
||||
{
|
||||
var element = CreateElement(rootElement);
|
||||
var context = BrowsingContext.New();
|
||||
var document = await context.OpenNewAsync();
|
||||
var element = document.CreateElement(rootElement);
|
||||
var hasContent = nodes.Length > 0;
|
||||
|
||||
if (replyInaccessible)
|
||||
{
|
||||
var wrapper = CreateElement("span");
|
||||
var re = CreateElement("span");
|
||||
var wrapper = document.CreateElement("span");
|
||||
var re = document.CreateElement("span");
|
||||
re.TextContent = "RE: \ud83d\udd12"; // lock emoji
|
||||
wrapper.AppendChild(re);
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
wrapper.AppendChild(CreateElement("br"));
|
||||
wrapper.AppendChild(CreateElement("br"));
|
||||
wrapper.AppendChild(document.CreateElement("br"));
|
||||
wrapper.AppendChild(document.CreateElement("br"));
|
||||
}
|
||||
|
||||
element.AppendChild(wrapper);
|
||||
|
@ -127,23 +120,23 @@ public class MfmConverter(
|
|||
|
||||
var usedMedia = new List<MfmInlineMedia>();
|
||||
foreach (var node in nodes)
|
||||
element.AppendNodes(FromMfmNode(node, mentions, host, usedMedia, emoji, media));
|
||||
element.AppendNodes(FromMfmNode(document, node, mentions, host, usedMedia, emoji, media));
|
||||
|
||||
if (quoteUri != null)
|
||||
{
|
||||
var a = CreateElement("a");
|
||||
var a = document.CreateElement("a");
|
||||
a.SetAttribute("href", quoteUri);
|
||||
a.TextContent = quoteUri.StartsWith("https://") ? quoteUri[8..] : quoteUri[7..];
|
||||
var quote = CreateElement("span");
|
||||
var quote = document.CreateElement("span");
|
||||
quote.ClassList.Add("quote-inline");
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
quote.AppendChild(CreateElement("br"));
|
||||
quote.AppendChild(CreateElement("br"));
|
||||
quote.AppendChild(document.CreateElement("br"));
|
||||
quote.AppendChild(document.CreateElement("br"));
|
||||
}
|
||||
|
||||
var re = CreateElement("span");
|
||||
var re = document.CreateElement("span");
|
||||
re.TextContent = "RE: ";
|
||||
quote.AppendChild(re);
|
||||
quote.AppendChild(a);
|
||||
|
@ -151,47 +144,39 @@ public class MfmConverter(
|
|||
}
|
||||
else if (quoteInaccessible)
|
||||
{
|
||||
var wrapper = CreateElement("span");
|
||||
var re = CreateElement("span");
|
||||
var wrapper = document.CreateElement("span");
|
||||
var re = document.CreateElement("span");
|
||||
re.TextContent = "RE: \ud83d\udd12"; // lock emoji
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
wrapper.AppendChild(CreateElement("br"));
|
||||
wrapper.AppendChild(CreateElement("br"));
|
||||
wrapper.AppendChild(document.CreateElement("br"));
|
||||
wrapper.AppendChild(document.CreateElement("br"));
|
||||
}
|
||||
|
||||
wrapper.AppendChild(re);
|
||||
element.AppendChild(wrapper);
|
||||
}
|
||||
|
||||
return new MfmHtmlData(element.ToHtml(), usedMedia);
|
||||
await using var sw = new StringWriter();
|
||||
await element.ToHtmlAsync(sw);
|
||||
return new MfmHtmlData(sw.ToString(), usedMedia);
|
||||
}
|
||||
|
||||
public MfmHtmlData ToHtml(
|
||||
public async Task<MfmHtmlData> ToHtmlAsync(
|
||||
string mfm, List<Note.MentionedUser> mentions, string? host, string? quoteUri = null,
|
||||
bool quoteInaccessible = false, bool replyInaccessible = false, string rootElement = "p",
|
||||
List<Emoji>? emoji = null, List<MfmInlineMedia>? media = null
|
||||
)
|
||||
{
|
||||
var nodes = MfmParser.Parse(mfm);
|
||||
return ToHtml(nodes, mentions, host, quoteUri, quoteInaccessible, replyInaccessible, rootElement, emoji, media);
|
||||
}
|
||||
|
||||
public string ProfileFieldToHtml(MfmUrlNode node)
|
||||
{
|
||||
var parsed = FromMfmNode(node, [], null, []);
|
||||
if (parsed is not IHtmlAnchorElement el)
|
||||
return parsed.ToHtml();
|
||||
|
||||
el.SetAttribute("rel", "me nofollow noopener");
|
||||
el.SetAttribute("target", "_blank");
|
||||
|
||||
return el.ToHtml();
|
||||
return await ToHtmlAsync(nodes, mentions, host, quoteUri, quoteInaccessible,
|
||||
replyInaccessible, rootElement, emoji, media);
|
||||
}
|
||||
|
||||
private INode FromMfmNode(
|
||||
IMfmNode node, List<Note.MentionedUser> mentions, string? host, List<MfmInlineMedia> usedMedia,
|
||||
IDocument document, IMfmNode node, List<Note.MentionedUser> mentions, string? host,
|
||||
List<MfmInlineMedia> usedMedia,
|
||||
List<Emoji>? emoji = null, List<MfmInlineMedia>? media = null
|
||||
)
|
||||
{
|
||||
|
@ -209,7 +194,7 @@ public class MfmConverter(
|
|||
|
||||
if (!flags.SupportsInlineMedia.Value || current.Type == MfmInlineMedia.MediaType.Other)
|
||||
{
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", current.Src);
|
||||
|
||||
if (current.Type == MfmInlineMedia.MediaType.Other)
|
||||
|
@ -236,7 +221,7 @@ public class MfmConverter(
|
|||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var el = CreateElement(nodeName);
|
||||
var el = document.CreateElement(nodeName);
|
||||
el.SetAttribute("src", current.Src);
|
||||
el.SetAttribute("alt", current.Alt);
|
||||
return el;
|
||||
|
@ -245,16 +230,16 @@ public class MfmConverter(
|
|||
}
|
||||
|
||||
{
|
||||
var el = CreateInlineFormattingElement("i");
|
||||
AddHtmlMarkup(el, "*");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(el, "*");
|
||||
var el = CreateInlineFormattingElement(document, "i");
|
||||
AddHtmlMarkup(document, el, "*");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(document, el, "*");
|
||||
return el;
|
||||
}
|
||||
}
|
||||
case MfmFnNode { Name: "unixtime" } fn:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("i");
|
||||
var el = CreateInlineFormattingElement(document, "i");
|
||||
|
||||
if (fn.Children.Length != 1 || fn.Children.FirstOrDefault() is not MfmTextNode textNode)
|
||||
return Fallback();
|
||||
|
@ -269,55 +254,55 @@ public class MfmConverter(
|
|||
|
||||
IElement Fallback()
|
||||
{
|
||||
AddHtmlMarkup(el, "*");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(el, "*");
|
||||
AddHtmlMarkup(document, el, "*");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(document, el, "*");
|
||||
return el;
|
||||
}
|
||||
}
|
||||
case MfmBoldNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("b");
|
||||
AddHtmlMarkup(el, "**");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(el, "**");
|
||||
var el = CreateInlineFormattingElement(document, "b");
|
||||
AddHtmlMarkup(document, el, "**");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(document, el, "**");
|
||||
return el;
|
||||
}
|
||||
case MfmSmallNode:
|
||||
{
|
||||
var el = CreateElement("small");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
var el = document.CreateElement("small");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
return el;
|
||||
}
|
||||
case MfmStrikeNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("del");
|
||||
AddHtmlMarkup(el, "~~");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(el, "~~");
|
||||
var el = CreateInlineFormattingElement(document, "del");
|
||||
AddHtmlMarkup(document, el, "~~");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(document, el, "~~");
|
||||
return el;
|
||||
}
|
||||
case MfmItalicNode:
|
||||
case MfmFnNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("i");
|
||||
AddHtmlMarkup(el, "*");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(el, "*");
|
||||
var el = CreateInlineFormattingElement(document, "i");
|
||||
AddHtmlMarkup(document, el, "*");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkup(document, el, "*");
|
||||
return el;
|
||||
}
|
||||
case MfmCodeBlockNode codeBlockNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("pre");
|
||||
var inner = CreateInlineFormattingElement("code");
|
||||
var el = CreateInlineFormattingElement(document, "pre");
|
||||
var inner = CreateInlineFormattingElement(document, "code");
|
||||
inner.TextContent = codeBlockNode.Code;
|
||||
el.AppendNodes(inner);
|
||||
return el;
|
||||
}
|
||||
case MfmCenterNode:
|
||||
{
|
||||
var el = CreateElement("div");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
var el = document.CreateElement("div");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
return el;
|
||||
}
|
||||
case MfmEmojiCodeNode emojiCodeNode:
|
||||
|
@ -325,8 +310,8 @@ public class MfmConverter(
|
|||
var punyHost = host?.ToPunycodeLower();
|
||||
if (emoji?.FirstOrDefault(p => p.Name == emojiCodeNode.Name && p.Host == punyHost) is { } hit)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var inner = CreateElement("img");
|
||||
var el = document.CreateElement("span");
|
||||
var inner = document.CreateElement("img");
|
||||
inner.SetAttribute("src", mediaProxy.GetProxyUrl(hit));
|
||||
inner.SetAttribute("alt", hit.Name);
|
||||
el.AppendChild(inner);
|
||||
|
@ -334,11 +319,11 @@ public class MfmConverter(
|
|||
return el;
|
||||
}
|
||||
|
||||
return CreateTextNode($"\u200B:{emojiCodeNode.Name}:\u200B");
|
||||
return document.CreateTextNode($"\u200B:{emojiCodeNode.Name}:\u200B");
|
||||
}
|
||||
case MfmHashtagNode hashtagNode:
|
||||
{
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", $"https://{config.Value.WebDomain}/tags/{hashtagNode.Hashtag}");
|
||||
el.TextContent = $"#{hashtagNode.Hashtag}";
|
||||
el.SetAttribute("rel", "tag");
|
||||
|
@ -347,32 +332,32 @@ public class MfmConverter(
|
|||
}
|
||||
case MfmInlineCodeNode inlineCodeNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("code");
|
||||
var el = CreateInlineFormattingElement(document, "code");
|
||||
el.TextContent = inlineCodeNode.Code;
|
||||
return el;
|
||||
}
|
||||
case MfmInlineMathNode inlineMathNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("code");
|
||||
var el = CreateInlineFormattingElement(document, "code");
|
||||
el.TextContent = inlineMathNode.Formula;
|
||||
return el;
|
||||
}
|
||||
case MfmMathBlockNode mathBlockNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("code");
|
||||
var el = CreateInlineFormattingElement(document, "code");
|
||||
el.TextContent = mathBlockNode.Formula;
|
||||
return el;
|
||||
}
|
||||
case MfmLinkNode linkNode:
|
||||
{
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", linkNode.Url);
|
||||
el.TextContent = linkNode.Text;
|
||||
return el;
|
||||
}
|
||||
case MfmMentionNode mentionNode:
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
// Fall back to object host, as localpart-only mentions are relative to the instance the note originated from
|
||||
var finalHost = mentionNode.Host ?? host ?? config.Value.AccountDomain;
|
||||
|
@ -393,10 +378,10 @@ public class MfmConverter(
|
|||
{
|
||||
el.ClassList.Add("h-card");
|
||||
el.SetAttribute("translate", "no");
|
||||
var a = CreateElement("a");
|
||||
var a = document.CreateElement("a");
|
||||
a.ClassList.Add("u-url", "mention");
|
||||
a.SetAttribute("href", mention.Url ?? mention.Uri);
|
||||
var span = CreateElement("span");
|
||||
var span = document.CreateElement("span");
|
||||
span.TextContent = $"@{mention.Username}";
|
||||
a.AppendChild(span);
|
||||
el.AppendChild(a);
|
||||
|
@ -406,25 +391,25 @@ public class MfmConverter(
|
|||
}
|
||||
case MfmQuoteNode:
|
||||
{
|
||||
var el = CreateInlineFormattingElement("blockquote");
|
||||
AddHtmlMarkup(el, "> ");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkupTag(el, "br");
|
||||
AddHtmlMarkupTag(el, "br");
|
||||
var el = CreateInlineFormattingElement(document, "blockquote");
|
||||
AddHtmlMarkup(document, el, "> ");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
AddHtmlMarkupTag(document, el, "br");
|
||||
AddHtmlMarkupTag(document, el, "br");
|
||||
return el;
|
||||
}
|
||||
case MfmTextNode textNode:
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
var nodes = textNode.Text.Split("\r\n")
|
||||
.SelectMany(p => p.Split('\r'))
|
||||
.SelectMany(p => p.Split('\n'))
|
||||
.Select(CreateTextNode);
|
||||
.Select(document.CreateTextNode);
|
||||
|
||||
foreach (var htmlNode in nodes)
|
||||
{
|
||||
el.AppendNodes(htmlNode);
|
||||
el.AppendNodes(CreateElement("br"));
|
||||
el.AppendNodes(document.CreateElement("br"));
|
||||
}
|
||||
|
||||
if (el.LastChild != null)
|
||||
|
@ -433,25 +418,17 @@ public class MfmConverter(
|
|||
}
|
||||
case MfmUrlNode urlNode:
|
||||
{
|
||||
if (
|
||||
!Uri.TryCreate(urlNode.Url, UriKind.Absolute, out var uri)
|
||||
|| uri is not { Scheme: "http" or "https" }
|
||||
)
|
||||
{
|
||||
var fallbackEl = CreateElement("span");
|
||||
fallbackEl.TextContent = urlNode.Url;
|
||||
return fallbackEl;
|
||||
}
|
||||
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", urlNode.Url);
|
||||
el.TextContent = uri.ToMfmDisplayString();
|
||||
var prefix = urlNode.Url.StartsWith("https://") ? "https://" : "http://";
|
||||
var length = prefix.Length;
|
||||
el.TextContent = urlNode.Url[length..];
|
||||
return el;
|
||||
}
|
||||
case MfmPlainNode:
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
AppendChildren(el, node, mentions, host, usedMedia);
|
||||
var el = document.CreateElement("span");
|
||||
AppendChildren(el, document, node, mentions, host, usedMedia);
|
||||
return el;
|
||||
}
|
||||
default:
|
||||
|
@ -462,32 +439,32 @@ public class MfmConverter(
|
|||
}
|
||||
|
||||
private void AppendChildren(
|
||||
INode element, IMfmNode parent,
|
||||
INode element, IDocument document, IMfmNode parent,
|
||||
List<Note.MentionedUser> mentions, string? host, List<MfmInlineMedia> usedMedia,
|
||||
List<Emoji>? emoji = null, List<MfmInlineMedia>? media = null
|
||||
)
|
||||
{
|
||||
foreach (var node in parent.Children)
|
||||
element.AppendNodes(FromMfmNode(node, mentions, host, usedMedia, emoji, media));
|
||||
element.AppendNodes(FromMfmNode(document, node, mentions, host, usedMedia, emoji, media));
|
||||
}
|
||||
|
||||
private IElement CreateInlineFormattingElement(string name)
|
||||
private IElement CreateInlineFormattingElement(IDocument document, string name)
|
||||
{
|
||||
return CreateElement(flags.SupportsHtmlFormatting.Value ? name : "span");
|
||||
return document.CreateElement(flags.SupportsHtmlFormatting.Value ? name : "span");
|
||||
}
|
||||
|
||||
private void AddHtmlMarkup(IElement node, string chars)
|
||||
private void AddHtmlMarkup(IDocument document, IElement node, string chars)
|
||||
{
|
||||
if (flags.SupportsHtmlFormatting.Value) return;
|
||||
var el = CreateElement("span");
|
||||
el.AppendChild(CreateTextNode(chars));
|
||||
var el = document.CreateElement("span");
|
||||
el.AppendChild(document.CreateTextNode(chars));
|
||||
node.AppendChild(el);
|
||||
}
|
||||
|
||||
private void AddHtmlMarkupTag(IElement node, string tag)
|
||||
private void AddHtmlMarkupTag(IDocument document, IElement node, string tag)
|
||||
{
|
||||
if (flags.SupportsHtmlFormatting.Value) return;
|
||||
var el = CreateElement(tag);
|
||||
var el = document.CreateElement(tag);
|
||||
node.AppendChild(el);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1028,7 +1028,7 @@ public class NoteService(
|
|||
.ToList()
|
||||
?? [];
|
||||
|
||||
(text, htmlInlineMedia) = MfmConverter.FromHtml(note.Content, mentionData.Mentions, hashtags);
|
||||
(text, htmlInlineMedia) = await MfmConverter.FromHtmlAsync(note.Content, mentionData.Mentions, hashtags);
|
||||
}
|
||||
|
||||
var cw = note.Summary;
|
||||
|
@ -1128,7 +1128,7 @@ public class NoteService(
|
|||
.ToList()
|
||||
?? [];
|
||||
|
||||
(text, htmlInlineMedia) = MfmConverter.FromHtml(note.Content, mentionData.Mentions, hashtags);
|
||||
(text, htmlInlineMedia) = await MfmConverter.FromHtmlAsync(note.Content, mentionData.Mentions, hashtags);
|
||||
}
|
||||
|
||||
var cw = note.Summary;
|
||||
|
|
|
@ -27,10 +27,11 @@ public class UserProfileMentionsResolver(
|
|||
?? [];
|
||||
|
||||
if (fields is not { Count: > 0 } && (actor.MkSummary ?? actor.Summary) == null) return ([], []);
|
||||
var parsedFields = fields.SelectMany<ASField, string?>(p => [p.Name, p.Value])
|
||||
.Select(MfmConverter.ExtractMentionsFromHtml);
|
||||
var parsedFields = await fields.SelectMany<ASField, string?>(p => [p.Name, p.Value])
|
||||
.Select(async p => await MfmConverter.ExtractMentionsFromHtmlAsync(p))
|
||||
.AwaitAllAsync();
|
||||
|
||||
var parsedBio = actor.MkSummary == null ? MfmConverter.ExtractMentionsFromHtml(actor.Summary) : [];
|
||||
var parsedBio = actor.MkSummary == null ? await MfmConverter.ExtractMentionsFromHtmlAsync(actor.Summary) : [];
|
||||
|
||||
var userUris = parsedFields.Prepend(parsedBio).SelectMany(p => p).ToList();
|
||||
var mentionNodes = new List<MfmMentionNode>();
|
||||
|
|
|
@ -148,12 +148,16 @@ public class UserService(
|
|||
|
||||
var emoji = await emojiSvc.ProcessEmojiAsync(actor.Tags?.OfType<ASEmoji>().ToList(), host);
|
||||
|
||||
var fields = actor.Attachments?.OfType<ASField>()
|
||||
.Where(p => p is { Name: not null, Value: not null })
|
||||
.Select(p => new UserProfile.Field
|
||||
{
|
||||
Name = p.Name!, Value = MfmConverter.FromHtml(p.Value).Mfm
|
||||
});
|
||||
var fields = actor.Attachments != null
|
||||
? await actor.Attachments
|
||||
.OfType<ASField>()
|
||||
.Where(p => p is { Name: not null, Value: not null })
|
||||
.Select(async p => new UserProfile.Field
|
||||
{
|
||||
Name = p.Name!, Value = (await MfmConverter.FromHtmlAsync(p.Value)).Mfm
|
||||
})
|
||||
.AwaitAllAsync()
|
||||
: null;
|
||||
|
||||
var pronouns = actor.Pronouns?.Values.ToDictionary(p => p.Key, p => p.Value ?? "");
|
||||
|
||||
|
@ -166,7 +170,7 @@ public class UserService(
|
|||
.ToList()
|
||||
?? [];
|
||||
|
||||
bio = MfmConverter.FromHtml(actor.Summary, hashtags: asHashtags).Mfm;
|
||||
bio = (await MfmConverter.FromHtmlAsync(actor.Summary, hashtags: asHashtags)).Mfm;
|
||||
}
|
||||
|
||||
var tags = ResolveHashtags(MfmParser.Parse(bio), actor);
|
||||
|
@ -314,12 +318,16 @@ public class UserService(
|
|||
?? throw new
|
||||
Exception("User host must not be null at this stage"));
|
||||
|
||||
var fields = actor.Attachments?.OfType<ASField>()
|
||||
.Where(p => p is { Name: not null, Value: not null })
|
||||
.Select(p => new UserProfile.Field
|
||||
{
|
||||
Name = p.Name!, Value = MfmConverter.FromHtml(p.Value).Mfm
|
||||
});
|
||||
var fields = actor.Attachments != null
|
||||
? await actor.Attachments
|
||||
.OfType<ASField>()
|
||||
.Where(p => p is { Name: not null, Value: not null })
|
||||
.Select(async p => new UserProfile.Field
|
||||
{
|
||||
Name = p.Name!, Value = (await MfmConverter.FromHtmlAsync(p.Value)).Mfm
|
||||
})
|
||||
.AwaitAllAsync()
|
||||
: null;
|
||||
|
||||
var pronouns = actor.Pronouns?.Values.ToDictionary(p => p.Key, p => p.Value ?? "");
|
||||
|
||||
|
@ -340,7 +348,7 @@ public class UserService(
|
|||
.ToList()
|
||||
?? [];
|
||||
|
||||
user.UserProfile.Description = MfmConverter.FromHtml(actor.Summary, hashtags: asHashtags).Mfm;
|
||||
user.UserProfile.Description = (await MfmConverter.FromHtmlAsync(actor.Summary, hashtags: asHashtags)).Mfm;
|
||||
}
|
||||
|
||||
//user.UserProfile.Birthday = TODO;
|
||||
|
@ -1124,17 +1132,21 @@ public class UserService(
|
|||
{
|
||||
var (mentions, splitDomainMapping) =
|
||||
await bgMentionsResolver.ResolveMentionsAsync(actor, bgUser.Host);
|
||||
var fields = actor.Attachments?.OfType<ASField>()
|
||||
.Where(p => p is { Name: not null, Value: not null })
|
||||
.Select(p => new UserProfile.Field
|
||||
{
|
||||
Name = p.Name!,
|
||||
Value = MfmConverter.FromHtml(p.Value, mentions).Mfm
|
||||
});
|
||||
var fields = actor.Attachments != null
|
||||
? await actor.Attachments
|
||||
.OfType<ASField>()
|
||||
.Where(p => p is { Name: not null, Value: not null })
|
||||
.Select(async p => new UserProfile.Field
|
||||
{
|
||||
Name = p.Name!,
|
||||
Value = (await MfmConverter.FromHtmlAsync(p.Value, mentions)).Mfm
|
||||
})
|
||||
.AwaitAllAsync()
|
||||
: null;
|
||||
|
||||
var description = actor.MkSummary != null
|
||||
? mentionsResolver.ResolveMentions(actor.MkSummary, bgUser.Host, mentions, splitDomainMapping)
|
||||
: MfmConverter.FromHtml(actor.Summary, mentions).Mfm;
|
||||
: (await MfmConverter.FromHtmlAsync(actor.Summary, mentions)).Mfm;
|
||||
|
||||
bgUser.UserProfile.Mentions = mentions;
|
||||
bgUser.UserProfile.Fields = fields?.ToArray() ?? [];
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<PackageReference Include="Ulid" Version="1.3.4" />
|
||||
<PackageReference Include="Iceshrimp.Assets.Branding" Version="1.0.1" />
|
||||
<PackageReference Include="Iceshrimp.AssemblyUtils" Version="1.0.3" />
|
||||
<PackageReference Include="Iceshrimp.MfmSharp" Version="1.2.20" />
|
||||
<PackageReference Include="Iceshrimp.MfmSharp" Version="1.2.18" />
|
||||
<PackageReference Include="Iceshrimp.Utils.Common" Version="1.2.1" />
|
||||
<PackageReference Include="Iceshrimp.MimeTypes" Version="1.0.1" />
|
||||
<PackageReference Include="Iceshrimp.WebPush" Version="2.1.0" />
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"externalUrlConfiguration": true,
|
||||
"commandLineArgs": "--migrate-and-start",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ ListenHost = localhost
|
|||
;;ListenSocketPerms = 660
|
||||
|
||||
;; Caution: changing these settings after initial setup *will* break federation
|
||||
WebDomain = localhost:3000
|
||||
AccountDomain = localhost:3000
|
||||
WebDomain = shrimp.example.org
|
||||
AccountDomain = example.org
|
||||
;; End of problematic settings block
|
||||
|
||||
;; Additional domains this instance allows API access from, separated by commas.
|
||||
|
@ -184,7 +184,7 @@ ProxyRemoteMedia = true
|
|||
|
||||
[Storage:Local]
|
||||
;; Path where media is stored at. Must be writable for the service user.
|
||||
Path = /home/luke/Documents/shrimp
|
||||
Path = /path/to/media/location
|
||||
|
||||
[Storage:ObjectStorage]
|
||||
;;Endpoint = endpoint.example.org
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
if (Text != null)
|
||||
{
|
||||
var instance = await MetadataService.Instance.Value;
|
||||
TextBody = MfmRenderer.RenderString(Text, Emoji, instance.AccountDomain, Simple);
|
||||
TextBody = await MfmRenderer.RenderStringAsync(Text, Emoji, instance.AccountDomain, Simple);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
|||
if (Text != null)
|
||||
{
|
||||
var instance = await MetadataService.Instance.Value;
|
||||
TextBody = MfmRenderer.RenderString(Text, Emoji, instance.AccountDomain, Simple);
|
||||
TextBody = await MfmRenderer.RenderStringAsync(Text, Emoji, instance.AccountDomain, Simple);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@
|
|||
}
|
||||
|
||||
.truncate-btn {
|
||||
z-index: 5;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
background-color: var(--background-color);
|
||||
|
|
|
@ -86,54 +86,54 @@
|
|||
</button>
|
||||
<button @ref="MenuButton" class="btn" @onclick="ToggleMenu" @onclick:stopPropagation="true" aria-label="more">
|
||||
<Icon Name="Icons.DotsThreeOutline" Size="1.3em"/>
|
||||
<Menu @ref="ContextMenu">
|
||||
@if (Note.User.Id != Session.Current?.Id)
|
||||
{
|
||||
<MenuElement Icon="Icons.Tooth" OnSelect="Bite">
|
||||
<Text>@Loc["Bite"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
@if (Note.User.Host != null)
|
||||
{
|
||||
<MenuElement Icon="Icons.ArrowsClockwise" OnSelect="RefetchNote">
|
||||
<Text>@Loc["Refetch"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
<MenuElement Icon="Icons.SpeakerX" OnSelect="Mute">
|
||||
<Text>@Loc["Mute thread"]</Text>
|
||||
</MenuElement>
|
||||
<hr class="rule"/>
|
||||
<MenuElement Icon="Icons.ArrowSquareOut" OnSelect="OpenOriginal">
|
||||
<Text>@Loc["Open original page"]</Text>
|
||||
</MenuElement>
|
||||
<MenuElement Icon="Icons.Share" OnSelect="CopyLink">
|
||||
<Text>@Loc["Copy link"]</Text>
|
||||
</MenuElement>
|
||||
@if (Note.User.Host != null)
|
||||
{
|
||||
<MenuElement Icon="Icons.ShareNetwork" OnSelect="CopyLinkRemote">
|
||||
<Text>@Loc["Copy link (remote)"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Note.Text))
|
||||
{
|
||||
<MenuElement Icon="Icons.Copy" OnSelect="CopyContents">
|
||||
<Text>@Loc["Copy contents"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
@if (Note.User.Id == Session.Current?.Id)
|
||||
{
|
||||
<hr class="rule"/>
|
||||
<MenuElement Icon="Icons.Eraser" OnSelect="Redraft" Danger>
|
||||
<Text>@Loc["Delete & redraft"]</Text>
|
||||
</MenuElement>
|
||||
<MenuElement Icon="Icons.Trash" OnSelect="Delete" Danger>
|
||||
<Text>@Loc["Delete"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
<ClosingBackdrop OnClose="ContextMenu.Close"></ClosingBackdrop>
|
||||
</Menu>
|
||||
</button>
|
||||
<Menu @ref="ContextMenu">
|
||||
@if (Note.User.Id != Session.Current?.Id)
|
||||
{
|
||||
<MenuElement Icon="Icons.Tooth" OnSelect="Bite">
|
||||
<Text>@Loc["Bite"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
@if (Note.User.Host != null)
|
||||
{
|
||||
<MenuElement Icon="Icons.ArrowsClockwise" OnSelect="RefetchNote">
|
||||
<Text>@Loc["Refetch"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
<MenuElement Icon="Icons.SpeakerX" OnSelect="Mute">
|
||||
<Text>@Loc["Mute thread"]</Text>
|
||||
</MenuElement>
|
||||
<hr class="rule"/>
|
||||
<MenuElement Icon="Icons.ArrowSquareOut" OnSelect="OpenOriginal">
|
||||
<Text>@Loc["Open original page"]</Text>
|
||||
</MenuElement>
|
||||
<MenuElement Icon="Icons.Share" OnSelect="CopyLink">
|
||||
<Text>@Loc["Copy link"]</Text>
|
||||
</MenuElement>
|
||||
@if (Note.User.Host != null)
|
||||
{
|
||||
<MenuElement Icon="Icons.ShareNetwork" OnSelect="CopyLinkRemote">
|
||||
<Text>@Loc["Copy link (remote)"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Note.Text))
|
||||
{
|
||||
<MenuElement Icon="Icons.Copy" OnSelect="CopyContents">
|
||||
<Text>@Loc["Copy contents"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
@if (Note.User.Id == Session.Current?.Id)
|
||||
{
|
||||
<hr class="rule"/>
|
||||
<MenuElement Icon="Icons.Eraser" OnSelect="Redraft" Danger>
|
||||
<Text>@Loc["Delete & redraft"]</Text>
|
||||
</MenuElement>
|
||||
<MenuElement Icon="Icons.Trash" OnSelect="Delete" Danger>
|
||||
<Text>@Loc["Delete"]</Text>
|
||||
</MenuElement>
|
||||
}
|
||||
<ClosingBackdrop OnClose="ContextMenu.Close"></ClosingBackdrop>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
|
|
@ -10,11 +10,19 @@
|
|||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
::deep {
|
||||
.reactions .reaction {
|
||||
position: relative;
|
||||
z-index: +1;
|
||||
}
|
||||
}
|
||||
|
||||
.indent {
|
||||
padding-left: 0.75em;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 2.5em;
|
||||
|
@ -23,6 +31,7 @@
|
|||
background-color: var(--foreground-color);
|
||||
border: 0.1rem solid var(--foreground-color);
|
||||
width: fit-content;
|
||||
z-index: +1;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
using AngleSharp.Text;
|
||||
using Iceshrimp.MfmSharp;
|
||||
using Iceshrimp.Shared.Schemas.Web;
|
||||
|
@ -12,37 +10,34 @@ namespace Iceshrimp.Frontend.Core.Miscellaneous;
|
|||
|
||||
public static partial class MfmRenderer
|
||||
{
|
||||
public static MarkupString RenderString(
|
||||
public static async Task<MarkupString> RenderStringAsync(
|
||||
string text, List<EmojiResponse> emoji, string accountDomain, bool simple = false
|
||||
)
|
||||
{
|
||||
var res = MfmParser.Parse(text, simple);
|
||||
var renderedMfm = RenderMultipleNodes(res, emoji, accountDomain, simple);
|
||||
var context = BrowsingContext.New();
|
||||
var document = await context.OpenNewAsync();
|
||||
var renderedMfm = RenderMultipleNodes(res, document, emoji, accountDomain, simple);
|
||||
var html = renderedMfm.ToHtml();
|
||||
return new MarkupString(html);
|
||||
}
|
||||
|
||||
private static readonly Lazy<IHtmlDocument> OwnerDocument =
|
||||
new(() => new HtmlParser().ParseDocument(ReadOnlyMemory<char>.Empty));
|
||||
|
||||
private static IElement CreateElement(string name) => OwnerDocument.Value.CreateElement(name);
|
||||
|
||||
private static INode RenderMultipleNodes(
|
||||
IEnumerable<IMfmNode> nodes, List<EmojiResponse> emoji, string accountDomain, bool simple
|
||||
IEnumerable<IMfmNode> nodes, IDocument document, List<EmojiResponse> emoji, string accountDomain, bool simple
|
||||
)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
el.SetAttribute("mfm", "mfm");
|
||||
el.ClassName = "mfm";
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
el.AppendNodes(RenderNode(node, emoji, accountDomain, simple));
|
||||
el.AppendNodes(RenderNode(node, document, emoji, accountDomain, simple));
|
||||
}
|
||||
catch (NotImplementedException e)
|
||||
{
|
||||
var fallback = CreateElement("span");
|
||||
var fallback = document.CreateElement("span");
|
||||
fallback.TextContent = $"[Node type <{e.Message}> not implemented]";
|
||||
el.AppendNodes(fallback);
|
||||
}
|
||||
|
@ -52,32 +47,32 @@ public static partial class MfmRenderer
|
|||
}
|
||||
|
||||
private static INode RenderNode(
|
||||
IMfmNode node, List<EmojiResponse> emoji, string accountDomain, bool simple
|
||||
IMfmNode node, IDocument document, List<EmojiResponse> emoji, string accountDomain, bool simple
|
||||
)
|
||||
{
|
||||
// Hard wrap makes this impossible to read
|
||||
// @formatter:off
|
||||
var rendered = node switch
|
||||
{
|
||||
MfmCenterNode _ => MfmCenterNode(),
|
||||
MfmCodeBlockNode mfmCodeBlockNode => MfmCodeBlockNode(mfmCodeBlockNode),
|
||||
MfmCenterNode _ => MfmCenterNode(document),
|
||||
MfmCodeBlockNode mfmCodeBlockNode => MfmCodeBlockNode(mfmCodeBlockNode, document),
|
||||
MfmMathBlockNode mfmMathBlockNode => throw new NotImplementedException($"{mfmMathBlockNode.GetType()}"),
|
||||
MfmQuoteNode mfmQuoteNode => MfmQuoteNode(mfmQuoteNode),
|
||||
MfmQuoteNode mfmQuoteNode => MfmQuoteNode(mfmQuoteNode, document),
|
||||
IMfmBlockNode mfmBlockNode => throw new NotImplementedException($"{mfmBlockNode.GetType()}"),
|
||||
MfmBoldNode mfmBoldNode => MfmBoldNode(mfmBoldNode),
|
||||
MfmEmojiCodeNode mfmEmojiCodeNode => MfmEmojiCodeNode(mfmEmojiCodeNode, emoji, simple),
|
||||
MfmFnNode mfmFnNode => MfmFnNode(mfmFnNode),
|
||||
MfmHashtagNode mfmHashtagNode => MfmHashtagNode(mfmHashtagNode),
|
||||
MfmInlineCodeNode mfmInlineCodeNode => MfmInlineCodeNode(mfmInlineCodeNode),
|
||||
MfmItalicNode mfmItalicNode => MfmItalicNode(mfmItalicNode),
|
||||
MfmLinkNode mfmLinkNode => MfmLinkNode(mfmLinkNode),
|
||||
MfmBoldNode mfmBoldNode => MfmBoldNode(mfmBoldNode, document),
|
||||
MfmEmojiCodeNode mfmEmojiCodeNode => MfmEmojiCodeNode(mfmEmojiCodeNode, document, emoji, simple),
|
||||
MfmFnNode mfmFnNode => MfmFnNode(mfmFnNode, document),
|
||||
MfmHashtagNode mfmHashtagNode => MfmHashtagNode(mfmHashtagNode, document),
|
||||
MfmInlineCodeNode mfmInlineCodeNode => MfmInlineCodeNode(mfmInlineCodeNode, document),
|
||||
MfmItalicNode mfmItalicNode => MfmItalicNode(mfmItalicNode, document),
|
||||
MfmLinkNode mfmLinkNode => MfmLinkNode(mfmLinkNode, document),
|
||||
MfmInlineMathNode mfmInlineMathNode => throw new NotImplementedException($"{mfmInlineMathNode.GetType()}"),
|
||||
MfmMentionNode mfmMentionNode => MfmMentionNode(mfmMentionNode, accountDomain),
|
||||
MfmPlainNode mfmPlainNode => MfmPlainNode(mfmPlainNode),
|
||||
MfmSmallNode _ => MfmSmallNode(),
|
||||
MfmStrikeNode mfmStrikeNode => MfmStrikeNode(mfmStrikeNode),
|
||||
MfmTextNode mfmTextNode => MfmTextNode(mfmTextNode),
|
||||
MfmUrlNode mfmUrlNode => MfmUrlNode(mfmUrlNode),
|
||||
MfmMentionNode mfmMentionNode => MfmMentionNode(mfmMentionNode, document, accountDomain),
|
||||
MfmPlainNode mfmPlainNode => MfmPlainNode(mfmPlainNode, document),
|
||||
MfmSmallNode _ => MfmSmallNode(document),
|
||||
MfmStrikeNode mfmStrikeNode => MfmStrikeNode(mfmStrikeNode, document),
|
||||
MfmTextNode mfmTextNode => MfmTextNode(mfmTextNode, document),
|
||||
MfmUrlNode mfmUrlNode => MfmUrlNode(mfmUrlNode, document),
|
||||
IMfmInlineNode mfmInlineNode => throw new NotImplementedException($"{mfmInlineNode.GetType()}"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
||||
};
|
||||
|
@ -89,11 +84,11 @@ public static partial class MfmRenderer
|
|||
{
|
||||
try
|
||||
{
|
||||
rendered.AppendNodes(RenderNode(childNode, emoji, accountDomain, simple));
|
||||
rendered.AppendNodes(RenderNode(childNode, document, emoji, accountDomain, simple));
|
||||
}
|
||||
catch (NotImplementedException e)
|
||||
{
|
||||
var fallback = CreateElement("span");
|
||||
var fallback = document.CreateElement("span");
|
||||
fallback.TextContent = $"[Node type <{e.Message}> not implemented]";
|
||||
rendered.AppendNodes(fallback);
|
||||
}
|
||||
|
@ -103,56 +98,56 @@ public static partial class MfmRenderer
|
|||
return rendered;
|
||||
}
|
||||
|
||||
private static INode MfmPlainNode(MfmPlainNode _)
|
||||
private static INode MfmPlainNode(MfmPlainNode _, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
el.ClassName = "plain";
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmCenterNode()
|
||||
private static INode MfmCenterNode(IDocument document)
|
||||
{
|
||||
var el = CreateElement("div");
|
||||
var el = document.CreateElement("div");
|
||||
el.SetAttribute("style", "text-align: center");
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmCodeBlockNode(MfmCodeBlockNode node)
|
||||
private static INode MfmCodeBlockNode(MfmCodeBlockNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("pre");
|
||||
var el = document.CreateElement("pre");
|
||||
el.ClassName = "code-pre";
|
||||
var childEl = CreateElement("code");
|
||||
var childEl = document.CreateElement("code");
|
||||
childEl.TextContent = node.Code;
|
||||
el.AppendChild(childEl);
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmQuoteNode(MfmQuoteNode _)
|
||||
private static INode MfmQuoteNode(MfmQuoteNode _, IDocument document)
|
||||
{
|
||||
var el = CreateElement("blockquote");
|
||||
var el = document.CreateElement("blockquote");
|
||||
el.ClassName = "quote-node";
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmInlineCodeNode(MfmInlineCodeNode node)
|
||||
private static INode MfmInlineCodeNode(MfmInlineCodeNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("code");
|
||||
var el = document.CreateElement("code");
|
||||
el.TextContent = node.Code;
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmHashtagNode(MfmHashtagNode node)
|
||||
private static INode MfmHashtagNode(MfmHashtagNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", $"/tags/{node.Hashtag}");
|
||||
el.ClassName = "hashtag-node";
|
||||
el.TextContent = "#" + node.Hashtag;
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmLinkNode(MfmLinkNode node)
|
||||
private static INode MfmLinkNode(MfmLinkNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", node.Url);
|
||||
el.SetAttribute("target", "_blank");
|
||||
el.ClassName = "link-node";
|
||||
|
@ -160,18 +155,18 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmItalicNode(MfmItalicNode _)
|
||||
private static INode MfmItalicNode(MfmItalicNode _, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
el.SetAttribute("style", "font-style: italic");
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmEmojiCodeNode(
|
||||
MfmEmojiCodeNode node, List<EmojiResponse> emojiList, bool simple
|
||||
MfmEmojiCodeNode node, IDocument document, List<EmojiResponse> emojiList, bool simple
|
||||
)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
el.ClassName = simple ? "emoji simple" : "emoji";
|
||||
|
||||
var emoji = emojiList.Find(p => p.Name == node.Name);
|
||||
|
@ -181,7 +176,7 @@ public static partial class MfmRenderer
|
|||
}
|
||||
else
|
||||
{
|
||||
var image = CreateElement("img");
|
||||
var image = document.CreateElement("img");
|
||||
image.SetAttribute("src", emoji.PublicUrl);
|
||||
image.SetAttribute("alt", node.Name);
|
||||
image.SetAttribute("title", $":{emoji.Name}:");
|
||||
|
@ -191,9 +186,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmUrlNode(MfmUrlNode node)
|
||||
private static INode MfmUrlNode(MfmUrlNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("a");
|
||||
var el = document.CreateElement("a");
|
||||
el.SetAttribute("href", node.Url);
|
||||
el.SetAttribute("target", "_blank");
|
||||
el.ClassName = "url-node";
|
||||
|
@ -201,47 +196,47 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmBoldNode(MfmBoldNode _)
|
||||
private static INode MfmBoldNode(MfmBoldNode _, IDocument document)
|
||||
{
|
||||
var el = CreateElement("strong");
|
||||
var el = document.CreateElement("strong");
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmSmallNode()
|
||||
private static INode MfmSmallNode(IDocument document)
|
||||
{
|
||||
var el = CreateElement("small");
|
||||
var el = document.CreateElement("small");
|
||||
el.SetAttribute("style", "opacity: 0.7;");
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmStrikeNode(MfmStrikeNode _)
|
||||
private static INode MfmStrikeNode(MfmStrikeNode _, IDocument document)
|
||||
{
|
||||
var el = CreateElement("del");
|
||||
var el = document.CreateElement("del");
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmTextNode(MfmTextNode node)
|
||||
private static INode MfmTextNode(MfmTextNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
el.TextContent = node.Text;
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmMentionNode(MfmMentionNode node, string accountDomain)
|
||||
private static INode MfmMentionNode(MfmMentionNode node, IDocument document, string accountDomain)
|
||||
{
|
||||
var link = CreateElement("a");
|
||||
var link = document.CreateElement("a");
|
||||
link.SetAttribute("href",
|
||||
node.Host != null && node.Host != accountDomain
|
||||
? $"/@{node.Acct}"
|
||||
: $"/@{node.User}");
|
||||
link.ClassName = "mention";
|
||||
var userPart = CreateElement("span");
|
||||
var userPart = document.CreateElement("span");
|
||||
userPart.ClassName = "user";
|
||||
userPart.TextContent = $"@{node.User}";
|
||||
link.AppendChild(userPart);
|
||||
if (node.Host != null && node.Host != accountDomain)
|
||||
{
|
||||
var hostPart = CreateElement("span");
|
||||
var hostPart = document.CreateElement("span");
|
||||
hostPart.ClassName = "host";
|
||||
hostPart.TextContent = $"@{node.Host}";
|
||||
link.AppendChild(hostPart);
|
||||
|
@ -250,46 +245,46 @@ public static partial class MfmRenderer
|
|||
return link;
|
||||
}
|
||||
|
||||
private static INode MfmFnNode(MfmFnNode node)
|
||||
private static INode MfmFnNode(MfmFnNode node, IDocument document)
|
||||
{
|
||||
// Simplify node.Args structure to make it more readable in below functions
|
||||
var args = node.Args ?? [];
|
||||
|
||||
return node.Name switch {
|
||||
"flip" => MfmFnFlip(args),
|
||||
"font" => MfmFnFont(args),
|
||||
"x2" => MfmFnX(node.Name),
|
||||
"x3" => MfmFnX(node.Name),
|
||||
"x4" => MfmFnX(node.Name),
|
||||
"blur" => MfmFnBlur(),
|
||||
"jelly" => MfmFnAnimation(node.Name, args),
|
||||
"tada" => MfmFnAnimation(node.Name, args),
|
||||
"jump" => MfmFnAnimation(node.Name, args, "0.75s"),
|
||||
"bounce" => MfmFnAnimation(node.Name, args, "0.75s"),
|
||||
"spin" => MfmFnSpin(args),
|
||||
"shake" => MfmFnAnimation(node.Name, args, "0.5s"),
|
||||
"twitch" => MfmFnAnimation(node.Name, args, "0.5s"),
|
||||
"rainbow" => MfmFnAnimation(node.Name, args),
|
||||
"flip" => MfmFnFlip(args, document),
|
||||
"font" => MfmFnFont(args, document),
|
||||
"x2" => MfmFnX(node.Name, document),
|
||||
"x3" => MfmFnX(node.Name, document),
|
||||
"x4" => MfmFnX(node.Name, document),
|
||||
"blur" => MfmFnBlur(document),
|
||||
"jelly" => MfmFnAnimation(node.Name, args, document),
|
||||
"tada" => MfmFnAnimation(node.Name, args, document),
|
||||
"jump" => MfmFnAnimation(node.Name, args, document, "0.75s"),
|
||||
"bounce" => MfmFnAnimation(node.Name, args, document, "0.75s"),
|
||||
"spin" => MfmFnSpin(args, document),
|
||||
"shake" => MfmFnAnimation(node.Name, args, document, "0.5s"),
|
||||
"twitch" => MfmFnAnimation(node.Name, args, document, "0.5s"),
|
||||
"rainbow" => MfmFnAnimation(node.Name, args, document),
|
||||
"sparkle" => throw new NotImplementedException($"{node.Name}"),
|
||||
"rotate" => MfmFnRotate(args),
|
||||
"fade" => MfmFnFade(args),
|
||||
"crop" => MfmFnCrop(args),
|
||||
"position" => MfmFnPosition(args),
|
||||
"scale" => MfmFnScale(args),
|
||||
"fg" => MfmFnFg(args),
|
||||
"bg" => MfmFnBg(args),
|
||||
"border" => MfmFnBorder(args),
|
||||
"ruby" => MfmFnRuby(node),
|
||||
"unixtime" => MfmFnUnixtime(node),
|
||||
"center" => MfmCenterNode(),
|
||||
"small" => MfmSmallNode(),
|
||||
"rotate" => MfmFnRotate(args, document),
|
||||
"fade" => MfmFnFade(args, document),
|
||||
"crop" => MfmFnCrop(args, document),
|
||||
"position" => MfmFnPosition(args, document),
|
||||
"scale" => MfmFnScale(args, document),
|
||||
"fg" => MfmFnFg(args, document),
|
||||
"bg" => MfmFnBg(args, document),
|
||||
"border" => MfmFnBorder(args, document),
|
||||
"ruby" => MfmFnRuby(node, document),
|
||||
"unixtime" => MfmFnUnixtime(node, document),
|
||||
"center" => MfmCenterNode(document),
|
||||
"small" => MfmSmallNode(document),
|
||||
_ => throw new NotImplementedException($"{node.Name}")
|
||||
};
|
||||
}
|
||||
|
||||
private static INode MfmFnFlip(Dictionary<string, string?> args)
|
||||
private static INode MfmFnFlip(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
if (args.ContainsKey("h") && args.ContainsKey("v"))
|
||||
el.ClassName = "fn-flip h v";
|
||||
|
@ -301,9 +296,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnFont(Dictionary<string, string?> args)
|
||||
private static INode MfmFnFont(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
if (args.ContainsKey("serif"))
|
||||
el.SetAttribute("style", "font-family: serif;");
|
||||
|
@ -317,9 +312,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnX(string name)
|
||||
private static INode MfmFnX(string name, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
var size = name switch
|
||||
{
|
||||
|
@ -333,9 +328,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnBlur()
|
||||
private static INode MfmFnBlur(IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
el.ClassName = "fn-blur";
|
||||
|
||||
|
@ -343,10 +338,10 @@ public static partial class MfmRenderer
|
|||
}
|
||||
|
||||
private static INode MfmFnAnimation(
|
||||
string name, Dictionary<string, string?> args, string defaultSpeed = "1s"
|
||||
string name, Dictionary<string, string?> args, IDocument document, string defaultSpeed = "1s"
|
||||
)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
el.ClassName = "fn-animation";
|
||||
|
||||
|
@ -363,9 +358,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnSpin(Dictionary<string, string?> args)
|
||||
private static INode MfmFnSpin(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
el.ClassName = "fn-spin";
|
||||
|
||||
|
@ -390,9 +385,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnRotate(Dictionary<string, string?> args)
|
||||
private static INode MfmFnRotate(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
var deg = args.GetValueOrDefault("deg") ?? "90";
|
||||
|
||||
|
@ -405,9 +400,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnFade(Dictionary<string, string?> args)
|
||||
private static INode MfmFnFade(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
el.ClassName = "fn-fade";
|
||||
|
||||
|
@ -422,9 +417,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnCrop(Dictionary<string, string?> args)
|
||||
private static INode MfmFnCrop(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
var inset = $"{args.GetValueOrDefault("top") ?? "0"}% {args.GetValueOrDefault("right") ?? "0"}% {args.GetValueOrDefault("bottom") ?? "0"}% {args.GetValueOrDefault("left") ?? "0"}%";
|
||||
el.SetAttribute("style", $"display: inline-block; clip-path: inset({inset});");
|
||||
|
@ -432,9 +427,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnPosition(Dictionary<string, string?> args)
|
||||
private static INode MfmFnPosition(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
var translateX = args.GetValueOrDefault("x") ?? "0";
|
||||
var translateY = args.GetValueOrDefault("y") ?? "0";
|
||||
|
@ -443,9 +438,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnScale(Dictionary<string, string?> args)
|
||||
private static INode MfmFnScale(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
var scaleX = args.GetValueOrDefault("x") ?? "1";
|
||||
var scaleY = args.GetValueOrDefault("y") ?? "1";
|
||||
|
@ -462,9 +457,9 @@ public static partial class MfmRenderer
|
|||
return color != null && ColorRegex().Match(color).Success;
|
||||
}
|
||||
|
||||
private static INode MfmFnFg(Dictionary<string, string?> args)
|
||||
private static INode MfmFnFg(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
if (args.TryGetValue("color", out var color) && ValidColor(color))
|
||||
el.SetAttribute("style", $"display: inline-block; color: #{color};");
|
||||
|
@ -472,9 +467,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnBg(Dictionary<string, string?> args)
|
||||
private static INode MfmFnBg(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
if (args.TryGetValue("color", out var color) && ValidColor(color))
|
||||
el.SetAttribute("style", $"display: inline-block; background-color: #{color};");
|
||||
|
@ -482,9 +477,9 @@ public static partial class MfmRenderer
|
|||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnBorder(Dictionary<string, string?> args)
|
||||
private static INode MfmFnBorder(Dictionary<string, string?> args, IDocument document)
|
||||
{
|
||||
var el = CreateElement("span");
|
||||
var el = document.CreateElement("span");
|
||||
|
||||
var width = args.GetValueOrDefault("width") ?? "1";
|
||||
var radius = args.GetValueOrDefault("radius") ?? "0";
|
||||
|
@ -505,9 +500,9 @@ public static partial class MfmRenderer
|
|||
};
|
||||
}
|
||||
|
||||
private static INode MfmFnRuby(MfmFnNode node)
|
||||
private static INode MfmFnRuby(MfmFnNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("ruby");
|
||||
var el = document.CreateElement("ruby");
|
||||
|
||||
if (node.Children.Length != 1) return el;
|
||||
var childText = GetNodeText(node.Children[0]);
|
||||
|
@ -517,24 +512,24 @@ public static partial class MfmRenderer
|
|||
|
||||
el.TextContent = split[0];
|
||||
|
||||
var rp1 = CreateElement("rp");
|
||||
var rp1 = document.CreateElement("rp");
|
||||
rp1.TextContent = "(";
|
||||
el.AppendChild(rp1);
|
||||
|
||||
var rt = CreateElement("rt");
|
||||
var rt = document.CreateElement("rt");
|
||||
rt.TextContent = split[1];
|
||||
el.AppendChild(rt);
|
||||
|
||||
var rp2 = CreateElement("rp");
|
||||
var rp2 = document.CreateElement("rp");
|
||||
rp1.TextContent = ")";
|
||||
el.AppendChild(rp2);
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
private static INode MfmFnUnixtime(MfmFnNode node)
|
||||
private static INode MfmFnUnixtime(MfmFnNode node, IDocument document)
|
||||
{
|
||||
var el = CreateElement("time");
|
||||
var el = document.CreateElement("time");
|
||||
|
||||
if (node.Children.Length != 1) return el;
|
||||
var childText = GetNodeText(node.Children[0]);
|
||||
|
|
|
@ -36,7 +36,7 @@ internal class NoteStore : NoteMessageProvider, IDisposable
|
|||
note.Poll = noteResponse.Poll;
|
||||
|
||||
AnyNoteChanged?.Invoke(this, note);
|
||||
NoteChangedHandlers.FirstOrDefault(p => p.Key == note.Id).Value?.Invoke(this, note);
|
||||
NoteChangedHandlers.First(p => p.Key == note.Id).Value.Invoke(this, note);
|
||||
}
|
||||
}
|
||||
public void Delete(string id)
|
||||
|
|
|
@ -87,7 +87,7 @@ internal class NotificationStore : NoteMessageProvider, IAsyncDisposable
|
|||
el.Value.Note.Attachments = noteResponse.Attachments;
|
||||
el.Value.Note.Reactions = noteResponse.Reactions;
|
||||
el.Value.Note.Poll = noteResponse.Poll;
|
||||
NoteChangedHandlers.FirstOrDefault(p => p.Key == noteResponse.Id).Value?.Invoke(this, el.Value.Note);
|
||||
NoteChangedHandlers.First(p => p.Key == noteResponse.Id).Value.Invoke(this, el.Value.Note);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ internal class RelatedStore : NoteMessageProvider, IDisposable
|
|||
note.Reactions = noteResponse.Reactions;
|
||||
note.Poll = noteResponse.Poll;
|
||||
|
||||
NoteChangedHandlers.FirstOrDefault(p => p.Key == note.Id).Value?.Invoke(this, note);
|
||||
NoteChangedHandlers.First(p => p.Key == note.Id).Value.Invoke(this, note);
|
||||
NoteChanged?.Invoke(this, note);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using System.Diagnostics;
|
||||
using Iceshrimp.Shared.Schemas.Web;
|
||||
|
||||
namespace Iceshrimp.Frontend.Core.Services.NoteStore;
|
||||
|
@ -17,8 +16,6 @@ internal class StateSynchronizer: IAsyncDisposable
|
|||
|
||||
public void Broadcast(NoteBase note)
|
||||
{
|
||||
// Trace Logging for broadcast note is null;
|
||||
if (note == null) throw new UnreachableException("Note null when not nullable");
|
||||
NoteChanged?.Invoke(this, note);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="BlazorIntersectionObserver" Version="3.1.0" />
|
||||
<PackageReference Include="Iceshrimp.Assets.Branding" Version="1.0.1" />
|
||||
<PackageReference Include="Iceshrimp.MfmSharp" Version="1.2.20" />
|
||||
<PackageReference Include="Iceshrimp.MfmSharp" Version="1.2.18" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.2" />
|
||||
|
|
Loading…
Add table
Reference in a new issue