Skip to content
Open
Changes from 1 commit
Commits
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
55 changes: 55 additions & 0 deletions src/html/HTMLWriter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,61 @@
return SearchRecord(ctx, navnode; text = mdflatten(node))
end


# for @example blocks
function SearchRecord(ctx::HTMLContext, navnode::Documenter.NavNode, node::MarkdownAST.Node{Nothing}, element::Documenter.MultiOutput)
@info "MultiOutput SearchRecord method called!"
children_array = collect(node.children)

if isempty(children_array) || length(children_array) < 2
return SearchRecord(ctx, navnode; text = "")
end

output_text = ""
for child in children_array[2:end]
if isa(child.element, Documenter.MultiOutputElement) && isa(child.element.element, Dict)
for mime in [MIME"text/plain"(), MIME"text/markdown"(), MIME"text/html"()]
if haskey(child.element.element, mime)
output_text *= string(child.element.element[mime]) * " "
break
end
end

Check warning on line 734 in src/html/HTMLWriter.jl

View check run for this annotation

Codecov / codecov/patch

src/html/HTMLWriter.jl#L734

Added line #L734 was not covered by tests
else
output_text *= mdflatten(child) * " "
end
Comment on lines 774 to 792
Copy link
Member

@mortenpi mortenpi Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for at-example blocks, we render both the input and the output. We probably want to put the source code into the index as well?

image

In general, though, I wonder if it's better to just add the search index entries in the actual domify methods, like here:

