Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.

Commit f6159a7

Browse files
authored
feat(editor-export): uniq-id for each editor blocks (#293)
* feat(editor-export): add uid anchor_id for header * feat(editor-export): add uid anchor_id for list * feat(editor-export): add uid anchor_id for table * feat(editor-export): add uid anchor_id for quote * feat(editor-export): clean up wip2 tags * feat(editor-export): add tests for uid generator * test(editor-export): fix header test
1 parent a8f52d5 commit f6159a7

File tree

13 files changed

+251
-149
lines changed

13 files changed

+251
-149
lines changed

lib/helper/converter/editor_to_html/frags/header.ex

+13-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Helper.Converter.EditorToHTML.Frags.Header do
55
see https://editorjs.io/
66
"""
77
alias Helper.Types, as: T
8+
alias Helper.Utils
89

910
alias Helper.Converter.EditorToHTML.Class
1011

@@ -19,7 +20,9 @@ defmodule Helper.Converter.EditorToHTML.Frags.Header do
1920
eyebrow_class = @class["eyebrow_title"]
2021
footer_class = @class["footer_title"]
2122

22-
~s(<div class="#{wrapper_class}">
23+
anchor_id = Utils.uid(:html, data)
24+
25+
~s(<div id="#{anchor_id}" class="#{wrapper_class}">
2326
<div class="#{eyebrow_class}">#{eyebrow_title}</div>
2427
<h#{level} class="#{header_class}">#{text}</h#{level}>
2528
<div class="#{footer_class}">#{footer_title}</div>
@@ -33,7 +36,9 @@ defmodule Helper.Converter.EditorToHTML.Frags.Header do
3336
header_class = @class["header"]
3437
eyebrow_class = @class["eyebrow_title"]
3538

36-
~s(<div class="#{wrapper_class}">
39+
anchor_id = Utils.uid(:html, data)
40+
41+
~s(<div id="#{anchor_id}" class="#{wrapper_class}">
3742
<div class="#{eyebrow_class}">#{eyebrow_title}</div>
3843
<h#{level} class="#{header_class}">#{text}</h#{level}>
3944
</div>)
@@ -46,13 +51,16 @@ defmodule Helper.Converter.EditorToHTML.Frags.Header do
4651
header_class = @class["header"]
4752
footer_class = @class["footer_title"]
4853

49-
~s(<div class="#{wrapper_class}">
54+
anchor_id = Utils.uid(:html, data)
55+
~s(<div id="#{anchor_id}" class="#{wrapper_class}">
5056
<h#{level} class="#{header_class}">#{text}</h#{level}>
5157
<div class="#{footer_class}">#{footer_title}</div>
5258
</div>)
5359
end
5460

55-
def get(%{"text" => text, "level" => level}) do
56-
"<h#{level}>#{text}</h#{level}>"
61+
def get(%{"text" => text, "level" => level} = data) do
62+
anchor_id = Utils.uid(:html, data)
63+
64+
~s(<h#{level} id="#{anchor_id}">#{text}</h#{level}>)
5765
end
5866
end

lib/helper/converter/editor_to_html/frags/list.ex

+32-23
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ defmodule Helper.Converter.EditorToHTML.Frags.List do
1010
@class get_in(Class.article(), ["list"])
1111

1212
@spec get_item(:checklist | :unorder_list | :order_list, T.editor_list_item()) :: T.html()
13-
def get_item(:unorder_list, %{
14-
"hideLabel" => hide_label,
15-
"indent" => indent,
16-
"label" => label,
17-
"labelType" => label_type,
18-
"text" => text
19-
}) do
13+
def get_item(
14+
:unorder_list,
15+
%{
16+
"hideLabel" => hide_label,
17+
"indent" => indent,
18+
"label" => label,
19+
"labelType" => label_type,
20+
"text" => text
21+
}
22+
) do
2023
prefix_frag = frag(:unorder_list_prefix)
2124
label_frag = if hide_label, do: "", else: frag(:label, label_type, indent, label)
2225
text_frag = frag(:text, text)
@@ -31,14 +34,17 @@ defmodule Helper.Converter.EditorToHTML.Frags.List do
3134
</div>)
3235
end
3336

34-
def get_item(:order_list, %{
35-
"hideLabel" => hide_label,
36-
"indent" => indent,
37-
"label" => label,
38-
"labelType" => label_type,
39-
"prefixIndex" => prefix_index,
40-
"text" => text
41-
}) do
37+
def get_item(
38+
:order_list,
39+
%{
40+
"hideLabel" => hide_label,
41+
"indent" => indent,
42+
"label" => label,
43+
"labelType" => label_type,
44+
"prefixIndex" => prefix_index,
45+
"text" => text
46+
}
47+
) do
4248
prefix_frag = frag(:order_list_prefix, prefix_index)
4349
label_frag = if hide_label, do: "", else: frag(:label, label_type, indent, label)
4450
text_frag = frag(:text, text)
@@ -53,14 +59,17 @@ defmodule Helper.Converter.EditorToHTML.Frags.List do
5359
</div>)
5460
end
5561

56-
def get_item(:checklist, %{
57-
"checked" => checked,
58-
"hideLabel" => hide_label,
59-
"indent" => indent,
60-
"label" => label,
61-
"labelType" => label_type,
62-
"text" => text
63-
}) do
62+
def get_item(
63+
:checklist,
64+
%{
65+
"checked" => checked,
66+
"hideLabel" => hide_label,
67+
"indent" => indent,
68+
"label" => label,
69+
"labelType" => label_type,
70+
"text" => text
71+
}
72+
) do
6473
# local fragments
6574
checkbox_frag = frag(:checkbox, checked)
6675
label_frag = if hide_label, do: "", else: frag(:label, label_type, indent, label)

lib/helper/converter/editor_to_html/frags/quote.ex

+10-5
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,34 @@ defmodule Helper.Converter.EditorToHTML.Frags.Quote do
66
"""
77
import Helper.Validator.Guards, only: [g_none_empty_str: 1]
88

9-
alias Helper.Types, as: T
109
alias Helper.Converter.EditorToHTML.Class
10+
alias Helper.Types, as: T
11+
alias Helper.Utils
1112

1213
@class get_in(Class.article(), ["quote"])
1314

1415
@spec get(T.editor_quote()) :: T.html()
15-
def get(%{"mode" => "short", "text" => text}) do
16+
def get(%{"mode" => "short", "text" => text} = data) do
1617
wrapper_class = @class["short_wrapper"]
1718
text_class = @class["text"]
1819

19-
~s(<blockquote class="#{wrapper_class}">
20+
anchor_id = Utils.uid(:html, data)
21+
22+
~s(<blockquote id="#{anchor_id}" class="#{wrapper_class}">
2023
<div class="#{text_class}">#{text}</div>
2124
</blockquote>)
2225
end
2326

24-
def get(%{"mode" => "long", "text" => text, "caption" => caption})
27+
def get(%{"mode" => "long", "text" => text, "caption" => caption} = data)
2528
when g_none_empty_str(caption) do
2629
wrapper_class = @class["long_wrapper"]
2730
text_class = @class["text"]
2831

2932
caption = frag(:caption, caption)
3033

31-
~s(<blockquote class="#{wrapper_class}">
34+
anchor_id = Utils.uid(:html, data)
35+
36+
~s(<blockquote id="#{anchor_id}" class="#{wrapper_class}">
3237
<div class="#{text_class}">#{text}</div>
3338
#{caption}
3439
</blockquote>)

lib/helper/converter/editor_to_html/index.ex

+5-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ defmodule Helper.Converter.EditorToHTML do
5757
acc <> Frags.List.get_item(mode |> String.to_atom(), item)
5858
end)
5959

60-
~s(<div class="#{list_wrapper_class}">#{items_content}</div>)
60+
anchor_id = Utils.uid(:html, data)
61+
~s(<div id="#{anchor_id}" class="#{list_wrapper_class}">#{items_content}</div>)
6162
end
6263

6364
defp parse_block(%{"type" => "table", "data" => data}) do
@@ -74,7 +75,9 @@ defmodule Helper.Converter.EditorToHTML do
7475

7576
table_wrapper_class = get_in(@root_class, ["table", "wrapper"])
7677

77-
~s(<div class="#{table_wrapper_class}">
78+
anchor_id = Utils.uid(:html, data)
79+
80+
~s(<div id="#{anchor_id}" class="#{table_wrapper_class}">
7881
<table>
7982
<tbody>
8083
#{rows_content}

lib/helper/converter/editor_to_html/validator/editor_schema.ex

+19-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule Helper.Converter.EditorToHTML.Validator.EditorSchema do
1515
# table
1616
@valid_table_align ["left", "center", "right"]
1717

18-
@spec get(String.t()) :: map
18+
@spec get(String.t()) :: map | [parent: map, item: map]
1919
def get("editor") do
2020
%{
2121
"time" => [:number],
@@ -26,27 +26,37 @@ defmodule Helper.Converter.EditorToHTML.Validator.EditorSchema do
2626

2727
def get("header") do
2828
%{
29+
"id" => [:string, required: false],
2930
"text" => [:string],
3031
"level" => [enum: @valid_header_level],
3132
"eyebrowTitle" => [:string, required: false],
3233
"footerTitle" => [:string, required: false]
3334
}
3435
end
3536

36-
def get("paragraph"), do: %{"text" => [:string]}
37+
def get("paragraph") do
38+
%{
39+
"id" => [:string, required: false],
40+
"text" => [:string]
41+
}
42+
end
3743

3844
def get("quote") do
3945
%{
46+
"id" => [:string, required: false],
4047
"text" => [:string],
4148
"mode" => [enum: @valid_quote_mode],
4249
"caption" => [:string, required: false]
4350
}
4451
end
4552

46-
@spec get(String.t()) :: [parent: map, item: map]
4753
def get("list") do
4854
[
49-
parent: %{"mode" => [enum: @valid_list_mode], "items" => [:list]},
55+
parent: %{
56+
"id" => [:string, required: false],
57+
"mode" => [enum: @valid_list_mode],
58+
"items" => [:list]
59+
},
5060
item: %{
5161
"checked" => [:boolean],
5262
"hideLabel" => [:boolean],
@@ -61,7 +71,11 @@ defmodule Helper.Converter.EditorToHTML.Validator.EditorSchema do
6171

6272
def get("table") do
6373
[
64-
parent: %{"columnCount" => [:number, min: 2], "items" => [:list]},
74+
parent: %{
75+
"id" => [:string, required: false],
76+
"columnCount" => [:number, min: 2],
77+
"items" => [:list]
78+
},
6579
item: %{
6680
"text" => [:string],
6781
"align" => [enum: @valid_table_align],

lib/helper/converter/html_sanitizer.ex

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ defmodule Helper.Converter.HtmlSanitizer do
3333
# Meta.allow_tag_with_these_attributes("h6", ["class"])
3434
Meta.allow_tag_with_these_attributes("p", ["class"])
3535
Meta.allow_tag_with_these_attributes("img", ["class", "src"])
36-
Meta.allow_tag_with_these_attributes("div", ["class", "data-index"])
36+
Meta.allow_tag_with_these_attributes("div", ["id", "class", "data-index"])
3737
Meta.allow_tag_with_these_attributes("ul", ["class"])
3838
Meta.allow_tag_with_these_attributes("ol", ["class"])
3939
Meta.allow_tag_with_these_attributes("li", ["class"])
@@ -46,7 +46,7 @@ defmodule Helper.Converter.HtmlSanitizer do
4646
Meta.allow_tag_with_these_attributes("td", ["class", "style"])
4747

4848
# blockquote
49-
Meta.allow_tag_with_these_attributes("blockquote", ["class"])
49+
Meta.allow_tag_with_these_attributes("blockquote", ["id", "class"])
5050

5151
Meta.allow_tag_with_these_attributes("svg", [
5252
"t",

lib/helper/utils.ex

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ defmodule Helper.Utils do
66
import Helper.ErrorHandler
77
import Helper.ErrorCode
88

9+
import Helper.Validator.Guards, only: [g_none_empty_str: 1]
10+
911
alias Helper.Cache
1012

1113
def get_config(section, key, app \\ :groupher_server)
@@ -282,4 +284,13 @@ defmodule Helper.Utils do
282284
def less_than(value, target, :no_equal) when is_integer(value) and is_integer(target) do
283285
value < target
284286
end
287+
288+
@doc "html uniq id generator for editorjs"
289+
@spec uid(:html, map) :: String.t()
290+
def uid(:html, %{"id" => id}) when g_none_empty_str(id), do: id
291+
292+
def uid(:html, _) do
293+
# number is invalid for html id(if first letter)
294+
Nanoid.generate(5, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
295+
end
285296
end

mix.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ defmodule GroupherServer.Mixfile do
9292
{:excoveralls, "~> 0.8", only: :test},
9393
{:sentry, "~> 7.1"},
9494
{:recase, "~> 0.7.0"},
95-
{:nanoid, "~> 2.0.0"},
95+
{:nanoid, "~> 2.0.5"},
9696
# mailer
9797
{:bamboo, "1.3.0"},
9898
# mem cache

0 commit comments

Comments
 (0)