Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.

Commit 72c9d23

Browse files
Merge pull request #65 from alfredbaudisch/toc-tree
HTML Header Table of Contents nesting, closes #64, closes alfredbaudisch/pardall_markdown_phoenix_demo#3
2 parents 79168fa + b401ffa commit 72c9d23

File tree

3 files changed

+351
-13
lines changed

3 files changed

+351
-13
lines changed

lib/pardall_markdown/content/html_utils.ex

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ defmodule PardallMarkdown.Content.HtmlUtils do
101101
def generate_anchors_and_toc(html, %{slug: slug}) do
102102
{updated_tree, %{toc: toc}} =
103103
Floki.parse_fragment!(html)
104-
|> Floki.traverse_and_update(%{counters: %{}, toc: []}, fn
105-
{"h" <> level, attrs, children} = el, acc ->
104+
|> Floki.traverse_and_update(%{counters: %{}, toc: [], toc_positions: %{}}, fn
105+
{"h" <> header_level, attrs, children} = el, acc ->
106106
case find_node_text(children) do
107107
nil ->
108108
{el, acc}
@@ -123,17 +123,23 @@ defmodule PardallMarkdown.Content.HtmlUtils do
123123
{"data-title", title}
124124
], []}
125125

126-
toc_item = %{
126+
int_header_level = parse_header_level(header_level)
127+
{:level, level, :positions, positions} =
128+
get_toc_level(acc.toc, int_header_level, acc.toc_positions)
129+
130+
link = %{
127131
id: link_id,
132+
level: level,
133+
header: int_header_level,
128134
parent_slug: slug,
129-
title: title,
130-
level: get_level_for_toc(acc[:toc], level)
135+
title: title
131136
}
132137

133138
acc = put_in(acc[:counters][id], increase_id_count(count))
134-
acc = put_in(acc[:toc], acc.toc ++ [toc_item])
139+
acc = put_in(acc[:toc], acc.toc ++ [link])
140+
acc = put_in(acc[:toc_positions], positions)
135141

136-
{{"h" <> level, attrs, [anchor | children]}, acc}
142+
{{"h" <> header_level, attrs, [anchor | children]}, acc}
137143
end
138144

139145
el, acc ->
@@ -143,8 +149,47 @@ defmodule PardallMarkdown.Content.HtmlUtils do
143149
{:ok, updated_tree |> Floki.raw_html(), toc}
144150
end
145151

146-
defp get_level_for_toc([], _), do: 1
147-
defp get_level_for_toc(_, level), do: level |> String.to_integer()
152+
defp get_toc_level([], header, _), do:
153+
{:level, 1, :positions, %{level: 1, header: header, index: 0}}
154+
155+
defp get_toc_level(_, header, %{level: curr_level, header: curr_header, index: idx})
156+
when header == curr_header, do:
157+
{:level, curr_level, :positions, %{level: curr_level, header: curr_header, index: idx + 1}}
158+
159+
defp get_toc_level(toc, header, %{level: curr_level, header: curr_header, index: idx})
160+
when header < curr_header do
161+
[_|reversed_toc] = toc |> Enum.reverse()
162+
163+
{_, level} =
164+
reversed_toc
165+
|> Enum.reduce({curr_header, curr_level}, fn
166+
_, {acc_header, _} = acc
167+
when acc_header <= header ->
168+
acc
169+
170+
%{header: i_header, level: i_level}, _acc
171+
when i_header < header ->
172+
{i_header, i_level + 1}
173+
174+
%{header: i_header, level: i_level}, _acc
175+
when i_header == header ->
176+
{i_header, i_level}
177+
178+
%{header: i_header}, acc
179+
when i_header > header ->
180+
acc
181+
end)
182+
183+
{:level, level, :positions, %{level: level, header: header, index: idx + 1}}
184+
end
185+
186+
defp get_toc_level(_, header, %{level: curr_level, header: curr_header, index: idx})
187+
when header > curr_header do
188+
level = curr_level + 1
189+
{:level, level, :positions, %{level: level, header: header, index: idx + 1}}
190+
end
191+
192+
defp parse_header_level(level), do: String.to_integer(level)
148193

149194
def strip_in_between_space(html),
150195
do:
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule PardallMarkdown.Utils.StructUtils do
2+
def struct_to_map([]), do: []
3+
4+
def struct_to_map([_h|_t] = list) do
5+
Enum.map(list, &struct_to_map/1)
6+
end
7+
8+
def struct_to_map(item) when is_struct(item) do
9+
item
10+
|> Map.from_struct()
11+
|> navigate_item()
12+
end
13+
14+
def struct_to_map(item) when is_map(item) do
15+
item
16+
|> navigate_item()
17+
end
18+
19+
defp navigate_item(item) do
20+
item
21+
|> Enum.map(fn
22+
{k, v} when is_list(v) ->
23+
{k, v |> struct_to_map()}
24+
{k, v} -> {k, v}
25+
end)
26+
|> Enum.into(%{})
27+
end
28+
end

0 commit comments

Comments
 (0)