From d9ffd99e4d3805187dc9bd2b78f72e7e5ef461c6 Mon Sep 17 00:00:00 2001 From: Ilja Date: Sun, 24 Jul 2022 16:31:52 +0200 Subject: [PATCH] Convert a tree to HTML + formatting + docs --- README.md | 50 +++-- lib/mfm_parser.ex | 196 +++++++++++++++++- lib/node/mfm/bounce.ex | 2 +- lib/node/mfm/flip.ex | 2 +- lib/node/mfm/font.ex | 2 +- lib/node/mfm/jelly.ex | 2 +- lib/node/mfm/jump.ex | 2 +- lib/node/mfm/rainbow.ex | 2 +- lib/node/mfm/rotate.ex | 2 +- lib/node/mfm/shake.ex | 2 +- lib/node/mfm/tada.ex | 2 +- lib/node/mfm/twitch.ex | 2 +- lib/node/mfm/x.ex | 2 +- lib/token/mfm.ex | 2 +- lib/token/mfm/close.ex | 2 +- lib/token/mfm/open.ex | 2 +- lib/token/newline.ex | 2 +- lib/token/text.ex | 2 +- test/mfm_parser_test.exs | 434 ++++++++++++++++++++++++++++++++++++++- test/parser_test.exs | 4 +- test/token/mfm_test.exs | 1 + 21 files changed, 682 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 235759b..ee95d8e 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,43 @@ # MfmParser -**TODO: Add description** +A simple parser for [Misskey Flavoured Markdown](https://github.com/misskey-dev/mfm.js/). -## Installation +It only parses the MFM specific syntax of the form $[name.params content] and newlines. +That means that it doesn't parse links, usernames, HTML, Markdown or Katex. -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `mfm_parser` to your list of dependencies in `mix.exs`: +The Parser returns a tree, which looks like -```elixir -def deps do - [ - {:mfm_parser, "~> 0.1.0"} - ] -end -``` + [ + %MfmParser.Text{ + props: %{ + text: "it's not chocolatine, it's " + } + }, + %MfmParser.MFM.Twitch{ + props: %{ + speed: "0.2s" + }, + children: [ + %MfmParser.Text{ + props: %{ + text: "pain au chocolat" + } + } + ] + } + ] -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at . +You can also convert the tree into HTML. + +## Examples + + iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") + [ + %MfmParser.Node.MFM.Twitch{ + children: [%MfmParser.Node.Text{props: %{text: "🍮"}}], + props: %{speed: "5s"} + } + ] + iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.to_html() + "🍮" diff --git a/lib/mfm_parser.ex b/lib/mfm_parser.ex index 59ade44..ded6af3 100644 --- a/lib/mfm_parser.ex +++ b/lib/mfm_parser.ex @@ -1,4 +1,7 @@ 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). @@ -17,7 +20,196 @@ defmodule MfmParser do props: %{speed: "5s"} } ] - iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.Converter.to_html() - "🍮" + iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.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/lib/node/mfm/bounce.ex b/lib/node/mfm/bounce.ex index 44ddc0f..b439a2a 100644 --- a/lib/node/mfm/bounce.ex +++ b/lib/node/mfm/bounce.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Bounce do - defstruct props: %{ speed: "0.75s" }, children: [] + defstruct props: %{speed: "0.75s"}, children: [] end diff --git a/lib/node/mfm/flip.ex b/lib/node/mfm/flip.ex index 59fbc85..917bd57 100644 --- a/lib/node/mfm/flip.ex +++ b/lib/node/mfm/flip.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Flip do - defstruct props: %{ v: false, h: false }, children: [] + defstruct props: %{v: false, h: false}, children: [] end diff --git a/lib/node/mfm/font.ex b/lib/node/mfm/font.ex index e18f80a..2ff6ecb 100644 --- a/lib/node/mfm/font.ex +++ b/lib/node/mfm/font.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Font do - defstruct props: %{ font: nil }, children: [] + defstruct props: %{font: nil}, children: [] end diff --git a/lib/node/mfm/jelly.ex b/lib/node/mfm/jelly.ex index e3e5561..764f414 100644 --- a/lib/node/mfm/jelly.ex +++ b/lib/node/mfm/jelly.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Jelly do - defstruct props: %{ speed: "1s" }, children: [] + defstruct props: %{speed: "1s"}, children: [] end diff --git a/lib/node/mfm/jump.ex b/lib/node/mfm/jump.ex index e65bb5e..87665e6 100644 --- a/lib/node/mfm/jump.ex +++ b/lib/node/mfm/jump.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Jump do - defstruct props: %{ speed: "0.75s" }, children: [] + defstruct props: %{speed: "0.75s"}, children: [] end diff --git a/lib/node/mfm/rainbow.ex b/lib/node/mfm/rainbow.ex index 51042a6..fb33626 100644 --- a/lib/node/mfm/rainbow.ex +++ b/lib/node/mfm/rainbow.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Rainbow do - defstruct props: %{ speed: "1s" }, children: [] + defstruct props: %{speed: "1s"}, children: [] end diff --git a/lib/node/mfm/rotate.ex b/lib/node/mfm/rotate.ex index 8b745ae..03c14d5 100644 --- a/lib/node/mfm/rotate.ex +++ b/lib/node/mfm/rotate.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Rotate do - defstruct props: %{}, children: [] + defstruct props: %{}, children: [] end diff --git a/lib/node/mfm/shake.ex b/lib/node/mfm/shake.ex index 44aa5c7..4ab0a8a 100644 --- a/lib/node/mfm/shake.ex +++ b/lib/node/mfm/shake.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Shake do - defstruct props: %{ speed: "0.5s" }, children: [] + defstruct props: %{speed: "0.5s"}, children: [] end diff --git a/lib/node/mfm/tada.ex b/lib/node/mfm/tada.ex index 857e5a7..d47d52d 100644 --- a/lib/node/mfm/tada.ex +++ b/lib/node/mfm/tada.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Tada do - defstruct props: %{ speed: "1s" }, children: [] + defstruct props: %{speed: "1s"}, children: [] end diff --git a/lib/node/mfm/twitch.ex b/lib/node/mfm/twitch.ex index 87b9a4c..92b7b61 100644 --- a/lib/node/mfm/twitch.ex +++ b/lib/node/mfm/twitch.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.Twitch do - defstruct props: %{ speed: "0.5s" }, children: [] + defstruct props: %{speed: "0.5s"}, children: [] end diff --git a/lib/node/mfm/x.ex b/lib/node/mfm/x.ex index 80040fd..7de5024 100644 --- a/lib/node/mfm/x.ex +++ b/lib/node/mfm/x.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Node.MFM.X do - defstruct props: %{ size: nil }, children: [] + defstruct props: %{size: nil}, children: [] end diff --git a/lib/token/mfm.ex b/lib/token/mfm.ex index 02cedfe..864850e 100644 --- a/lib/token/mfm.ex +++ b/lib/token/mfm.ex @@ -1,7 +1,7 @@ defmodule MfmParser.Token.MFM do def to_props(opts_string) when is_binary(opts_string) do if opts_string =~ "." do - Regex.replace(~r/^.*\./u, opts_string, "") + Regex.replace(~r/^.*?\./u, opts_string, "") |> String.trim() |> String.split(",") |> Enum.reduce(%{}, fn opt, acc -> diff --git a/lib/token/mfm/close.ex b/lib/token/mfm/close.ex index 91e5eca..67c5d4c 100644 --- a/lib/token/mfm/close.ex +++ b/lib/token/mfm/close.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Token.MFM.Close do - defstruct content: "" + defstruct content: "" end diff --git a/lib/token/mfm/open.ex b/lib/token/mfm/open.ex index 8c3720b..603b93b 100644 --- a/lib/token/mfm/open.ex +++ b/lib/token/mfm/open.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Token.MFM.Open do - defstruct content: "" + defstruct content: "" end diff --git a/lib/token/newline.ex b/lib/token/newline.ex index d45ca15..b31774d 100644 --- a/lib/token/newline.ex +++ b/lib/token/newline.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Token.Newline do - defstruct content: "" + defstruct content: "" end diff --git a/lib/token/text.ex b/lib/token/text.ex index f19efa5..a9389e5 100644 --- a/lib/token/text.ex +++ b/lib/token/text.ex @@ -1,3 +1,3 @@ defmodule MfmParser.Token.Text do - defstruct content: "" + defstruct content: "" end diff --git a/test/mfm_parser_test.exs b/test/mfm_parser_test.exs index 71d2a09..a1d6e3a 100644 --- a/test/mfm_parser_test.exs +++ b/test/mfm_parser_test.exs @@ -1,4 +1,436 @@ defmodule MfmParserTest do use ExUnit.Case - # doctest MfmParser + 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 diff --git a/test/parser_test.exs b/test/parser_test.exs index 82e7173..da0a65c 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -420,12 +420,12 @@ defmodule MfmParser.ParserTest do } ] - input_speed = "$[twitch.speed=20s ]" + input_speed = "$[twitch.speed=0.2s ]" output_speed = [ %MfmParser.Node.MFM.Twitch{ props: %{ - speed: "20s" + speed: "0.2s" }, children: [] } diff --git a/test/token/mfm_test.exs b/test/token/mfm_test.exs index 41caeb7..907d481 100644 --- a/test/token/mfm_test.exs +++ b/test/token/mfm_test.exs @@ -5,6 +5,7 @@ defmodule MfmParser.MFMTest do test "it returns speed in the list of parameters" do assert %{speed: "5s"} = MFM.to_props("$[blabla.speed=5s") + assert %{speed: "0.5s"} = MFM.to_props("$[blabla.speed=0.5s") end test "it returns v and h in the list of parameters" do