diff --git a/Iceshrimp.Backend/Core/Helpers/LibMfm/Serialization/MfmSerializer.cs b/Iceshrimp.Backend/Core/Helpers/LibMfm/Serialization/MfmSerializer.cs
index 22b37ad7..63b3a1b2 100644
--- a/Iceshrimp.Backend/Core/Helpers/LibMfm/Serialization/MfmSerializer.cs
+++ b/Iceshrimp.Backend/Core/Helpers/LibMfm/Serialization/MfmSerializer.cs
@@ -35,11 +35,13 @@ public static class MfmSerializer
result.Append(" [search]");
break;
}
- case MfmBoldNode:
+ case MfmBoldNode mfmBoldNode:
{
- result.Append("**");
+ var start = mfmBoldNode.Type.IsSymbol ? "**" : "";
+ var end = mfmBoldNode.Type.IsSymbol ? "**" : "";
+ result.Append(start);
result.Append(SerializeInternal(node.Children));
- result.Append("**");
+ result.Append(end);
break;
}
case MfmCenterNode:
@@ -80,11 +82,13 @@ public static class MfmSerializer
result.Append($"`{mfmInlineCodeNode.Code}`");
break;
}
- case MfmItalicNode:
+ case MfmItalicNode mfmItalicNode:
{
- result.Append('*');
+ var start = mfmItalicNode.Type.IsSymbol ? "*" : "";
+ var end = mfmItalicNode.Type.IsSymbol ? "*" : "";
+ result.Append(start);
result.Append(SerializeInternal(node.Children));
- result.Append('*');
+ result.Append(end);
break;
}
case MfmLinkNode mfmLinkNode:
@@ -125,11 +129,13 @@ public static class MfmSerializer
result.Append("");
break;
}
- case MfmStrikeNode:
+ case MfmStrikeNode mfmStrikeNode:
{
- result.Append("~~");
+ var start = mfmStrikeNode.Type.IsSymbol ? "~~" : "";
+ var end = mfmStrikeNode.Type.IsSymbol ? "~~" : "";
+ result.Append(start);
result.Append(SerializeInternal(node.Children));
- result.Append("~~");
+ result.Append(end);
break;
}
case MfmTextNode mfmTextNode:
diff --git a/Iceshrimp.Parsing/Mfm.fs b/Iceshrimp.Parsing/Mfm.fs
index d3426610..30cacfb3 100644
--- a/Iceshrimp.Parsing/Mfm.fs
+++ b/Iceshrimp.Parsing/Mfm.fs
@@ -19,18 +19,25 @@ module MfmNodeTypes =
inherit MfmNode()
do base.Children <- c |> List.map (fun x -> x :> MfmNode)
+ type InlineNodeType =
+ | Symbol
+ | HtmlTag
+
type MfmTextNode(v: string) =
inherit MfmInlineNode([])
member val Text = v
- type MfmItalicNode(c) =
+ type MfmItalicNode(c, t) =
inherit MfmInlineNode(c)
+ member val Type: InlineNodeType = t
- type MfmBoldNode(c) =
+ type MfmBoldNode(c, t) =
inherit MfmInlineNode(c)
+ member val Type: InlineNodeType = t
- type MfmStrikeNode(c) =
+ type MfmStrikeNode(c, t) =
inherit MfmInlineNode(c)
+ member val Type: InlineNodeType = t
type MfmInlineCodeNode(v: string) =
inherit MfmInlineNode([])
@@ -240,7 +247,7 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode italicPatternAsterisk
.>> assertLine
- |>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmItalicNode(aggregateTextInline c, Symbol) :> MfmNode
let italicUnderscoreNode =
previousCharSatisfiesNot isNotWhitespace
@@ -248,11 +255,11 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode italicPatternUnderscore
.>> assertLine
- |>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmItalicNode(aggregateTextInline c, Symbol) :> MfmNode
let italicTagNode =
skipString "" >>. manyTill inlineNode (skipString "")
- |>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmItalicNode(aggregateTextInline c, HtmlTag) :> MfmNode
let boldAsteriskNode =
previousCharSatisfiesNot isNotWhitespace
@@ -260,7 +267,7 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode (skipString "**")
.>> assertLine
- |>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmBoldNode(aggregateTextInline c, Symbol) :> MfmNode
let boldUnderscoreNode =
previousCharSatisfiesNot isNotWhitespace
@@ -268,16 +275,20 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode (skipString "__")
.>> assertLine
- |>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmBoldNode(aggregateTextInline c, Symbol) :> MfmNode
let boldTagNode =
skipString "" >>. manyTill inlineNode (skipString "")
- |>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmBoldNode(aggregateTextInline c, HtmlTag) :> MfmNode
let strikeNode =
skipString "~~" >>. pushLine >>. manyTill inlineNode (skipString "~~")
.>> assertLine
- |>> fun c -> MfmStrikeNode(aggregateTextInline c) :> MfmNode
+ |>> fun c -> MfmStrikeNode(aggregateTextInline c, Symbol) :> MfmNode
+
+ let strikeTagNode =
+ skipString "" >>. manyTill inlineNode (skipString "")
+ |>> fun c -> MfmStrikeNode(aggregateTextInline c, HtmlTag) :> MfmNode
let codeNode =
codePattern >>. pushLine >>. manyCharsTill anyChar codePattern .>> assertLine
@@ -430,6 +441,14 @@ module private MfmParser =
| Simple
let parseNode (m: ParseMode) =
+ let inlineTagNodes =
+ [ plainNode
+ smallNode
+ italicTagNode
+ boldTagNode
+ strikeTagNode
+ urlNodeBrackets ]
+
let prefixedNode (m: ParseMode) : Parser =
fun (stream: CharStream<_>) ->
match (stream.Peek(), m) with
@@ -450,7 +469,7 @@ module private MfmParser =
| ':', (Full | Inline | Simple) -> emojiCodeNode
| '~', (Full | Inline) when stream.Match "~~" -> strikeNode
| '[', (Full | Inline) -> linkNode
- | '<', (Full | Inline) -> choice [ plainNode; smallNode; italicTagNode; boldTagNode; urlNodeBrackets ]
+ | '<', (Full | Inline) -> choice inlineTagNodes
| '<', Simple when stream.Match "" -> plainNode
| '\\', (Full | Inline) when stream.Match "\\(" -> mathNode
| '$', (Full | Inline) when stream.Match "$[" -> fnNode
diff --git a/Iceshrimp.Tests/Parsing/MfmTests.cs b/Iceshrimp.Tests/Parsing/MfmTests.cs
index 34f9821a..685f5a4c 100644
--- a/Iceshrimp.Tests/Parsing/MfmTests.cs
+++ b/Iceshrimp.Tests/Parsing/MfmTests.cs
@@ -14,30 +14,73 @@ public class MfmTests
[TestMethod]
public void TestParseBoldItalic()
{
- List expected =
+ // @formatter:off
+ List expected123 =
[
new MfmItalicNode(ListModule.OfSeq([
new MfmTextNode("italic "),
- new MfmBoldNode(ListModule.OfSeq([new MfmTextNode("bold")])),
+ new MfmBoldNode(ListModule.OfSeq([new MfmTextNode("bold")]), InlineNodeType.Symbol),
new MfmTextNode(" italic")
- ]))
+ ]), InlineNodeType.Symbol)
];
+
+ List expected4 =
+ [
+ new MfmItalicNode(ListModule.OfSeq([
+ new MfmTextNode("italic "),
+ new MfmBoldNode(ListModule.OfSeq([new MfmTextNode("bold")]), InlineNodeType.HtmlTag),
+ new MfmTextNode(" italic")
+ ]), InlineNodeType.HtmlTag)
+ ];
+
+ List expected5 =
+ [
+ new MfmItalicNode(ListModule.OfSeq([
+ new MfmTextNode("italic "),
+ new MfmBoldNode(ListModule.OfSeq([new MfmTextNode("bold")]), InlineNodeType.Symbol),
+ new MfmTextNode(" italic")
+ ]), InlineNodeType.HtmlTag)
+ ];
+
+ List expected6 =
+ [
+ new MfmItalicNode(ListModule.OfSeq([
+ new MfmTextNode("italic "),
+ new MfmBoldNode(ListModule.OfSeq([new MfmTextNode("bold")]), InlineNodeType.HtmlTag),
+ new MfmTextNode(" italic")
+ ]), InlineNodeType.Symbol)
+ ];
+ // @formatter:on
- var res = Mfm.parse("*italic **bold** italic*").ToList();
- var resAlt = Mfm.parse("_italic **bold** italic_").ToList();
- var resAlt2 = Mfm.parse("_italic __bold__ italic_").ToList();
- var resAlt3 = Mfm.parse("italic bold italic").ToList();
- var resMixed = Mfm.parse("italic **bold** italic").ToList();
- var resMixedAlt = Mfm.parse("*italic bold italic*").ToList();
+ const string input = "*italic **bold** italic*";
+ const string input2 = "_italic **bold** italic_";
+ const string input3 = "_italic __bold__ italic_";
+ const string input4 = "italic bold italic";
+ const string input5 = "italic **bold** italic";
+ const string input6 = "*italic bold italic*";
+
+ var res = Mfm.parse(input).ToList();
+ var res2 = Mfm.parse(input2).ToList();
+ var res3 = Mfm.parse(input3).ToList();
+ var res4 = Mfm.parse(input4).ToList();
+ var res5 = Mfm.parse(input5).ToList();
+ var res6 = Mfm.parse(input6).ToList();
AssertionOptions.FormattingOptions.MaxDepth = 100;
- res.Should().Equal(expected, MfmNodeEqual);
- resAlt.Should().Equal(expected, MfmNodeEqual);
- resAlt2.Should().Equal(expected, MfmNodeEqual);
- resAlt3.Should().Equal(expected, MfmNodeEqual);
- resMixed.Should().Equal(expected, MfmNodeEqual);
- resMixedAlt.Should().Equal(expected, MfmNodeEqual);
+ res.Should().Equal(expected123, MfmNodeEqual);
+ res2.Should().Equal(expected123, MfmNodeEqual);
+ res3.Should().Equal(expected123, MfmNodeEqual);
+ res4.Should().Equal(expected4, MfmNodeEqual);
+ res5.Should().Equal(expected5, MfmNodeEqual);
+ res6.Should().Equal(expected6, MfmNodeEqual);
+
+ MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
+ MfmSerializer.Serialize(res2).Should().BeEquivalentTo(input);
+ MfmSerializer.Serialize(res3).Should().BeEquivalentTo(input);
+ MfmSerializer.Serialize(res4).Should().BeEquivalentTo(input4);
+ MfmSerializer.Serialize(res5).Should().BeEquivalentTo(input5);
+ MfmSerializer.Serialize(res6).Should().BeEquivalentTo(input6);
}
[TestMethod]
@@ -58,7 +101,7 @@ public class MfmTests
expected =
[
new MfmTextNode("test "),
- new MfmItalicNode(ListModule.OfSeq([new MfmTextNode("test")])),
+ new MfmItalicNode(ListModule.OfSeq([new MfmTextNode("test")]), InlineNodeType.Symbol),
new MfmTextNode("test")
];
@@ -66,6 +109,31 @@ public class MfmTests
Mfm.parse("test _test_test").ToList().Should().Equal(expected, MfmNodeEqual);
}
+ [TestMethod]
+ public void TestStrike()
+ {
+ const string input = "~~test~~";
+ const string input2 = "test";
+ List expected =
+ [
+ new MfmStrikeNode(ListModule.OfSeq([new MfmTextNode("test")]), InlineNodeType.Symbol)
+ ];
+
+ List expected2 =
+ [
+ new MfmStrikeNode(ListModule.OfSeq([new MfmTextNode("test")]), InlineNodeType.HtmlTag)
+ ];
+
+ var res = Mfm.parse(input).ToList();
+ var res2 = Mfm.parse(input2).ToList();
+
+ res.Should().Equal(expected, MfmNodeEqual);
+ res2.Should().Equal(expected2, MfmNodeEqual);
+
+ MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
+ MfmSerializer.Serialize(res2).Should().BeEquivalentTo(input2);
+ }
+
[TestMethod]
public void TestParseList()
{
@@ -511,6 +579,24 @@ public class MfmTests
{
case MfmTextNode textNode when ((MfmTextNode)b).Text != textNode.Text:
return false;
+ case MfmItalicNode ax:
+ {
+ var bx = (MfmItalicNode)b;
+ if (!bx.Type.Equals(ax.Type)) return false;
+ break;
+ }
+ case MfmBoldNode ax:
+ {
+ var bx = (MfmBoldNode)b;
+ if (!bx.Type.Equals(ax.Type)) return false;
+ break;
+ }
+ case MfmStrikeNode ax:
+ {
+ var bx = (MfmStrikeNode)b;
+ if (!bx.Type.Equals(ax.Type)) return false;
+ break;
+ }
case MfmMentionNode ax:
{
var bx = (MfmMentionNode)b;