Skip to content

Commit 882983f

Browse files
committed
allow for new extra sidebar format that groups h3s into h2s
1 parent a3f75e8 commit 882983f

File tree

8 files changed

+178
-20
lines changed

8 files changed

+178
-20
lines changed

assets/js/autocomplete/suggestions.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export function getSuggestions (query, limit = 8) {
4242
...findSuggestionsInTopLevelNodes(nodes.extras, query, SUGGESTION_CATEGORY.extra, 'page'),
4343
...findSuggestionsInSectionsOfNodes(nodes.modules, query, SUGGESTION_CATEGORY.section, 'module'),
4444
...findSuggestionsInSectionsOfNodes(nodes.tasks, query, SUGGESTION_CATEGORY.section, 'mix task'),
45-
...findSuggestionsInSectionsOfNodes(nodes.extras, query, SUGGESTION_CATEGORY.section, 'page')
45+
...findSuggestionsInSectionsOfNodes(nodes.extras, query, SUGGESTION_CATEGORY.section, 'page'),
46+
...findSuggestionsInChildNodesOfExtras(nodes.extras, query, SUGGESTION_CATEGORY.section, 'page')
4647
].filter(suggestion => suggestion !== null)
4748

4849
return sort(suggestions).slice(0, limit)
@@ -74,13 +75,29 @@ function findSuggestionsInChildNodes (nodes, query, category) {
7475
const label = nodeGroupKeyToLabel(key)
7576

7677
return childNodes.map(childNode =>
77-
childNodeSuggestion(childNode, node.id, query, category, label) ||
78-
moduleChildNodeSuggestion(childNode, node.id, query, category, label)
78+
childNodeSuggestion(childNode, node.id, node.id, query, category, label) ||
79+
moduleChildNodeSuggestion(childNode, node.id, node.id, query, category, label)
7980
)
8081
})
8182
})
8283
}
8384

