Skip to content

Commit 850c3b8

Browse files
Add source link and Livebook badge to extras (#1408)
1 parent 1792d12 commit 850c3b8

9 files changed

Lines changed: 119 additions & 24 deletions

File tree

assets/js/content.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { qs } from './helpers'
1+
import { qs, qsAll } from './helpers'
22

33
const CONTENT_SELECTOR = '.content'
44
const CONTENT_INNER_SELECTOR = '.content-inner'
5+
const LIVEBOOK_BADGE_ANCHOR_SELECTOR = '.livebook-badge'
56

67
/**
78
* Runs some general modifiers on the documentation content.
89
*/
910
export function initialize () {
1011
fixLinks()
1112
fixSpacebar()
13+
setLivebookBadgeUrl()
1214
}
1315

1416
/**
@@ -34,3 +36,14 @@ function fixSpacebar () {
3436
qs(CONTENT_INNER_SELECTOR).setAttribute('tabindex', -1)
3537
qs(CONTENT_INNER_SELECTOR).focus()
3638
}
39+
40+
function setLivebookBadgeUrl () {
41+
const path = window.location.pathname
42+
const notebookPath = path.replace(/\.html$/, '.livemd')
43+
const notebookUrl = new URL(notebookPath, window.location.href).toString()
44+
const targetUrl = `https://livebook.dev/run?url=${encodeURIComponent(notebookUrl)}`
45+
46+
for (const anchor of qsAll(LIVEBOOK_BADGE_ANCHOR_SELECTOR)) {
47+
anchor.href = targetUrl
48+
}
49+
}

assets/less/content/general.less

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ a.view-source {
6464
}
6565
}
6666

67+
a.livebook-badge {
68+
display: flex;
69+
}
70+
6771
.note {
6872
color: #727272;
6973
margin-right: 5px;

lib/ex_doc/doc_ast.ex

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ defmodule ExDoc.DocAST do
2424
end
2525

2626
# https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element
27-
@void_elements ~W(area base br col command embed hr img input keygen link
27+
@void_elements ~W(area base br col command embed hr img input keygen link
2828
meta param source track wbr)a
2929

3030
@doc """
@@ -94,6 +94,38 @@ defmodule ExDoc.DocAST do
9494
{tag, attrs, parse_erl_ast(content), %{}}
9595
end
9696

97+
@doc """
98+
Extracts leading title element from the given AST.
99+
100+
If found, the title element is stripped from the resulting AST.
101+
"""
102+
def extract_title(ast)
103+
104+
def extract_title([{:h1, _attrs, inner, _meta} | ast]) do
105+
{:ok, inner, ast}
106+
end
107+
108+
def extract_title(_ast) do
109+
:error
110+
end
111+
112+
@doc """
113+
Returns text content from the given AST.
114+
"""
115+
def text_from_ast(ast) do
116+
ast
117+
|> do_text_from_ast()
118+
|> IO.iodata_to_binary()
119+
|> String.trim()
120+
end
121+
122+
def do_text_from_ast(ast) when is_list(ast) do
123+
Enum.map(ast, &do_text_from_ast/1)
124+
end
125+
126+
def do_text_from_ast(ast) when is_binary(ast), do: ast
127+
def do_text_from_ast({_tag, _attr, ast, _meta}), do: text_from_ast(ast)
128+
97129
@doc """
98130
Highlights a DocAST converted to string.
99131
"""

lib/ex_doc/formatter/epub.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ defmodule ExDoc.Formatter.EPUB do
5757

5858
defp generate_extras(config) do
5959
for {_title, extras} <- config.extras do
60-
Enum.each(extras, fn %{id: id, title: title, content: content} ->
60+
Enum.each(extras, fn %{id: id, title: title, title_content: title_content, content: content} ->
6161
output = "#{config.output}/OEBPS/#{id}.xhtml"
62-
html = Templates.extra_template(config, title, content)
62+
html = Templates.extra_template(config, title, title_content, content)
6363

6464
if File.regular?(output) do
6565
IO.puts(:stderr, "warning: file #{Path.relative_to_cwd(output)} already exists")

lib/ex_doc/formatter/epub/templates.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ defmodule ExDoc.Formatter.EPUB.Templates do
7676
:def,
7777
:extra_template,
7878
Path.expand("templates/extra_template.eex", __DIR__),
79-
[:config, :title, :content],
79+
[:config, :title, :title_content, :content],
8080
trim: true
8181
)
8282

lib/ex_doc/formatter/epub/templates/extra_template.eex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<%= head_template(config, %{title: title}) %>
2+
<h1 id="content">
3+
<%= title_content %>
4+
</h1>
25
<%= content %>
36
<%# Extra content specified by the user (e.g. custom Javascript) %>
47
<%= config.before_closing_body_tag.(:epub) %>

lib/ex_doc/formatter/html.ex

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ defmodule ExDoc.Formatter.HTML do
230230
end
231231

232232
defp copy_extras(config, extras) do
233-
for %{source_path: source_path, id: id} <- extras,
233+
for %{source_path: source_path, id: id} when source_path != nil <- extras,
234234
ext = extension_name(source_path),
235235
ext == ".livemd" do
236236
output = "#{config.output}/#{id}#{ext}"
@@ -293,7 +293,16 @@ defmodule ExDoc.Formatter.HTML do
293293

294294
defp build_api_reference(nodes_map, config) do
295295
api_reference = Templates.api_reference_template(config, nodes_map)
296-
%{id: "api-reference", title: "API Reference", group: "", content: api_reference}
296+
297+
%{
298+
id: "api-reference",
299+
title: "API Reference",
300+
group: "",
301+
title_content: "API Reference",
302+
content: api_reference,
303+
source_path: nil,
304+
source_url: nil
305+
}
297306
end
298307

299308
@doc """
@@ -346,14 +355,40 @@ defmodule ExDoc.Formatter.HTML do
346355
"file extension not recognized, allowed extension is either .md, .txt or no extension"
347356
end
348357

358+
{title_ast, ast} =
359+
case ExDoc.DocAST.extract_title(ast) do
360+
{:ok, title_ast, ast} -> {title_ast, ast}
361+
:error -> {nil, ast}
362+
end
363+
364+
title_text = title_ast && ExDoc.DocAST.text_from_ast(title_ast)
365+
title_html = title_ast && ExDoc.DocAST.to_string(title_ast)
366+
349367
# TODO: don't hardcode Elixir for extras?
350368
language = ExDoc.Language.Elixir
351-
html_content = autolink_and_render(ast, language, autolink_opts, opts)
369+
content_html = autolink_and_render(ast, language, autolink_opts, opts)
352370

353371
group = GroupMatcher.match_extra(groups, input)
354-
title = title || extract_title(html_content) || filename_to_title(input)
372+
title = title || title_text || filename_to_title(input)
373+
374+
source_path = Path.relative_to(input, ".")
375+
376+
source_url =
377+
if url = config.source_url_pattern do
378+
url
379+
|> String.replace("%{path}", source_path)
380+
|> String.replace("%{line}", "1")
381+
end
355382

356-
%{id: id, title: title, group: group, content: html_content, source_path: input}
383+
%{
384+
id: id,
385+
title: title,
386+
group: group,
387+
title_content: title_html,
388+
content: content_html,
389+
source_path: source_path,
390+
source_url: source_url
391+
}
357392
end
358393

359394
defp extension_name(input) do
@@ -362,20 +397,6 @@ defmodule ExDoc.Formatter.HTML do
362397
|> String.downcase()
363398
end
364399

365-
@tag_regex ~r/<[^>]*>/m
366-
defp strip_html(header) do
367-
Regex.replace(@tag_regex, header, "")
368-
end
369-
370-
@h1_regex ~r/<h1.*?>(.+?)<\/h1>/m
371-
defp extract_title(content) do
372-
title = Regex.run(@h1_regex, content, capture: :all_but_first)
373-
374-
if title do
375-
title |> List.first() |> strip_html() |> String.trim()
376-
end
377-
end
378-
379400
@doc """
380401
Convert the input file name into a title
381402
"""
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
<%= head_template(config, %{title: node.title, type: :extra}) %>
22
<%= sidebar_template(config, nodes_map) %>
33

4+
<h1 id="content">
5+
<%= node.title_content %>
6+
<%= if node.source_url do %>
7+
<a href="<%= node.source_url %>" title="View Source" class="view-source" rel="help">
8+
<span class="icon-code" aria-hidden="true"></span>
9+
<span class="sr-only">View Source</span>
10+
</a>
11+
<% end %>
12+
</h1>
13+
14+
<%= if node.source_path && String.ends_with?(node.source_path, ".livemd") do %>
15+
<a href="#" class="livebook-badge">
16+
<img src="https://livebook.dev/badge/v1/blue.svg" alt="Run in Livebook" width="150" />
17+
</a>
18+
<% end %>
19+
420
<%= link_headings(node.content) %>
521
<%= bottom_actions_template(refs) %>
622
<%= footer_template(config, node) %>

test/ex_doc/formatter/html_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@ defmodule ExDoc.Formatter.HTMLTest do
344344
assert content =~
345345
~r{<a href="https://hexdocs.pm/mix/Mix.Tasks.Compile.Elixir.html"><code(\sclass="inline")?>mix compile.elixir</code></a>}
346346

347+
refute content =~
348+
~R{<img src="https://livebook.dev/badge/v1/blue.svg" alt="Run in Livebook" width="150" />}
349+
347350
assert content =~ "<p><strong>raw content</strong></p>"
348351

349352
content = File.read!("#{output_dir()}/plaintextfiles.html")
@@ -368,6 +371,9 @@ defmodule ExDoc.Formatter.HTMLTest do
368371

369372
assert content =~
370373
~R{<p>Read <code class="inline">.livemd</code> files generated by <a href="https://github.com/livebook-dev/livebook">livebook</a>.}
374+
375+
assert content =~
376+
~R{<img src="https://livebook.dev/badge/v1/blue.svg" alt="Run in Livebook" width="150" />}
371377
end
372378

373379
test "without any other content" do

0 commit comments

Comments
 (0)