# Select the "best" representation for HTML output.
domify(dctx::DCtx, node::Node, ::Documenter.MultiOutput) = domify(dctx, node.children)
domify(dctx::DCtx, node::Node, moe::Documenter.MultiOutputElement) = Base.invokelatest(domify, dctx, node, moe.element)
function domify(dctx::DCtx, node::Node, d::Dict{MIME, Any})
rawhtml(code) = DOM.Tag(Symbol("#RAW#"))(code)
# Our first preference for the MIME type is 'text/html', which we can natively include
# in the HTML. But it might happen that it's too large (above example_size_threshold),
# in which case we move on to trying to write it as an image.
has_text_html = false
if haskey(d, MIME"text/html"())
if length(d[MIME"text/html"()]) < dctx.ctx.settings.example_size_threshold
# If the size threshold is met, we can just return right away
return rawhtml(d[MIME"text/html"()])
end
# We'll set has_text_html to distinguish it from the case where the 'text/html' MIME
# show() method was simply missing.
has_text_html = true
end
# If text/html failed, we try to write the output as an image, possibly to a file.
image = if haskey(d, MIME"image/svg+xml"())
@tags img
svg = d[MIME"image/svg+xml"()]
svg_tag_match = match(r"<svg[^>]*>", svg)
dom = if length(svg) >= dctx.ctx.settings.example_size_threshold
filename = write_data_file(dctx, svg; suffix = ".svg")
@assert !isnothing(filename)
img[:src => filename, :alt => "Example block output"]
elseif svg_tag_match === nothing
# There is no svg tag so we don't do any more advanced
# processing and just return the svg as HTML.
# The svg string should be invalid but that's not our concern here.
rawhtml(svg)
else
# The xmlns attribute has to be present for data:image/svg+xml
# to work (https://stackoverflow.com/questions/18467982).
# If it doesn't exist, we splice it into the first svg tag.
# This should never invalidate otherwise valid svg.
svg_tag = svg_tag_match.match
xmlns_present = occursin("xmlns", svg_tag)
if !xmlns_present
svg = replace(svg, "<svg" => "<svg xmlns=\"http://www.w3.org/2000/svg\"", count = 1)
end
# We can leave the svg as utf8, but the minimum safety precaution we need
# is to ensure the src string separator is not in the svg.
# That can be either " or ', and the svg will most likely use only one of them
# so we check which one occurs more often and use the other as the separator.
# This should leave most svg basically intact.
# Replace % with %25 and # with %23 https://github.com/jakubpawlowicz/clean-css/issues/763#issuecomment-215283553
svg = replace(svg, "%" => "%25")
svg = replace(svg, "#" => "%23")
singles = count(==('\''), svg)
doubles = count(==('"'), svg)
if singles > doubles
# Replace every " with %22 because it terminates the src=" string otherwise
svg = replace(svg, "\"" => "%22")
sep = "\""
else
# Replace every ' with %27 because it terminates the src=' string otherwise
svg = replace(svg, "\'" => "%27")
sep = "'"
end
rawhtml(string("<img src=", sep, "data:image/svg+xml;utf-8,", svg, sep, "/>"))
end
(; dom = dom, mime = "image/svg+xml")
elseif haskey(d, MIME"image/png"())
domify_show_image_binary(dctx, "png", d)
elseif haskey(d, MIME"image/webp"())
domify_show_image_binary(dctx, "webp", d)
elseif haskey(d, MIME"image/gif"())
domify_show_image_binary(dctx, "gif", d)
elseif haskey(d, MIME"image/jpeg"())
domify_show_image_binary(dctx, "jpeg", d)
end
# If image is nothing, then the object did not have any of the supported image
# representations. In that case, if the 'text/html' representation exists, we use that,
# but with a warning because it goes over the size limit. If 'text/html' was missing too,
# we carry on to additional MIME types.
if has_text_html && isnothing(image)
# The 'text/html' representation of an @example block is above the threshold, but no
# supported image representation is present as an alternative.
push!(
dctx.ctx.atexample_warnings,
AtExampleFallbackWarning(
page = dctx.navnode.page,
size_bytes = length(d[MIME"text/html"()]),
fallback = nothing,
)
)
return rawhtml(d[MIME"text/html"()])
elseif has_text_html && !isnothing(image)
# The 'text/html' representation of an @example block is above the threshold,
# falling back to '$(image.mime)' representation.
push!(
dctx.ctx.atexample_warnings,
AtExampleFallbackWarning(
page = dctx.navnode.page,
size_bytes = length(d[MIME"text/html"()]),
fallback = image.mime,
)
)
return image.dom
elseif !has_text_html && !isnothing(image)
return image.dom
end
# Check for some 'fallback' MIMEs, defaulting to 'text/plain' if we can't find any of them.
return if haskey(d, MIME"text/latex"())
latex = d[MIME"text/latex"()]
# If the show(io, ::MIME"text/latex", x) output is already wrapped in
# \[ ... \] or $$ ... $$, we unwrap it first, since when we output
# Markdown.LaTeX objects we put the correct delimiters around it anyway.
has_math, latex = _strip_latex_math_delimiters(latex)
out = if !has_math
Documenter.mdparse(latex; mode = :single)
else
[MarkdownAST.@ast MarkdownAST.DisplayMath(latex)]
end
domify(dctx, out)
elseif haskey(d, MIME"text/markdown"())
out = Markdown.parse(d[MIME"text/markdown"()])
out = convert(MarkdownAST.Node, out)
domify(dctx, out)
elseif haskey(d, MIME"text/plain"())
@tags pre
text = d[MIME"text/plain"()]
pre[".documenter-example-output"](domify_ansicoloredtext(text, "nohighlight hljs"))
else
error("this should never happen.")
end
end

The bonus is that the "rendered" output of a block is already present, so it would reduce duplication I think.

X-ref #2672 (comment) about how to remove the other entries.

end
return SearchRecord(ctx, navnode; text = output_text)
end

# for @repl blocks

function SearchRecord(ctx::HTMLContext, navnode::Documenter.NavNode, node::MarkdownAST.Node{Nothing}, element::Documenter.MultiCodeBlock)
@info "MultiCodeBlock SearchRecord method called!"
children_array = collect(node.children)

if isempty(children_array) || length(children_array) < 2
return SearchRecord(ctx, navnode; text = "")
end

output_text = ""
for i in 2:2:length(children_array)
if i <= length(children_array)
output_text *= mdflatten(children_array[i]) * " "
end
end
return SearchRecord(ctx, navnode; text = output_text)
end

# for @eval blocks
function SearchRecord(ctx, navnode, node::Node, element::Documenter.EvalNode)
@info "EvalNode SearchRecord method called!"
if isnothing(element.result)
return SearchRecord(ctx, navnode; text = "")
else
return SearchRecord(ctx, navnode; text = mdflatten(element.result))
end
end

function JSON.lower(rec::SearchRecord)
# Replace any backslashes in links, if building the docs on Windows
src = replace(rec.src, '\\' => '/')
Expand Down
Loading