Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
76b0eee
chore(clean-up): remove old wip tags
mydearxym Feb 7, 2021
72f3813
test(editor): paragraph and header basic test
mydearxym Feb 7, 2021
f527057
test(editor): re-org codebase by parse header part
mydearxym Feb 8, 2021
995e602
test(editor): parse workflow adjust
mydearxym Feb 9, 2021
80b8831
test(editor-parse): re-org dir structure based on blocks
mydearxym Feb 10, 2021
7392f79
test(editor-parse): clean up imports
mydearxym Feb 10, 2021
221c050
test(editor): add basic paragraph block test
mydearxym Feb 10, 2021
e074944
test(editor): using macro && clean up
mydearxym Feb 10, 2021
fd8831c
refactor(editor): re-org the editor json validte workflow
mydearxym Feb 15, 2021
b255880
refactor(editor): return map fmt when error raised
mydearxym Feb 15, 2021
ba9b230
fix(editor): utils
mydearxym Feb 15, 2021
efa82d5
fix(editor): error message alert
mydearxym Feb 15, 2021
dc5f0cf
fix(editor): add :lang to tursted_atoms
mydearxym Feb 16, 2021
0ea6c34
refactor(editor): use string-fmt map instead of atom-fmt
mydearxym Feb 16, 2021
50d6108
fix(editor): re-org validate workflow by using config
mydearxym Feb 16, 2021
39ff354
fix(editor): add list to validator workflow as general
mydearxym Feb 16, 2021
2ece8ea
refactor(editor): use common schema to validate editor-fmt json
mydearxym Feb 17, 2021
e244d89
refactor(editor): use macros to reduce similar code
mydearxym Feb 17, 2021
89c2e2a
refactor(editor): adjust guard name in schema matchers
mydearxym Feb 17, 2021
23085c8
refactor(editor): re-org / rename the schema validator
mydearxym Feb 18, 2021
bad9428
refactor(editor): adjust parent schema cast logic
mydearxym Feb 18, 2021
e8d940e
refactor(editor): validate_with logic re-org
mydearxym Feb 18, 2021
0d43e6a
refactor(editor): adjust xss test && clean up
mydearxym Feb 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .iex.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# see: https://github.com/blackode/elixir-tips#loading-project-module-aliases-iexexs
alias Helper.Converter.EditorToHTML
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,8 @@ test.watch.wip:
# test.watch not work now, see: https://github.com/lpil/mix-test.watch/issues/116
# mix test.watch --only wip --stale
test.watch.wip2:
mix test --listen-on-stdin --stale --trace --only wip2
mix test --listen-on-stdin --stale --only wip2
# mix test.watch --only wip2
mix test.watch --only wip2
test.watch.bug:
mix test.watch --only bug
test.report:
Expand Down
2 changes: 1 addition & 1 deletion lib/helper/converter/assets/delimiter_icons.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Helper.Converter.EditorToHtml.Assets.DelimiterIcons do
defmodule Helper.Converter.EditorToHTML.Assets.DelimiterIcons do
@moduledoc """
svg icons for delimiter block
NOTE: those svg should be sync with frontend svg
Expand Down
80 changes: 80 additions & 0 deletions lib/helper/converter/editor_to_html/header.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
defmodule Helper.Converter.EditorToHTML.Header do
@moduledoc """
parse editor.js's header block

see https://editorjs.io/
"""

# @behaviour Helper.Converter.EditorToHTML.Parser

defmacro __using__(_opts) do
quote do
alias Helper.Metric

@clazz Metric.Article.class_names(:html)

defp parse_block(%{
"type" => "header",
"data" =>
%{
"text" => text,
"level" => level,
"eyebrowTitle" => eyebrow_title,
"footerTitle" => footer_title
} = data
}) do
"""
<div class="#{@clazz.header.wrapper}">
<div class="#{@clazz.header.eyebrow_title}">#{eyebrow_title}</div>
<h#{level}>#{text}</h#{level}>
<div class="#{@clazz.header.footer_title}">#{footer_title}</div>
</div>
"""
end

defp parse_block(%{
"type" => "header",
"data" =>
%{
"text" => text,
"level" => level,
"eyebrowTitle" => eyebrow_title
} = data
}) do
"""
<div class="#{@clazz.header.wrapper}">
<div class="#{@clazz.header.eyebrow_title}">#{eyebrow_title}</div>
<h#{level}>#{text}</h#{level}>
</div>
"""
end

defp parse_block(%{
"type" => "header",
"data" =>
%{
"text" => text,
"level" => level,
"footerTitle" => footer_title
} = data
}) do
"""
<div class="#{@clazz.header.wrapper}">
<h#{level}>#{text}</h#{level}>
<div class="#{@clazz.header.footer_title}">#{footer_title}</div>
</div>
"""
end

defp parse_block(%{
"type" => "header",
"data" => %{
"text" => text,
"level" => level
}
}) do
"<h#{level}>#{text}</h#{level}>"
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,61 +1,58 @@
defmodule Helper.Converter.EditorToHtml do
# defmodule Helper.Converter.EditorToHTML.Parser do
# @moduledoc false

# # TODO: map should be editor_block
# @callback parse_block(editor_json :: Map.t()) :: String.t()
# end

defmodule Helper.Converter.EditorToHTML do
@moduledoc """
parse editor.js's json data to raw html and sanitize it

