diff --git a/README.md b/README.md
index ee95d8e..3ecfd3a 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,6 @@ The Parser returns a tree, which looks like
}
]
-
You can also convert the tree into HTML.
## Examples
diff --git a/lib/encoder.ex b/lib/encoder.ex
new file mode 100644
index 0000000..3c3f161
--- /dev/null
+++ b/lib/encoder.ex
@@ -0,0 +1,193 @@
+defmodule MfmParser.Encoder do
+ alias MfmParser.Parser
+ alias MfmParser.Node
+
+ def to_html(tree) when is_list(tree) do
+ {html, styles} = to_html_styles(tree)
+
+ html |> append_styles_when_not_empty(styles)
+ end
+
+ def to_html(input) when is_binary(input) do
+ Parser.parse(input) |> to_html()
+ end
+
+ defp to_html_styles(tree, _style \\ []) do
+ tree
+ |> Enum.reduce({"", []}, fn node, {html, styles} ->
+ case node do
+ %Node.Text{} ->
+ {html <> node.props.text, styles}
+
+ %Node.Newline{} ->
+ {html <> node.props.text, styles}
+
+ %Node.MFM.Flip{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ case node.props do
+ %{v: true, h: true} ->
+ {html <>
+ "#{html_child}",
+ styles}
+
+ %{v: true} ->
+ {html <>
+ "#{html_child}",
+ styles}
+
+ _ ->
+ {html <>
+ "#{html_child}",
+ styles ++ styles_child}
+ end
+
+ %Node.MFM.Font{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++ styles_child}
+
+ %Node.MFM.X{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++ styles_child}
+
+ %Node.MFM.Blur{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <> "#{html_child}",
+ styles ++
+ [
+ "._mfm_blur_ { filter: blur(6px); transition: filter .3s; } ._mfm_blur_:hover { filter: blur(0px); }"
+ ] ++ styles_child}
+
+ %Node.MFM.Jelly{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-rubberBand { 0% { transform:scaleZ(1) } 30% { transform:scale3d(1.25,.75,1) } 40% { transform:scale3d(.75,1.25,1) } 50% { transform:scale3d(1.15,.85,1) } 65% { transform:scale3d(.95,1.05,1) } 75% { transform:scale3d(1.05,.95,1) } to { transform:scaleZ(1) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Tada{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes tada { 0% { transform: scaleZ(1); } 10%, 20% { transform: scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg); } 30%, 50%, 70%, 90% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg); } 40%, 60%, 80% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg); } 100% { transform: scaleZ(1); }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Jump{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-jump { 0% { transform:translateY(0) } 25% { transform:translateY(-16px) } 50% { transform:translateY(0) } 75% { transform:translateY(-8px) } to { transform:translateY(0) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Bounce{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-bounce { 0% { transform:translateY(0) scale(1) } 25% { transform:translateY(-16px) scale(1) } 50% { transform:translateY(0) scale(1) } 75% { transform:translateY(0) scale(1.5,.75) } to { transform:translateY(0) scale(1) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Spin{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ stylemap = %{
+ "mfm-spin" =>
+ "@keyframes mfm-spin { 0% { transform:rotate(0) } to { transform:rotate(360deg) }}",
+ "mfm-spinX" =>
+ "@keyframes mfm-spinX { 0% { transform:perspective(128px) rotateX(0) } to { transform:perspective(128px) rotateX(360deg) }}",
+ "mfm-spinY" =>
+ "@keyframes mfm-spinY { 0% { transform:perspective(128px) rotateY(0) } to { transform:perspective(128px) rotateY(360deg) }}"
+ }
+
+ {html <>
+ "#{html_child}",
+ styles ++ [Map.get(stylemap, node.props.keyframes_name, "")] ++ styles_child}
+
+ %Node.MFM.Shake{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-shake { 0% { transform:translate(-3px,-1px) rotate(-8deg) } 5% { transform:translateY(-1px) rotate(-10deg) } 10% { transform:translate(1px,-3px) rotate(0) } 15% { transform:translate(1px,1px) rotate(11deg) } 20% { transform:translate(-2px,1px) rotate(1deg) } 25% { transform:translate(-1px,-2px) rotate(-2deg) } 30% { transform:translate(-1px,2px) rotate(-3deg) } 35% { transform:translate(2px,1px) rotate(6deg) } 40% { transform:translate(-2px,-3px) rotate(-9deg) } 45% { transform:translateY(-1px) rotate(-12deg) } 50% { transform:translate(1px,2px) rotate(10deg) } 55% { transform:translateY(-3px) rotate(8deg) } 60% { transform:translate(1px,-1px) rotate(8deg) } 65% { transform:translateY(-1px) rotate(-7deg) } 70% { transform:translate(-1px,-3px) rotate(6deg) } 75% { transform:translateY(-2px) rotate(4deg) } 80% { transform:translate(-2px,-1px) rotate(3deg) } 85% { transform:translate(1px,-3px) rotate(-10deg) } 90% { transform:translate(1px) rotate(3deg) } 95% { transform:translate(-2px) rotate(-3deg) } to { transform:translate(2px,1px) rotate(2deg) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Twitch{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-twitch { 0% { transform:translate(7px,-2px) } 5% { transform:translate(-3px,1px) } 10% { transform:translate(-7px,-1px) } 15% { transform:translateY(-1px) } 20% { transform:translate(-8px,6px) } 25% { transform:translate(-4px,-3px) } 30% { transform:translate(-4px,-6px) } 35% { transform:translate(-8px,-8px) } 40% { transform:translate(4px,6px) } 45% { transform:translate(-3px,1px) } 50% { transform:translate(2px,-10px) } 55% { transform:translate(-7px) } 60% { transform:translate(-2px,4px) } 65% { transform:translate(3px,-8px) } 70% { transform:translate(6px,7px) } 75% { transform:translate(-7px,-2px) } 80% { transform:translate(-7px,-8px) } 85% { transform:translate(9px,3px) } 90% { transform:translate(-3px,-2px) } 95% { transform:translate(-10px,2px) } to { transform:translate(-2px,-6px) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Rainbow{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-rainbow { 0% { filter:hue-rotate(0deg) contrast(150%) saturate(150%) } to { filter:hue-rotate(360deg) contrast(150%) saturate(150%) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Sparkle{} ->
+ # TODO: This is not how Misskey does it and should be changed to make it work like Misskey.
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++
+ [
+ "@keyframes mfm-sparkle { 0% { filter: brightness(100%) } to { filter: brightness(300%) }}"
+ ] ++ styles_child}
+
+ %Node.MFM.Rotate{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}",
+ styles ++ styles_child}
+
+ %Node.MFM.Undefined{} ->
+ {html_child, styles_child} = to_html_styles(node.children)
+
+ {html <>
+ "#{html_child}", styles ++ styles_child}
+
+ _ ->
+ {html, styles}
+ end
+ end)
+ end
+
+ defp append_styles_when_not_empty(html, []) do
+ html
+ end
+
+ defp append_styles_when_not_empty(html, styles) do
+ styles = styles |> Enum.uniq() |> Enum.reduce("", fn style, acc -> style <> acc end)
+
+ html <> ""
+ end
+end
diff --git a/lib/mfm_parser.ex b/lib/mfm_parser.ex
index ded6af3..3d21a11 100644
--- a/lib/mfm_parser.ex
+++ b/lib/mfm_parser.ex
@@ -1,7 +1,4 @@
defmodule MfmParser do
- alias MfmParser.Parser
- alias MfmParser.Node
-
@moduledoc """
`MfmParser` is a parser for [Misskey Flavoured Markdown](https://mk.nixnet.social/mfm-cheat-sheet).
@@ -20,196 +17,7 @@ defmodule MfmParser do
props: %{speed: "5s"}
}
]
- iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.to_html()
+ iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.Encoder.to_html()
"🍮"
"""
-
- def to_html(tree) when is_list(tree) do
- {html, styles} = to_html_styles(tree)
-
- html |> append_styles_when_not_empty(styles)
- end
-
- def to_html(input) when is_binary(input) do
- Parser.parse(input) |> to_html()
- end
-
- def to_html_styles(tree, _style \\ []) do
- tree
- |> Enum.reduce({"", []}, fn node, {html, styles} ->
- case node do
- %Node.Text{} ->
- {html <> node.props.text, styles}
-
- %Node.Newline{} ->
- {html <> node.props.text, styles}
-
- %Node.MFM.Flip{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- case node.props do
- %{v: true, h: true} ->
- {html <>
- "#{html_child}",
- styles}
-
- %{v: true} ->
- {html <>
- "#{html_child}",
- styles}
-
- _ ->
- {html <>
- "#{html_child}",
- styles ++ styles_child}
- end
-
- %Node.MFM.Font{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++ styles_child}
-
- %Node.MFM.X{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++ styles_child}
-
- %Node.MFM.Blur{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <> "#{html_child}",
- styles ++
- [
- "._mfm_blur_ { filter: blur(6px); transition: filter .3s; } ._mfm_blur_:hover { filter: blur(0px); }"
- ] ++ styles_child}
-
- %Node.MFM.Jelly{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-rubberBand { 0% { transform:scaleZ(1) } 30% { transform:scale3d(1.25,.75,1) } 40% { transform:scale3d(.75,1.25,1) } 50% { transform:scale3d(1.15,.85,1) } 65% { transform:scale3d(.95,1.05,1) } 75% { transform:scale3d(1.05,.95,1) } to { transform:scaleZ(1) }}"
- ] ++ styles_child}
-
- %Node.MFM.Tada{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes tada { 0% { transform: scaleZ(1); } 10%, 20% { transform: scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg); } 30%, 50%, 70%, 90% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg); } 40%, 60%, 80% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg); } 100% { transform: scaleZ(1); }}"
- ] ++ styles_child}
-
- %Node.MFM.Jump{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-jump { 0% { transform:translateY(0) } 25% { transform:translateY(-16px) } 50% { transform:translateY(0) } 75% { transform:translateY(-8px) } to { transform:translateY(0) }}"
- ] ++ styles_child}
-
- %Node.MFM.Bounce{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-bounce { 0% { transform:translateY(0) scale(1) } 25% { transform:translateY(-16px) scale(1) } 50% { transform:translateY(0) scale(1) } 75% { transform:translateY(0) scale(1.5,.75) } to { transform:translateY(0) scale(1) }}"
- ] ++ styles_child}
-
- %Node.MFM.Spin{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- stylemap = %{
- "mfm-spin" =>
- "@keyframes mfm-spin { 0% { transform:rotate(0) } to { transform:rotate(360deg) }}",
- "mfm-spinX" =>
- "@keyframes mfm-spinX { 0% { transform:perspective(128px) rotateX(0) } to { transform:perspective(128px) rotateX(360deg) }}",
- "mfm-spinY" =>
- "@keyframes mfm-spinY { 0% { transform:perspective(128px) rotateY(0) } to { transform:perspective(128px) rotateY(360deg) }}"
- }
-
- {html <>
- "#{html_child}",
- styles ++ [Map.get(stylemap, node.props.keyframes_name, "")] ++ styles_child}
-
- %Node.MFM.Shake{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-shake { 0% { transform:translate(-3px,-1px) rotate(-8deg) } 5% { transform:translateY(-1px) rotate(-10deg) } 10% { transform:translate(1px,-3px) rotate(0) } 15% { transform:translate(1px,1px) rotate(11deg) } 20% { transform:translate(-2px,1px) rotate(1deg) } 25% { transform:translate(-1px,-2px) rotate(-2deg) } 30% { transform:translate(-1px,2px) rotate(-3deg) } 35% { transform:translate(2px,1px) rotate(6deg) } 40% { transform:translate(-2px,-3px) rotate(-9deg) } 45% { transform:translateY(-1px) rotate(-12deg) } 50% { transform:translate(1px,2px) rotate(10deg) } 55% { transform:translateY(-3px) rotate(8deg) } 60% { transform:translate(1px,-1px) rotate(8deg) } 65% { transform:translateY(-1px) rotate(-7deg) } 70% { transform:translate(-1px,-3px) rotate(6deg) } 75% { transform:translateY(-2px) rotate(4deg) } 80% { transform:translate(-2px,-1px) rotate(3deg) } 85% { transform:translate(1px,-3px) rotate(-10deg) } 90% { transform:translate(1px) rotate(3deg) } 95% { transform:translate(-2px) rotate(-3deg) } to { transform:translate(2px,1px) rotate(2deg) }}"
- ] ++ styles_child}
-
- %Node.MFM.Twitch{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-twitch { 0% { transform:translate(7px,-2px) } 5% { transform:translate(-3px,1px) } 10% { transform:translate(-7px,-1px) } 15% { transform:translateY(-1px) } 20% { transform:translate(-8px,6px) } 25% { transform:translate(-4px,-3px) } 30% { transform:translate(-4px,-6px) } 35% { transform:translate(-8px,-8px) } 40% { transform:translate(4px,6px) } 45% { transform:translate(-3px,1px) } 50% { transform:translate(2px,-10px) } 55% { transform:translate(-7px) } 60% { transform:translate(-2px,4px) } 65% { transform:translate(3px,-8px) } 70% { transform:translate(6px,7px) } 75% { transform:translate(-7px,-2px) } 80% { transform:translate(-7px,-8px) } 85% { transform:translate(9px,3px) } 90% { transform:translate(-3px,-2px) } 95% { transform:translate(-10px,2px) } to { transform:translate(-2px,-6px) }}"
- ] ++ styles_child}
-
- %Node.MFM.Rainbow{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-rainbow { 0% { filter:hue-rotate(0deg) contrast(150%) saturate(150%) } to { filter:hue-rotate(360deg) contrast(150%) saturate(150%) }}"
- ] ++ styles_child}
-
- %Node.MFM.Sparkle{} ->
- # TODO: This is not how Misskey does it and should be changed to make it work like Misskey.
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++
- [
- "@keyframes mfm-sparkle { 0% { filter: brightness(100%) } to { filter: brightness(300%) }}"
- ] ++ styles_child}
-
- %Node.MFM.Rotate{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}",
- styles ++ styles_child}
-
- %Node.MFM.Undefined{} ->
- {html_child, styles_child} = to_html_styles(node.children)
-
- {html <>
- "#{html_child}", styles ++ styles_child}
-
- _ ->
- {html, styles}
- end
- end)
- end
-
- defp append_styles_when_not_empty(html, []) do
- html
- end
-
- defp append_styles_when_not_empty(html, styles) do
- styles = styles |> Enum.uniq() |> Enum.reduce("", fn style, acc -> style <> acc end)
-
- html <> ""
- end
end
diff --git a/test/encoder_test.exs b/test/encoder_test.exs
new file mode 100644
index 0000000..988ff9b
--- /dev/null
+++ b/test/encoder_test.exs
@@ -0,0 +1,431 @@
+defmodule MfmParser.EncoderTest do
+ use ExUnit.Case
+
+ alias MfmParser.Encoder
+ alias MfmParser.Node
+
+ describe "to_html" do
+ test "it handles text" do
+ input_tree = [%Node.Text{props: %{text: "chocolatine"}}]
+
+ expected = "chocolatine"
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles newlines" do
+ input_tree = [%Node.Newline{props: %{text: "\n"}}]
+
+ expected = "\n"
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles flip" do
+ input_tree = [
+ %Node.MFM.Flip{
+ children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}]
+ }
+ ]
+
+ input_tree_v = [
+ %Node.MFM.Flip{
+ children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
+ props: %{v: true}
+ }
+ ]
+
+ input_tree_h_v = [
+ %Node.MFM.Flip{
+ children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
+ props: %{v: true, h: true}
+ }
+ ]
+
+ expected =
+ ~s[Misskey expands the world of the Fediverse]
+
+ expected_v =
+ ~s[Misskey expands the world of the Fediverse]
+
+ expected_h_v =
+ ~s[Misskey expands the world of the Fediverse]
+
+ assert Encoder.to_html(input_tree) == expected
+ assert Encoder.to_html(input_tree_v) == expected_v
+ assert Encoder.to_html(input_tree_h_v) == expected_h_v
+ end
+
+ test "it handles font" do
+ input_tree = [
+ %Node.MFM.Font{
+ children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
+ props: %{font: "fantasy"}
+ }
+ ]
+
+ expected =
+ ~s[Misskey expands the world of the Fediverse]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles x" do
+ input_tree = [
+ %Node.MFM.X{
+ children: [%Node.Text{props: %{text: "🍮"}}],
+ props: %{size: "400%"}
+ }
+ ]
+
+ expected = ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles blur" do
+ input_tree = [
+ %Node.MFM.Blur{
+ children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}]
+ }
+ ]
+
+ expected =
+ ~s[Misskey expands the world of the Fediverse]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles jelly" do
+ input_tree = [
+ %Node.MFM.Jelly{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles tada" do
+ input_tree = [
+ %Node.MFM.Tada{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles jump" do
+ input_tree = [
+ %Node.MFM.Jump{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles bounce" do
+ input_tree = [
+ %Node.MFM.Bounce{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles spin" do
+ input_tree_spin_reverse = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spin", direction: "reverse", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spinx_reverse = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spinX", direction: "reverse", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spiny_reverse = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spinY", direction: "reverse", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spin_alternate = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spin", direction: "alternate", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spinx_alternate = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spinX", direction: "alternate", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spiny_alternate = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spinY", direction: "alternate", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spin_normal = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spin", direction: "normal", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spinx_normal = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spinX", direction: "normal", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ input_tree_spiny_normal = [
+ %Node.MFM.Spin{
+ props: %{keyframes_name: "mfm-spinY", direction: "normal", speed: "1.5s"},
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected_tree_spin_reverse =
+ ~s[🍮]
+
+ expected_tree_spinx_reverse =
+ ~s[🍮]
+
+ expected_tree_spiny_reverse =
+ ~s[🍮]
+
+ expected_tree_spin_alternate =
+ ~s[🍮]
+
+ expected_tree_spinx_alternate =
+ ~s[🍮]
+
+ expected_tree_spiny_alternate =
+ ~s[🍮]
+
+ expected_tree_spin_normal =
+ ~s[🍮]
+
+ expected_tree_spinx_normal =
+ ~s[🍮]
+
+ expected_tree_spiny_normal =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree_spin_reverse) == expected_tree_spin_reverse
+ assert Encoder.to_html(input_tree_spinx_reverse) == expected_tree_spinx_reverse
+ assert Encoder.to_html(input_tree_spiny_reverse) == expected_tree_spiny_reverse
+
+ assert Encoder.to_html(input_tree_spin_alternate) == expected_tree_spin_alternate
+ assert Encoder.to_html(input_tree_spinx_alternate) == expected_tree_spinx_alternate
+ assert Encoder.to_html(input_tree_spiny_alternate) == expected_tree_spiny_alternate
+
+ assert Encoder.to_html(input_tree_spin_normal) == expected_tree_spin_normal
+ assert Encoder.to_html(input_tree_spinx_normal) == expected_tree_spinx_normal
+ assert Encoder.to_html(input_tree_spiny_normal) == expected_tree_spiny_normal
+ end
+
+ test "it handles shake" do
+ input_tree = [
+ %Node.MFM.Shake{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles twitch" do
+ input_tree = [
+ %Node.MFM.Twitch{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles rainbow" do
+ input_tree = [
+ %Node.MFM.Rainbow{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles sparkle" do
+ # TODO: This is not how Misskey does it and should be changed to make it work like Misskey.
+ input_tree = [
+ %Node.MFM.Sparkle{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles rotate" do
+ input_tree = [
+ %Node.MFM.Rotate{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles unsuported formats" do
+ input_tree = [
+ %Node.MFM.Undefined{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected = ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles multpile nodes on the same level" do
+ input_tree = [
+ %Node.MFM.Rotate{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ },
+ %Node.Text{props: %{text: "pain au chocolat"}},
+ %Node.MFM.Font{
+ children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
+ props: %{font: "fantasy"}
+ }
+ ]
+
+ expected =
+ ~s[🍮pain au chocolatMisskey expands the world of the Fediverse]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles nesting" do
+ input_tree = [
+ %Node.MFM.Rotate{
+ children: [
+ %Node.MFM.Font{
+ children: [%Node.Text{props: %{text: "🍮"}}],
+ props: %{font: "fantasy"}
+ }
+ ]
+ }
+ ]
+
+ expected =
+ ~s[🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it shouldn't have duplicate styles" do
+ input_tree = [
+ %Node.MFM.Sparkle{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ },
+ %Node.MFM.Sparkle{
+ children: [%Node.Text{props: %{text: "🍮"}}]
+ }
+ ]
+
+ expected =
+ ~s[🍮🍮]
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it handles complex nesting of nodes" do
+ input_tree = [
+ %MfmParser.Node.Text{props: %{text: "It's not "}},
+ %MfmParser.Node.MFM.Twitch{
+ children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}],
+ props: %{speed: "0.2s"}
+ },
+ %MfmParser.Node.Newline{props: %{text: "\n"}},
+ %MfmParser.Node.Text{props: %{text: "it's "}},
+ %MfmParser.Node.MFM.X{
+ children: [
+ %MfmParser.Node.MFM.Spin{
+ children: [%MfmParser.Node.Text{props: %{text: "pain"}}],
+ props: %{direction: "normal", keyframes_name: "mfm-spin", speed: "1s"}
+ },
+ %MfmParser.Node.Text{props: %{text: " "}},
+ %MfmParser.Node.MFM.Rainbow{
+ children: [%MfmParser.Node.Text{props: %{text: "au"}}],
+ props: %{speed: "2s"}
+ },
+ %MfmParser.Node.Text{props: %{text: " "}},
+ %MfmParser.Node.MFM.Jump{
+ children: [%MfmParser.Node.Text{props: %{text: "chocolat"}}],
+ props: %{speed: "0.5s"}
+ }
+ ],
+ props: %{size: "600%"}
+ }
+ ]
+
+ expected =
+ "It's not chocolatine\nit's pain au chocolat"
+
+ assert Encoder.to_html(input_tree) == expected
+ end
+
+ test "it should be able to go from mfm-text input to html output" do
+ input =
+ "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]"
+
+ expected =
+ "It's not chocolatine\nit's pain au chocolat"
+
+ assert Encoder.to_html(input) == expected
+ end
+ end
+end
diff --git a/test/mfm_parser_test.exs b/test/mfm_parser_test.exs
index a1d6e3a..6035485 100644
--- a/test/mfm_parser_test.exs
+++ b/test/mfm_parser_test.exs
@@ -1,436 +1,4 @@
defmodule MfmParserTest do
use ExUnit.Case
doctest MfmParser
-
- alias MfmParser.Node
-
- describe "to_html" do
- test "it handles text" do
- input_tree = [%Node.Text{props: %{text: "chocolatine"}}]
-
- expected = "chocolatine"
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles newlines" do
- input_tree = [%Node.Newline{props: %{text: "\n"}}]
-
- expected = "\n"
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles flip" do
- input_tree = [
- %Node.MFM.Flip{
- children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}]
- }
- ]
-
- input_tree_v = [
- %Node.MFM.Flip{
- children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
- props: %{v: true}
- }
- ]
-
- input_tree_h_v = [
- %Node.MFM.Flip{
- children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
- props: %{v: true, h: true}
- }
- ]
-
- expected =
- ~s[Misskey expands the world of the Fediverse]
-
- expected_v =
- ~s[Misskey expands the world of the Fediverse]
-
- expected_h_v =
- ~s[Misskey expands the world of the Fediverse]
-
- assert MfmParser.to_html(input_tree) == expected
- assert MfmParser.to_html(input_tree_v) == expected_v
- assert MfmParser.to_html(input_tree_h_v) == expected_h_v
- end
-
- test "it handles font" do
- input_tree = [
- %Node.MFM.Font{
- children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
- props: %{font: "fantasy"}
- }
- ]
-
- expected =
- ~s[Misskey expands the world of the Fediverse]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles x" do
- input_tree = [
- %Node.MFM.X{
- children: [%Node.Text{props: %{text: "🍮"}}],
- props: %{size: "400%"}
- }
- ]
-
- expected = ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles blur" do
- input_tree = [
- %Node.MFM.Blur{
- children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}]
- }
- ]
-
- expected =
- ~s[Misskey expands the world of the Fediverse]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles jelly" do
- input_tree = [
- %Node.MFM.Jelly{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles tada" do
- input_tree = [
- %Node.MFM.Tada{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles jump" do
- input_tree = [
- %Node.MFM.Jump{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles bounce" do
- input_tree = [
- %Node.MFM.Bounce{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles spin" do
- input_tree_spin_reverse = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spin", direction: "reverse", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spinx_reverse = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spinX", direction: "reverse", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spiny_reverse = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spinY", direction: "reverse", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spin_alternate = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spin", direction: "alternate", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spinx_alternate = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spinX", direction: "alternate", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spiny_alternate = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spinY", direction: "alternate", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spin_normal = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spin", direction: "normal", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spinx_normal = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spinX", direction: "normal", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- input_tree_spiny_normal = [
- %Node.MFM.Spin{
- props: %{keyframes_name: "mfm-spinY", direction: "normal", speed: "1.5s"},
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected_tree_spin_reverse =
- ~s[🍮]
-
- expected_tree_spinx_reverse =
- ~s[🍮]
-
- expected_tree_spiny_reverse =
- ~s[🍮]
-
- expected_tree_spin_alternate =
- ~s[🍮]
-
- expected_tree_spinx_alternate =
- ~s[🍮]
-
- expected_tree_spiny_alternate =
- ~s[🍮]
-
- expected_tree_spin_normal =
- ~s[🍮]
-
- expected_tree_spinx_normal =
- ~s[🍮]
-
- expected_tree_spiny_normal =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree_spin_reverse) == expected_tree_spin_reverse
- assert MfmParser.to_html(input_tree_spinx_reverse) == expected_tree_spinx_reverse
- assert MfmParser.to_html(input_tree_spiny_reverse) == expected_tree_spiny_reverse
-
- assert MfmParser.to_html(input_tree_spin_alternate) == expected_tree_spin_alternate
- assert MfmParser.to_html(input_tree_spinx_alternate) == expected_tree_spinx_alternate
- assert MfmParser.to_html(input_tree_spiny_alternate) == expected_tree_spiny_alternate
-
- assert MfmParser.to_html(input_tree_spin_normal) == expected_tree_spin_normal
- assert MfmParser.to_html(input_tree_spinx_normal) == expected_tree_spinx_normal
- assert MfmParser.to_html(input_tree_spiny_normal) == expected_tree_spiny_normal
- end
-
- test "it handles shake" do
- input_tree = [
- %Node.MFM.Shake{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles twitch" do
- input_tree = [
- %Node.MFM.Twitch{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles rainbow" do
- input_tree = [
- %Node.MFM.Rainbow{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles sparkle" do
- # TODO: This is not how Misskey does it and should be changed to make it work like Misskey.
- input_tree = [
- %Node.MFM.Sparkle{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles rotate" do
- input_tree = [
- %Node.MFM.Rotate{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles unsuported formats" do
- input_tree = [
- %Node.MFM.Undefined{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected = ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles multpile nodes on the same level" do
- input_tree = [
- %Node.MFM.Rotate{
- children: [%Node.Text{props: %{text: "🍮"}}]
- },
- %Node.Text{props: %{text: "pain au chocolat"}},
- %Node.MFM.Font{
- children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
- props: %{font: "fantasy"}
- }
- ]
-
- expected =
- ~s[🍮pain au chocolatMisskey expands the world of the Fediverse]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles nesting" do
- input_tree = [
- %Node.MFM.Rotate{
- children: [
- %Node.MFM.Font{
- children: [%Node.Text{props: %{text: "🍮"}}],
- props: %{font: "fantasy"}
- }
- ]
- }
- ]
-
- expected =
- ~s[🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it shouldn't have duplicate styles" do
- input_tree = [
- %Node.MFM.Sparkle{
- children: [%Node.Text{props: %{text: "🍮"}}]
- },
- %Node.MFM.Sparkle{
- children: [%Node.Text{props: %{text: "🍮"}}]
- }
- ]
-
- expected =
- ~s[🍮🍮]
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it handles complex nesting of nodes" do
- input_tree = [
- %MfmParser.Node.Text{props: %{text: "It's not "}},
- %MfmParser.Node.MFM.Twitch{
- children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}],
- props: %{speed: "0.2s"}
- },
- %MfmParser.Node.Newline{props: %{text: "\n"}},
- %MfmParser.Node.Text{props: %{text: "it's "}},
- %MfmParser.Node.MFM.X{
- children: [
- %MfmParser.Node.MFM.Spin{
- children: [%MfmParser.Node.Text{props: %{text: "pain"}}],
- props: %{direction: "normal", keyframes_name: "mfm-spin", speed: "1s"}
- },
- %MfmParser.Node.Text{props: %{text: " "}},
- %MfmParser.Node.MFM.Rainbow{
- children: [%MfmParser.Node.Text{props: %{text: "au"}}],
- props: %{speed: "2s"}
- },
- %MfmParser.Node.Text{props: %{text: " "}},
- %MfmParser.Node.MFM.Jump{
- children: [%MfmParser.Node.Text{props: %{text: "chocolat"}}],
- props: %{speed: "0.5s"}
- }
- ],
- props: %{size: "600%"}
- }
- ]
-
- expected =
- "It's not chocolatine\nit's pain au chocolat"
-
- assert MfmParser.to_html(input_tree) == expected
- end
-
- test "it should be able to go from mfm-text input to html output" do
- input =
- "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]"
-
- expected =
- "It's not chocolatine\nit's pain au chocolat"
-
- assert MfmParser.to_html(input) == expected
- end
-
- # I would like to have options
- # * as much as possible in the span vs only a class and everything in style
- # * with or without style
- #
- end
end