Skip to content

Commit

Permalink
feat: customize search_data for an extra
Browse files Browse the repository at this point in the history
This allows configuring the "search data" for any given extra. When you
do this, you take over the way that the extra appears both in
autocomplete and in search_data. The current system assumes that you
give this information in markdown, which I think is a safe assumption,
but we could do something like allow for an html_body or markdown_body
to be given, etc.

This is likely not going to be useful for 99% of people, and as such I
think its okay that its not super ergonomic. Specifically, you have to
completely manage the search content etc. The way this will be used,
however, is in Spark where we generate markdown files for DSL
documentation. With this change, we can add a function called
Spark.Dsl.search_data_for(Your.Dsl) which will improve the autocomplete
experience and the search experience in various ways (by adding more
things to autocomplete than just headers, and by customizing the type of
the search data nodes
  • Loading branch information
zachdaniel committed Jan 12, 2025
1 parent 3139d3f commit b1ee88b
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 32 deletions.
30 changes: 25 additions & 5 deletions assets/js/autocomplete/suggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,15 @@ export function getSuggestions (query, limit = 8) {
* Finds suggestions in top level sidebar nodes.
*/
function findSuggestionsInTopLevelNodes (nodes, query, category, label) {
return nodes.map(node => nodeSuggestion(node, query, category, label))
return nodes.map(node => {
if (node.searchData) {
// When searchData is present, all search results are found by findSuggestionsInSectionsOfNodes
return null
}

return nodeSuggestion(node, query, category, label)
}
)
}

/**
Expand Down Expand Up @@ -88,7 +96,11 @@ function findSuggestionsInSectionsOfNodes (nodes, query, category, label) {
* Returns any sections of the given parent node.
*/
function nodeSections (node) {
return (node.sections || []).concat(node.headers || [])
if (node.searchData) {
return node.searchData
} else {
return (node.sections || []).concat(node.headers || [])
}
}

/**
Expand Down Expand Up @@ -133,12 +145,20 @@ function childNodeSuggestion (childNode, parentId, query, category, label) {
function nodeSectionSuggestion (node, section, query, category, label) {
if (!matchesAny(section.id, query)) { return null }

let link

if (section.anchor === '' || !section.anchor) {
link = `${node.id}.html`
} else {
link = `${node.id}.html#${section.anchor}`
}

return {
link: `${node.id}.html#${section.anchor}`,
link,
title: highlightMatches(section.id, query),
description: node.title,
description: section.description || node.title,
matchQuality: matchQuality(section.id, query),
labels: [label, 'section'],
labels: section.labels || [label, 'section'],
category
}
}
Expand Down
15 changes: 15 additions & 0 deletions assets/test/autocomplete/suggestions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ describe('getSuggestions', () => {
id: 'api-reference',
title: 'API Reference'
},
{
title: "how-to-make-things",
description: "How to make things",
searchData: [
{
id: "custom-text",
anchor: "Some Custom Text",
labels: ["custom"]
}
]
},
{
id: 'library-guidelines',
title: 'Library Guidelines',
Expand All @@ -89,6 +100,10 @@ describe('getSuggestions', () => {
expect(getSuggestions('Gua').length).to.eql(1)
})

it('returns custom searchData of extras', () => {
expect(getSuggestions('custom text').length).to.eql(1)
})

it('returns matching functions, callbacks and types', () => {
expect(getSuggestions('get_by').length).to.eql(1)
expect(getSuggestions('fetch').length).to.eql(1)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion lib/ex_doc/formatter/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule ExDoc.Formatter.HTML do

@main "api-reference"
@assets_dir "assets"
@search_data_keys [:anchor, :body, :description, :title, :type]

@doc """
Generates HTML documentation for the given modules.
Expand Down Expand Up @@ -183,7 +184,9 @@ defmodule ExDoc.Formatter.HTML do
end

defp generate_sidebar_items(nodes_map, extras, config) do
content = Templates.create_sidebar_items(nodes_map, extras)
content =
Templates.create_sidebar_items(nodes_map, extras)

path = "dist/sidebar_items-#{digest(content)}.js"
File.write!(Path.join(config.output, path), content)
[path]
Expand Down Expand Up @@ -429,13 +432,16 @@ defmodule ExDoc.Formatter.HTML do
source_path = source_file |> Path.relative_to(File.cwd!()) |> String.replace_leading("./", "")
source_url = Utils.source_url_pattern(source_url_pattern, source_path, 1)

search_data = normalize_search_data!(input_options[:search_data])

%{
source: source,
content: content_html,
group: group,
id: id,
source_path: source_path,
source_url: source_url,
search_data: search_data,
title: title,
title_content: title_html || title
}
Expand All @@ -445,6 +451,26 @@ defmodule ExDoc.Formatter.HTML do
build_extra({input, []}, groups, language, autolink_opts, source_url_pattern)
end

defp normalize_search_data!(nil), do: nil

defp normalize_search_data!(search_data) when is_list(search_data) do
Enum.each(search_data, fn search_data ->
has_keys = Map.keys(search_data)

if Enum.sort(has_keys) != @search_data_keys do
raise ArgumentError,
"Expected search data to be a list of maps with the keys: #{inspect(@search_data_keys)}, found keys: #{inspect(has_keys)}"
end
end)

search_data
end

defp normalize_search_data!(search_data) do
raise ArgumentError,
"Expected search data to be a list of maps with the keys: #{inspect(@search_data_keys)}, found: #{inspect(search_data)}"
end

defp extension_name(input) do
input
|> Path.extname()
Expand Down
Loading

0 comments on commit b1ee88b

Please sign in to comment.