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