using AngleSharp; using AngleSharp.Dom; using Iceshrimp.Parsing; using Iceshrimp.Shared.Schemas; using Microsoft.AspNetCore.Components; using Microsoft.FSharp.Core; namespace Iceshrimp.Frontend.Core.Miscellaneous; public class MfmRenderer { public static async Task RenderString(string text, List emoji) { var res = Mfm.parse(text); var context = BrowsingContext.New(); var document = await context.OpenNewAsync(); var renderedMfm = MfmRenderer.RenderMultipleNodes(res, document, emoji); var html = renderedMfm.ToHtml(); return new MarkupString(html); } public static INode RenderMultipleNodes(IEnumerable nodes, IDocument document, List emoji) { var el = document.CreateElement("span"); el.SetAttribute("mfm", "mfm"); el.ClassName = "mfm"; foreach (var node in nodes) { try { el.AppendNodes(RenderNode(node, document, emoji)); } catch (NotImplementedException e) { var fallback = document.CreateElement("span"); fallback.TextContent = $"[Node type <{e.Message}> not implemented]"; el.AppendNodes(fallback); } } return el; } private static INode RenderNode(MfmNodeTypes.MfmNode node, IDocument document, List emoji) { var rendered = node switch { MfmNodeTypes.MfmCenterNode mfmCenterNode => throw new NotImplementedException($"{mfmCenterNode.GetType()}"), MfmNodeTypes.MfmCodeBlockNode mfmCodeBlockNode => MfmCodeBlockNode(mfmCodeBlockNode, document), MfmNodeTypes.MfmMathBlockNode mfmMathBlockNode => throw new NotImplementedException($"{mfmMathBlockNode.GetType()}"), MfmNodeTypes.MfmQuoteNode mfmQuoteNode => throw new NotImplementedException($"{mfmQuoteNode.GetType()}"), 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.MfmFnNode mfmFnNode => throw new NotImplementedException($"{mfmFnNode.GetType()}"), MfmNodeTypes.MfmHashtagNode mfmHashtagNode => throw new NotImplementedException($"{mfmHashtagNode.GetType()}"), MfmNodeTypes.MfmInlineCodeNode mfmInlineCodeNode => MfmInlineCodeNode(mfmInlineCodeNode, document), MfmNodeTypes.MfmItalicNode mfmItalicNode => MfmItalicNode(mfmItalicNode, document), MfmNodeTypes.MfmLinkNode mfmLinkNode => MfmLinkNode(mfmLinkNode, document), MfmNodeTypes.MfmMathInlineNode mfmMathInlineNode => throw new NotImplementedException($"{mfmMathInlineNode.GetType()}"), MfmNodeTypes.MfmMentionNode mfmMentionNode => MfmMentionNode(mfmMentionNode, document), MfmNodeTypes.MfmPlainNode mfmPlainNode => throw new NotImplementedException($"{mfmPlainNode.GetType()}"), MfmNodeTypes.MfmSmallNode mfmSmallNode => throw new NotImplementedException($"{mfmSmallNode.GetType()}"), MfmNodeTypes.MfmStrikeNode mfmStrikeNode => throw new NotImplementedException($"{mfmStrikeNode.GetType()}"), MfmNodeTypes.MfmTextNode mfmTextNode => MfmTextNode(mfmTextNode, document), MfmNodeTypes.MfmUrlNode mfmUrlNode => MfmUrlNode(mfmUrlNode, document), MfmNodeTypes.MfmInlineNode mfmInlineNode => throw new NotImplementedException($"{mfmInlineNode.GetType()}"), _ => throw new ArgumentOutOfRangeException(nameof(node)) }; if (node.Children.Length > 0) { foreach (var childNode in node.Children) { try { rendered.AppendNodes(RenderNode(childNode, document, emoji)); } catch (NotImplementedException e) { var fallback = document.CreateElement("span"); fallback.TextContent = $"[Node type <{e.Message}> not implemented]"; rendered.AppendNodes(fallback); } } } return rendered; } private static INode MfmCodeBlockNode(MfmNodeTypes.MfmCodeBlockNode node, IDocument document) { var el = document.CreateElement("pre"); var childEl = document.CreateElement("code"); childEl.TextContent = node.Code; el.AppendChild(childEl); return el; } private static INode MfmInlineCodeNode(MfmNodeTypes.MfmInlineCodeNode node, IDocument document) { var el = document.CreateElement("code"); el.TextContent = node.Code; return el; } private static INode MfmLinkNode(MfmNodeTypes.MfmLinkNode node, IDocument document) { var el = document.CreateElement("a"); el.SetAttribute("href", node.Url); el.ClassName = "link-node"; return el; } private static INode MfmItalicNode(MfmNodeTypes.MfmItalicNode node, IDocument document) { var el = document.CreateElement("span"); el.SetAttribute("style", "font-style: italic"); return el; } private static INode MfmEmojiCodeNode(MfmNodeTypes.MfmEmojiCodeNode node, IDocument document, List emojiList) { var el = document.CreateElement("span"); el.ClassName = "emoji"; var emoji = emojiList.Find(p => p.Name == node.Name); if (emoji is null) { el.TextContent = node.Name; } else { var image = document.CreateElement("img"); image.SetAttribute("src", emoji.PublicUrl); image.SetAttribute("alt", node.Name); el.AppendChild(image); } return el; } private static INode MfmUrlNode(MfmNodeTypes.MfmUrlNode node, IDocument document) { var el = document.CreateElement("a"); el.SetAttribute("href", node.Url); el.ClassName = "url-node"; el.TextContent = node.Url; return el; } private static INode MfmBoldNode(MfmNodeTypes.MfmBoldNode node, IDocument document) { var el = document.CreateElement("strong"); return el; } private static INode MfmTextNode(MfmNodeTypes.MfmTextNode node, IDocument document) { var el = document.CreateElement("span"); el.TextContent = node.Text; return el; } private static INode MfmMentionNode(MfmNodeTypes.MfmMentionNode node, IDocument document) { var link = document.CreateElement("a"); link.SetAttribute("href", $"/@{node.Acct}"); link.ClassName = "mention"; var userPart = document.CreateElement("span"); userPart.ClassName = "user"; userPart.TextContent = $"@{node.Username}"; link.AppendChild(userPart); if (node.Host != null) { var hostPart = document.CreateElement("span"); hostPart.ClassName = "host"; hostPart.TextContent = $"@{node.Host.Value}"; link.AppendChild(hostPart); } return link; } }