85+
/**
86+
* Finds suggestions in node groups of the given parent nodes for extras.
87+
*/
88+
function findSuggestionsInChildNodesOfExtras (nodes, query, category) {
89+
return nodes
90+
.filter(node => node.nodeGroups && !node.searchData)
91+
.flatMap(node => {
92+
return node.nodeGroups.flatMap(group => {
93+
const { key, nodes: childNodes } = group;
94+
return childNodes.map(childNode =>
95+
childNodeSuggestion(childNode, node.title, node.id, query, category, 'section')
96+
).concat([childNodeSuggestion(group, node.title, node.id, query, category, 'section')])
97+
})
98+
})
99+
}
100+
84101
/**
85102
* Finds suggestions in the sections of the given parent nodes.
86103
*/
@@ -125,14 +142,14 @@ function nodeSuggestion (node, query, category, label) {
125142
* Builds a suggestion for a child node.
126143
* Returns null if the node doesn't match the query.
127144
*/
128-
function childNodeSuggestion (childNode, parentId, query, category, label) {
145+
function childNodeSuggestion (childNode, parentTitle, parentId, query, category, label) {
129146
if (!matchesAll(childNode.id, query)) { return null }
130147

131148
return {
132149
link: `${parentId}.html#${childNode.anchor}`,
133150
title: highlightMatches(childNode.id, query),
134151
labels: [label],
135-
description: parentId,
152+
description: parentTitle,
136153
matchQuality: matchQuality(childNode.id, query),
137154
deprecated: childNode.deprecated,
138155
category
Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ex_doc/formatter/html.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ defmodule ExDoc.Formatter.HTML do
315315
content: api_reference,
316316
group: nil,
317317
id: "api-reference",
318+
sidebar_style: :flat,
318319
source_path: nil,
319320
source_url: config.source_url,
320321
title: "API Reference",
@@ -438,6 +439,7 @@ defmodule ExDoc.Formatter.HTML do
438439
content: content_html,
439440
group: group,
440441
id: id,
442+
sidebar_style: input_options[:sidebar_style] || :flat,
441443
source_path: source_path,
442444
source_url: source_url,
443445
search_data: search_data,

lib/ex_doc/formatter/html/templates.ex

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ defmodule ExDoc.Formatter.HTML.Templates do
1212
text_to_id: 1
1313
]
1414

15+
@h2_regex ~r/<h2.*?>(.*?)<\/h2>/m
16+
@h2contents_regex ~r/<h2.*?>(.*?)<\/h2>(.*?)(?=<h2|<h1|\z)/ms
17+
@h3_regex ~r/<h3.*?>(.*?)<\/h3>/m
18+
1519
@doc """
1620
Generate content from the module template for a given `node`
1721
"""
@@ -82,15 +86,16 @@ defmodule ExDoc.Formatter.HTML.Templates do
8286

8387
defp sidebar_extras(extras) do
8488
for extra <- extras do
85-
%{id: id, title: title, group: group, content: content} = extra
89+
%{id: id, title: title, group: group, content: content, sidebar_style: sidebar_style} =
90+
extra
8691

8792
item =
8893
%{
8994
id: to_string(id),
9095
title: to_string(title),
91-
group: to_string(group),
92-
headers: extract_headers(content)
96+
group: to_string(group)
9397
}
98+
|> add_headers_or_node_groups(content, sidebar_style)
9499

95100
case extra do
96101
%{search_data: search_data} when is_list(search_data) ->
@@ -111,6 +116,41 @@ defmodule ExDoc.Formatter.HTML.Templates do
111116
end
112117
end
113118

119+
defp add_headers_or_node_groups(item, content, :flat) do
120+
Map.put(item, :headers, extract_headers(content))
121+
end
122+
123+
defp add_headers_or_node_groups(item, content, :grouped) do
124+
Map.put(item, :nodeGroups, extract_node_groups(content))
125+
end
126+
127+
defp extract_node_groups(content) do
128+
@h2contents_regex
129+
|> Regex.scan(content, capture: :all_but_first)
130+
|> Enum.filter(fn
131+
["" | _] -> false
132+
_ -> true
133+
end)
134+
|> Enum.map(fn [group, content] ->
135+
nodes =
136+
@h3_regex
137+
|> Regex.scan(content, capture: :all_but_first)
138+
|> List.flatten()
139+
|> Enum.filter(&(&1 != ""))
140+
|> Enum.map(&ExDoc.Utils.strip_tags/1)
141+
|> Enum.map(&%{id: &1, title: &1, anchor: URI.encode(text_to_id(&1)), deprecated: false})
142+
143+
%{
144+
key: group,
145+
name: group,
146+
nodes: nodes,
147+
title: group,
148+
id: text_to_id(group),
149+
anchor: URI.encode(text_to_id(group))
150+
}
151+
end)
152+
end
153+
114154
defp sidebar_module({id, modules}) do
115155
modules =
116156
for module <- modules do
@@ -182,7 +222,6 @@ defmodule ExDoc.Formatter.HTML.Templates do
182222
end
183223

184224
# TODO: split into sections in Formatter.HTML instead (possibly via DocAST)
185-
@h2_regex ~r/<h2.*?>(.*?)<\/h2>/m
186225
defp extract_headers(content) do
187226
@h2_regex
188227
|> Regex.scan(content, capture: :all_but_first)

lib/mix/tasks/docs.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ defmodule Mix.Tasks.Docs do
370370
the source file.
371371
* `:source` - The source file of the extra page. This is useful if you want to customize the filename or
372372
title but keep the source file unchanged. *
373+
* `:sidebar_style` - `:flat` or `:grouped`. Defaults to `:flat`. If set to `:grouped`, all h3s will be shown
374+
in collapsible sections of their corresponding h2. The epub format ignores this option.
373375
`:search_data` - A list of terms to be indexed for autocomplete and search. If not provided, the content
374376
of the extra page will be indexed for search. See the section below for more.
375377

test/ex_doc/formatter/html/templates_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ defmodule ExDoc.Formatter.HTML.TemplatesTest do
358358
end
359359

360360
test "outputs extras with headers" do
361-
item = %{content: nil, group: nil, id: nil, title: nil}
361+
item = %{content: nil, group: nil, id: nil, title: nil, sidebar_style: :flat}
362362

363363
assert create_sidebar_items(%{}, [%{item | content: "<h2>Foo</h2><h2>Bar</h2>"}])["extras"] ==
364364
[

test/ex_doc/formatter/html_test.exs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,77 @@ defmodule ExDoc.Formatter.HTMLTest do
592592
] = Jason.decode!(content)["extras"]
593593
end
594594

595+
test "extras can group sidebar nodes as h3s grouped by h2", %{tmp_dir: tmp_dir} = context do
596+
generate_docs(
597+
doc_config(context,
598+
source_beam: "unknown",
599+
extras: [
600+
{"test/fixtures/ExtraPageWithH3s.md", sidebar_style: :grouped}
601+
]
602+
)
603+
)
604+
605+
"sidebarNodes=" <> content = read_wildcard!(tmp_dir <> "/html/dist/sidebar_items-*.js")
606+
607+
assert [
608+
%{
609+
"group" => "",
610+
"headers" => [],
611+
"id" => "api-reference",
612+
"title" => "API Reference"
613+
},
614+
%{
615+
"group" => "",
616+
"id" => "extrapagewithh3s",
617+
"nodeGroups" => [
618+
%{
619+
"key" => "Section One",
620+
"name" => "Section One",
621+
"nodes" => [
622+
%{
623+
"anchor" => "nested-section-one",
624+
"deprecated" => false,
625+
"id" => "Nested Section One",
626+
"title" => "Nested Section One"
627+
},
628+
%{
629+
"anchor" => "nested-section-two",
630+
"deprecated" => false,
631+
"id" => "Nested Section Two",
632+
"title" => "Nested Section Two"
633+
}
634+
],
635+
"anchor" => "section-one",
636+
"id" => "section-one",
637+
"title" => "Section One"
638+
},
639+
%{
640+
"key" => "Section Two",
641+
"name" => "Section Two",
642+
"nodes" => [
643+
%{
644+
"anchor" => "nested-section-three",
645+
"deprecated" => false,
646+
"id" => "Nested Section Three",
647+
"title" => "Nested Section Three"
648+
},
649+
%{
650+
"anchor" => "nested-section-four",
651+
"deprecated" => false,
652+
"id" => "Nested Section Four",
653+
"title" => "Nested Section Four"
654+
}
655+
],
656+
"anchor" => "section-two",
657+
"id" => "section-two",
658+
"title" => "Section Two"
659+
}
660+
],
661+
"title" => "Extra Page Title"
662+
}
663+
] == Jason.decode!(content)["extras"]
664+
end
665+
595666
test "custom search data is added to the sidebar and search nodes",
596667
%{tmp_dir: tmp_dir} = context do
597668
generate_docs(

test/fixtures/ExtraPageWithH3s.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Extra Page Title
2+
3+
some text
4+
5+
## Section One
6+
7+
more text
8+
9+
### Nested Section One
10+
11+
nested section one text
12+
13+
### Nested Section Two
14+
15+
nested section two text
16+
17+
## Section Two
18+
19+
final text
20+
21+
### Nested Section Three
22+
23+
nested section three text
24+
25+
### Nested Section Four
26+
27+
nested section text

0 commit comments

Comments
 (0)