see https://editorjs.io/
"""
alias Helper.Converter.HtmlSanitizer
alias Helper.Converter.EditorToHtml.Assets
alias Helper.Utils

alias Assets.{DelimiterIcons}
use Helper.Converter.EditorToHTML.Header
use Helper.Converter.EditorToHTML.Paragraph
use Helper.Converter.EditorToHTML.List

alias Helper.Converter.EditorToHTML.Validator
alias Helper.Converter.{EditorToHTML, HtmlSanitizer}
alias Helper.{Metric, Utils}

alias EditorToHTML.Assets.{DelimiterIcons}

@html_class_prefix "cps-viewer"
@clazz Metric.Article.class_names(:html)

@spec to_html(binary | maybe_improper_list) :: false | {:ok, <<_::64, _::_*8>>}
def to_html(string) when is_binary(string) do
with {:ok, parsed} = string_to_json(string),
true <- valid_editor_data?(parsed) do
{:ok, _} <- Validator.is_valid(parsed) do
content =
Enum.reduce(parsed["blocks"], "", fn block, acc ->
clean_html = block |> parse_block |> HtmlSanitizer.sanitize()
acc <> clean_html
end)

{:ok, "<div class=\"#{@html_class_prefix}\">#{content}<div>"}
{:ok, "<div class=\"#{@clazz.viewer}\">#{content}<div>"}
end
end

@doc "used for markdown ast to editor"
def to_html(editor_blocks) when is_list(editor_blocks) do
content =
Enum.reduce(editor_blocks, "", fn block, acc ->
clean_html = block |> Utils.keys_to_strings() |> parse_block |> HtmlSanitizer.sanitize()
acc <> clean_html
end)

{:ok, "<div class=\"#{@html_class_prefix}\">#{content}<div>"}
end

# IO.inspect(data, label: "parse header")
defp parse_block(%{"type" => "header", "data" => data}) do
text = get_in(data, ["text"])
level = get_in(data, ["level"])

"<h#{level} class=\"#{@html_class_prefix}-header\">#{text}</h#{level}>"
{:ok, "<div class=\"#{@clazz.viewer}\">#{content}<div>"}
end

# IO.inspect(data, label: "parse paragraph")
defp parse_block(%{"type" => "paragraph", "data" => data}) do
text = get_in(data, ["text"])

"<p class=\"#{@html_class_prefix}-paragraph\">#{text}</p>"
end

# IO.inspect(data, label: "parse image")
defp parse_block(%{"type" => "image", "data" => data}) do
url = get_in(data, ["file", "url"])

"<div class=\"#{@html_class_prefix}-image\"><img src=\"#{url}\"></div>"
"<div class=\"#{@clazz.viewer}-image\"><img src=\"#{url}\"></div>"
# |> IO.inspect(label: "iamge ret")
end

Expand All @@ -77,7 +74,6 @@ defmodule Helper.Converter.EditorToHtml do
"<ol>#{content}</ol>"
end

# IO.inspect(items, label: "checklist items")
# TODO: add item class
defp parse_block(%{"type" => "checklist", "data" => %{"items" => items}}) do
content =
Expand All @@ -94,31 +90,31 @@ defmodule Helper.Converter.EditorToHtml do
end
end)

"<div class=\"#{@html_class_prefix}-checklist\">#{content}</div>"
"<div class=\"#{@clazz.viewer}-checklist\">#{content}</div>"
# |> IO.inspect(label: "jjj")
end

defp parse_block(%{"type" => "delimiter", "data" => %{"type" => type}}) do
svg_icon = DelimiterIcons.svg(type)

# TODO: left-wing, righ-wing staff
{:skip_sanitize, "<div class=\"#{@html_class_prefix}-delimiter\">#{svg_icon}</div>"}
{:skip_sanitize, "<div class=\"#{@clazz.viewer}-delimiter\">#{svg_icon}</div>"}
end

# IO.inspect(data, label: "parse linkTool")
# TODO: parse the link-card info
defp parse_block(%{"type" => "linkTool", "data" => data}) do
link = get_in(data, ["link"])

"<div class=\"#{@html_class_prefix}-linker\"><a href=\"#{link}\" target=\"_blank\">#{link}</a></div>"
"<div class=\"#{@clazz.viewer}-linker\"><a href=\"#{link}\" target=\"_blank\">#{link}</a></div>"
# |> IO.inspect(label: "linkTool ret")
end

# IO.inspect(data, label: "parse quote")
defp parse_block(%{"type" => "quote", "data" => data}) do
text = get_in(data, ["text"])

"<div class=\"#{@html_class_prefix}-quote\">#{text}</div>"
"<div class=\"#{@clazz.viewer}-quote\">#{text}</div>"
# |> IO.inspect(label: "quote ret")
end

Expand All @@ -132,18 +128,8 @@ defmodule Helper.Converter.EditorToHtml do
end

defp parse_block(_block) do
# IO.puts("[unknow block]")
"[unknow block]"
"<div class=\"#{@clazz.unknow_block}\">[unknow block]</div>"
end

def string_to_json(string), do: Jason.decode(string)

defp valid_editor_data?(map) when is_map(map) do
Map.has_key?(map, "time") and
Map.has_key?(map, "version") and
Map.has_key?(map, "blocks") and
is_list(map["blocks"]) and
is_binary(map["version"]) and
is_integer(map["time"])
end
end
Loading