From 281325876cddcfa503aebde3d1a7c2970243f680 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Sat, 10 Aug 2024 02:21:13 -0400 Subject: [PATCH 01/15] Convert admonition blockquotes to divs Some screen readers will announce that an element is a blockquote. It could be potentially surprising to hear something announced as a blockquote despite the inner contents not being a quote. --- lib/ex_doc/markdown/earmark.ex | 40 +++++++++++++++++++++++++++ test/ex_doc/markdown/earmark_test.exs | 37 +++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index 20e80a51e..7bb38f1b8 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -5,6 +5,8 @@ defmodule ExDoc.Markdown.Earmark do @behaviour ExDoc.Markdown + @admonition_classes ~w(warning error info tip neutral) + @impl true def available? do match?({:ok, _}, Application.ensure_all_started(:earmark_parser)) and @@ -74,6 +76,44 @@ defmodule ExDoc.Markdown.Earmark do "$$\n#{content}\n$$" end + # Convert admonition blockquotes to divs for screen reader accessibility + defp fixup( + {"blockquote", blockquote_attrs, [{tag, h_attrs, _h_content, _h_meta} = h_elem | rest] = ast, + blockquote_meta} + ) + when tag in ["h3", "h4"] do + h_admonition_classes = + h_attrs + |> Enum.find("", &match?({"class", _}, &1)) + |> then(fn + "" -> + "" + + {"class", classes} -> + classes + |> String.split(" ") + |> Enum.filter(&(&1 in @admonition_classes)) + |> Enum.join(" ") + end) + + if h_admonition_classes != "" do + blockquote_attrs = + case Enum.split_with(blockquote_attrs, &match?({"class", _}, &1)) do + {[], attrs} -> + [{"class", h_admonition_classes}, {"role", "note"} | attrs] + + {[{"class", classes}], attrs} -> + classes = String.trim_trailing(classes) <> " #{h_admonition_classes}" + [{"class", classes}, {"role", "note"} | attrs] + end + + fixup({"div", blockquote_attrs, [h_elem | rest], blockquote_meta}) + else + # regular blockquote, copied fixup/1 here to avoid infinite loop + {:blockquote, Enum.map(blockquote_attrs, &fixup_attr/1), fixup(ast), blockquote_meta} + end + end + defp fixup({tag, attrs, ast, meta}) when is_binary(tag) and is_list(attrs) and is_map(meta) do {fixup_tag(tag), Enum.map(attrs, &fixup_attr/1), fixup(ast), meta} end diff --git a/test/ex_doc/markdown/earmark_test.exs b/test/ex_doc/markdown/earmark_test.exs index ae3f271b6..e4fc683e5 100644 --- a/test/ex_doc/markdown/earmark_test.exs +++ b/test/ex_doc/markdown/earmark_test.exs @@ -75,6 +75,43 @@ defmodule ExDoc.Markdown.EarmarkTest do ] end + test "converts blockquote admonitions to regular divs" do + info = """ + > #### Info {: .info .ignore} + > This is info. + """ + assert Markdown.to_ast(info, []) == [ + {:div, [class: "info", role: "note"], [ + {:h4, [class: "ignore info"], ["Info"], %{}}, + {:p, [], ["This is info."], %{}} + ], %{}} + ] + + not_admonition = """ + > ### H3 {: .xyz} + > This is NOT an admonition! + """ + + assert Markdown.to_ast(not_admonition, []) == [ + {:blockquote, [], [ + {:h3, [class: "xyz"], ["H3"], %{}}, + {:p, [], ["This is NOT an admonition!"], %{}} + ], %{}} + ] + + warning_error = """ + > ### Warning! Error! {: .warning .error} + > A warning and an error. + """ + + assert Markdown.to_ast(warning_error, []) == [ + {:div, [class: "error warning", role: "note"], [ + {:h3, [class: "error warning"], ["Warning! Error!"], %{}}, + {:p, [], ["A warning and an error."], %{}} + ], %{}} + ] + end + test "keeps math syntax without interpreting math as markdown" do assert Markdown.to_ast("Math $x *y* y$", []) == [ {:p, [], ["Math ", "$x *y* y$"], %{}} From dad588bbbc6f28617ce593eb4658a4a8e9bd7098 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Sat, 10 Aug 2024 04:11:32 -0400 Subject: [PATCH 02/15] Fix styles, format code --- assets/css/content/admonition.css | 48 +++++++++++++-------------- assets/css/content/general.css | 2 +- lib/ex_doc/markdown/earmark.ex | 10 +++--- test/ex_doc/markdown/earmark_test.exs | 34 ++++++++++--------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index 863b91e44..69ccf6282 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -1,36 +1,36 @@ -.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) { +.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) { border-radius: 10px; border-left: 0; } -.content-inner blockquote.warning { +.content-inner div.admonition.warning { background-color: var(--warningBackground); } -.content-inner blockquote.error { +.content-inner div.admonition.error { background-color: var(--errorBackground); } -.content-inner blockquote.info { +.content-inner div.admonition.info { background-color: var(--infoBackground); } -.content-inner blockquote.neutral { +.content-inner div.admonition.neutral { background-color: var(--neutralBackground); } -.content-inner blockquote.tip { +.content-inner div.admonition.tip { background-color: var(--tipBackground); } -.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--contrast); margin: 0 -1.2rem; padding: .7rem 1.2rem .7rem 3.3rem; font-weight: 700; font-style: normal; } -.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { +.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { color: var(--contrast); position: absolute; left: 1rem; @@ -41,74 +41,74 @@ -moz-osx-font-smoothing: grayscale; } -.content-inner blockquote :is(h3, h4).warning { +.content-inner div.admonition :is(h3, h4).warning { background-color: var(--warningHeadingBackground); color: var(--warningHeading); } -.content-inner blockquote :is(h3, h4).warning::before { +.content-inner div.admonition :is(h3, h4).warning::before { content: var(--icon-error-warning); color: var(--warningHeading); } -.content-inner blockquote :is(h3, h4).error { +.content-inner div.admonition :is(h3, h4).error { background-color: var(--errorHeadingBackground); color: var(--errorHeading); } -.content-inner blockquote :is(h3, h4).error::before { +.content-inner div.admonition :is(h3, h4).error::before { content: var(--icon-error-warning); color: var(--errorHeading); } -.content-inner blockquote :is(h3, h4).info { +.content-inner div.admonition :is(h3, h4).info { background-color: var(--infoHeadingBackground); color: var(--infoHeading); } -.content-inner blockquote :is(h3, h4).info::before { +.content-inner div.admonition :is(h3, h4).info::before { content: var(--icon-information); color: var(--infoHeading); } -.content-inner blockquote :is(h3, h4).neutral { +.content-inner div.admonition :is(h3, h4).neutral { background-color: var(--neutralHeadingBackground); color: var(--neutralHeading); } -.content-inner blockquote :is(h3, h4).neutral::before { +.content-inner div.admonition :is(h3, h4).neutral::before { content: var(--icon-double-quotes-l); color: var(--neutralHeading); } -.content-inner blockquote :is(h3, h4).tip { +.content-inner div.admonition :is(h3, h4).tip { background-color: var(--tipHeadingBackground); color: var(--tipHeading); } -.content-inner blockquote :is(h3, h4).tip::before { +.content-inner div.admonition :is(h3, h4).tip::before { content: var(--icon-information); color: var(--tipHeading); } -.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); } -.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { +.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { color: inherit; text-decoration-color: currentColor; } @media screen and (max-width: 768px) { - .content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) { + .content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) { margin-left: calc(-1 * var(--content-gutter)); margin-right: calc(-1 * var(--content-gutter)); padding-left: var(--content-gutter); @@ -116,7 +116,7 @@ border-radius: 0; } - .content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { margin: 0 calc(-1 * var(--content-gutter)); } } diff --git a/assets/css/content/general.css b/assets/css/content/general.css index bf64ab3f2..1cf02b9cb 100644 --- a/assets/css/content/general.css +++ b/assets/css/content/general.css @@ -97,7 +97,7 @@ font-weight: normal; } -.content-inner blockquote { +.content-inner blockquote, .content-inner div.admonition { border-left: 3px solid var(--blockquoteBorder); position: relative; margin: 1.5625em 0; diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index 7bb38f1b8..662460795 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -78,8 +78,8 @@ defmodule ExDoc.Markdown.Earmark do # Convert admonition blockquotes to divs for screen reader accessibility defp fixup( - {"blockquote", blockquote_attrs, [{tag, h_attrs, _h_content, _h_meta} = h_elem | rest] = ast, - blockquote_meta} + {"blockquote", blockquote_attrs, + [{tag, h_attrs, _h_content, _h_meta} = h_elem | rest] = ast, blockquote_meta} ) when tag in ["h3", "h4"] do h_admonition_classes = @@ -97,13 +97,15 @@ defmodule ExDoc.Markdown.Earmark do end) if h_admonition_classes != "" do + admonition_classes = "admonition #{h_admonition_classes}" + blockquote_attrs = case Enum.split_with(blockquote_attrs, &match?({"class", _}, &1)) do {[], attrs} -> - [{"class", h_admonition_classes}, {"role", "note"} | attrs] + [{"class", admonition_classes}, {"role", "note"} | attrs] {[{"class", classes}], attrs} -> - classes = String.trim_trailing(classes) <> " #{h_admonition_classes}" + classes = String.trim_trailing(classes) <> " #{admonition_classes}" [{"class", classes}, {"role", "note"} | attrs] end diff --git a/test/ex_doc/markdown/earmark_test.exs b/test/ex_doc/markdown/earmark_test.exs index e4fc683e5..fec19504d 100644 --- a/test/ex_doc/markdown/earmark_test.exs +++ b/test/ex_doc/markdown/earmark_test.exs @@ -80,12 +80,14 @@ defmodule ExDoc.Markdown.EarmarkTest do > #### Info {: .info .ignore} > This is info. """ + assert Markdown.to_ast(info, []) == [ - {:div, [class: "info", role: "note"], [ - {:h4, [class: "ignore info"], ["Info"], %{}}, - {:p, [], ["This is info."], %{}} - ], %{}} - ] + {:div, [class: "info", role: "note"], + [ + {:h4, [class: "ignore info"], ["Info"], %{}}, + {:p, [], ["This is info."], %{}} + ], %{}} + ] not_admonition = """ > ### H3 {: .xyz} @@ -93,11 +95,12 @@ defmodule ExDoc.Markdown.EarmarkTest do """ assert Markdown.to_ast(not_admonition, []) == [ - {:blockquote, [], [ - {:h3, [class: "xyz"], ["H3"], %{}}, - {:p, [], ["This is NOT an admonition!"], %{}} - ], %{}} - ] + {:blockquote, [], + [ + {:h3, [class: "xyz"], ["H3"], %{}}, + {:p, [], ["This is NOT an admonition!"], %{}} + ], %{}} + ] warning_error = """ > ### Warning! Error! {: .warning .error} @@ -105,11 +108,12 @@ defmodule ExDoc.Markdown.EarmarkTest do """ assert Markdown.to_ast(warning_error, []) == [ - {:div, [class: "error warning", role: "note"], [ - {:h3, [class: "error warning"], ["Warning! Error!"], %{}}, - {:p, [], ["A warning and an error."], %{}} - ], %{}} - ] + {:div, [class: "error warning", role: "note"], + [ + {:h3, [class: "error warning"], ["Warning! Error!"], %{}}, + {:p, [], ["A warning and an error."], %{}} + ], %{}} + ] end test "keeps math syntax without interpreting math as markdown" do From 498b96c5bcc4416b4516bd052783c4250d98022d Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Sat, 10 Aug 2024 04:21:44 -0400 Subject: [PATCH 03/15] Update admonition styles for epub --- assets/css/content/epub-admonition.css | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index 0e26d2b9e..f243cb6c6 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -1,4 +1,4 @@ -.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) { +.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) { border-left: solid 4px; color: var(--black); font-size: 0.9em; @@ -9,68 +9,68 @@ page-break-inside: avoid; } -.content-inner blockquote.warning { +.content-inner div.admonition.warning { background-color: var(--warningBackground); border-left-color: var(--warningHeadingBackground); } -.content-inner blockquote.error { +.content-inner div.admonition.error { background-color: var(--errorBackground); border-left-color: var(--errorHeadingBackground); } -.content-inner blockquote.info { +.content-inner div.admonition.info { background-color: var(--infoBackground); border-left-color: var(--infoHeadingBackground); } -.content-inner blockquote.neutral { +.content-inner div.admonition.neutral { background-color: var(--neutralBackground); border-left-color: var(--neutralHeadingBackground); } -.content-inner blockquote.tip { +.content-inner div.admonition.tip { background-color: var(--tipBackground); border-left-color: var(--tipHeadingBackground); } -.content-inner blockquote :is(h3, h4) { +.content-inner div.admonition :is(h3, h4) { font-weight: bold; margin: 0px 10px 5px 0px; } -.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { font-style: normal; font-weight: 700; } -.content-inner blockquote :is(h3, h4).warning { +.content-inner div.admonition :is(h3, h4).warning { color: var(--warningHeadingBackground); } -.content-inner blockquote :is(h3, h4).error { +.content-inner div.admonition :is(h3, h4).error { color: var(--errorHeadingBackground); } -.content-inner blockquote :is(h3, h4).info { +.content-inner div.admonition :is(h3, h4).info { color: var(--infoHeadingBackground); } -.content-inner blockquote :is(h3, h4).neutral { +.content-inner div.admonition :is(h3, h4).neutral { color: var(--neutralHeadingBackground); } -.content-inner blockquote :is(h3, h4).tip { +.content-inner div.admonition :is(h3, h4).tip { color: var(--tipHeadingBackground); } -.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); From 7174cb31e4203f337ca244671e6b927464665b53 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 05:10:16 -0400 Subject: [PATCH 04/15] Make sure to test for admonition class --- test/ex_doc/markdown/earmark_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ex_doc/markdown/earmark_test.exs b/test/ex_doc/markdown/earmark_test.exs index fec19504d..c953b68d6 100644 --- a/test/ex_doc/markdown/earmark_test.exs +++ b/test/ex_doc/markdown/earmark_test.exs @@ -82,7 +82,7 @@ defmodule ExDoc.Markdown.EarmarkTest do """ assert Markdown.to_ast(info, []) == [ - {:div, [class: "info", role: "note"], + {:div, [class: "admonition info", role: "note"], [ {:h4, [class: "ignore info"], ["Info"], %{}}, {:p, [], ["This is info."], %{}} @@ -108,7 +108,7 @@ defmodule ExDoc.Markdown.EarmarkTest do """ assert Markdown.to_ast(warning_error, []) == [ - {:div, [class: "error warning", role: "note"], + {:div, [class: "admonition error warning", role: "note"], [ {:h3, [class: "error warning"], ["Warning! Error!"], %{}}, {:p, [], ["A warning and an error."], %{}} From 263c0821f6550a89d6af5f953e2a53ab40ed4d2a Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 05:23:54 -0400 Subject: [PATCH 05/15] Use section tags for admonitions --- assets/css/content/admonition.css | 48 +++++++++++++------------- assets/css/content/epub-admonition.css | 32 ++++++++--------- assets/css/content/general.css | 4 +-- lib/ex_doc/markdown/earmark.ex | 2 +- test/ex_doc/markdown/earmark_test.exs | 4 +-- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index 69ccf6282..ca59c338f 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -1,36 +1,36 @@ -.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { border-radius: 10px; border-left: 0; } -.content-inner div.admonition.warning { +.content-inner section.admonition.warning { background-color: var(--warningBackground); } -.content-inner div.admonition.error { +.content-inner section.admonition.error { background-color: var(--errorBackground); } -.content-inner div.admonition.info { +.content-inner section.admonition.info { background-color: var(--infoBackground); } -.content-inner div.admonition.neutral { +.content-inner section.admonition.neutral { background-color: var(--neutralBackground); } -.content-inner div.admonition.tip { +.content-inner section.admonition.tip { background-color: var(--tipBackground); } -.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--contrast); margin: 0 -1.2rem; padding: .7rem 1.2rem .7rem 3.3rem; font-weight: 700; font-style: normal; } -.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { color: var(--contrast); position: absolute; left: 1rem; @@ -41,74 +41,74 @@ -moz-osx-font-smoothing: grayscale; } -.content-inner div.admonition :is(h3, h4).warning { +.content-inner section.admonition :is(h3, h4).warning { background-color: var(--warningHeadingBackground); color: var(--warningHeading); } -.content-inner div.admonition :is(h3, h4).warning::before { +.content-inner section.admonition :is(h3, h4).warning::before { content: var(--icon-error-warning); color: var(--warningHeading); } -.content-inner div.admonition :is(h3, h4).error { +.content-inner section.admonition :is(h3, h4).error { background-color: var(--errorHeadingBackground); color: var(--errorHeading); } -.content-inner div.admonition :is(h3, h4).error::before { +.content-inner section.admonition :is(h3, h4).error::before { content: var(--icon-error-warning); color: var(--errorHeading); } -.content-inner div.admonition :is(h3, h4).info { +.content-inner section.admonition :is(h3, h4).info { background-color: var(--infoHeadingBackground); color: var(--infoHeading); } -.content-inner div.admonition :is(h3, h4).info::before { +.content-inner section.admonition :is(h3, h4).info::before { content: var(--icon-information); color: var(--infoHeading); } -.content-inner div.admonition :is(h3, h4).neutral { +.content-inner section.admonition :is(h3, h4).neutral { background-color: var(--neutralHeadingBackground); color: var(--neutralHeading); } -.content-inner div.admonition :is(h3, h4).neutral::before { +.content-inner section.admonition :is(h3, h4).neutral::before { content: var(--icon-double-quotes-l); color: var(--neutralHeading); } -.content-inner div.admonition :is(h3, h4).tip { +.content-inner section.admonition :is(h3, h4).tip { background-color: var(--tipHeadingBackground); color: var(--tipHeading); } -.content-inner div.admonition :is(h3, h4).tip::before { +.content-inner section.admonition :is(h3, h4).tip::before { content: var(--icon-information); color: var(--tipHeading); } -.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); } -.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { color: inherit; text-decoration-color: currentColor; } @media screen and (max-width: 768px) { - .content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { margin-left: calc(-1 * var(--content-gutter)); margin-right: calc(-1 * var(--content-gutter)); padding-left: var(--content-gutter); @@ -116,7 +116,7 @@ border-radius: 0; } - .content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { margin: 0 calc(-1 * var(--content-gutter)); } } diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index f243cb6c6..e36a60dce 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -1,4 +1,4 @@ -.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { border-left: solid 4px; color: var(--black); font-size: 0.9em; @@ -9,68 +9,68 @@ page-break-inside: avoid; } -.content-inner div.admonition.warning { +.content-inner section.admonition.warning { background-color: var(--warningBackground); border-left-color: var(--warningHeadingBackground); } -.content-inner div.admonition.error { +.content-inner section.admonition.error { background-color: var(--errorBackground); border-left-color: var(--errorHeadingBackground); } -.content-inner div.admonition.info { +.content-inner section.admonition.info { background-color: var(--infoBackground); border-left-color: var(--infoHeadingBackground); } -.content-inner div.admonition.neutral { +.content-inner section.admonition.neutral { background-color: var(--neutralBackground); border-left-color: var(--neutralHeadingBackground); } -.content-inner div.admonition.tip { +.content-inner section.admonition.tip { background-color: var(--tipBackground); border-left-color: var(--tipHeadingBackground); } -.content-inner div.admonition :is(h3, h4) { +.content-inner section.admonition :is(h3, h4) { font-weight: bold; margin: 0px 10px 5px 0px; } -.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { font-style: normal; font-weight: 700; } -.content-inner div.admonition :is(h3, h4).warning { +.content-inner section.admonition :is(h3, h4).warning { color: var(--warningHeadingBackground); } -.content-inner div.admonition :is(h3, h4).error { +.content-inner section.admonition :is(h3, h4).error { color: var(--errorHeadingBackground); } -.content-inner div.admonition :is(h3, h4).info { +.content-inner section.admonition :is(h3, h4).info { color: var(--infoHeadingBackground); } -.content-inner div.admonition :is(h3, h4).neutral { +.content-inner section.admonition :is(h3, h4).neutral { color: var(--neutralHeadingBackground); } -.content-inner div.admonition :is(h3, h4).tip { +.content-inner section.admonition :is(h3, h4).tip { color: var(--tipHeadingBackground); } -.content-inner div.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner div.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); diff --git a/assets/css/content/general.css b/assets/css/content/general.css index 1cf02b9cb..e00523a49 100644 --- a/assets/css/content/general.css +++ b/assets/css/content/general.css @@ -97,7 +97,7 @@ font-weight: normal; } -.content-inner blockquote, .content-inner div.admonition { +.content-inner blockquote, .content-inner section.admonition { border-left: 3px solid var(--blockquoteBorder); position: relative; margin: 1.5625em 0; @@ -106,7 +106,7 @@ background-color: var(--blockquoteBackground); border-radius: var(--borderRadius); } -.content-inner blockquote p:last-child { +.content-inner blockquote p:last-child, .content-inner section.admonition p:last-child { padding-bottom: 1em; margin-bottom: 0; } diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index 662460795..b6648844b 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -109,7 +109,7 @@ defmodule ExDoc.Markdown.Earmark do [{"class", classes}, {"role", "note"} | attrs] end - fixup({"div", blockquote_attrs, [h_elem | rest], blockquote_meta}) + fixup({"section", blockquote_attrs, [h_elem | rest], blockquote_meta}) else # regular blockquote, copied fixup/1 here to avoid infinite loop {:blockquote, Enum.map(blockquote_attrs, &fixup_attr/1), fixup(ast), blockquote_meta} diff --git a/test/ex_doc/markdown/earmark_test.exs b/test/ex_doc/markdown/earmark_test.exs index c953b68d6..d0c8b1cee 100644 --- a/test/ex_doc/markdown/earmark_test.exs +++ b/test/ex_doc/markdown/earmark_test.exs @@ -82,7 +82,7 @@ defmodule ExDoc.Markdown.EarmarkTest do """ assert Markdown.to_ast(info, []) == [ - {:div, [class: "admonition info", role: "note"], + {:section, [class: "admonition info", role: "note"], [ {:h4, [class: "ignore info"], ["Info"], %{}}, {:p, [], ["This is info."], %{}} @@ -108,7 +108,7 @@ defmodule ExDoc.Markdown.EarmarkTest do """ assert Markdown.to_ast(warning_error, []) == [ - {:div, [class: "admonition error warning", role: "note"], + {:section, [class: "admonition error warning", role: "note"], [ {:h3, [class: "error warning"], ["Warning! Error!"], %{}}, {:p, [], ["A warning and an error."], %{}} From 7f61e1b2edd24bccefb28a71cc69cb6f6a0ba690 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 09:45:56 -0400 Subject: [PATCH 06/15] Update print and tabset styles --- assets/css/print.css | 4 ++-- assets/css/tabset.css | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/assets/css/print.css b/assets/css/print.css index 94a8c522b..6582f2947 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -52,11 +52,11 @@ display: none; } - .content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { border: 2px solid var(--gray400); } - .content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--textHeaders); border-bottom: 2px solid var(--gray400); } diff --git a/assets/css/tabset.css b/assets/css/tabset.css index 63cf54067..697e0ef16 100644 --- a/assets/css/tabset.css +++ b/assets/css/tabset.css @@ -58,7 +58,8 @@ } .tabset-panel pre, - .tabset-panel blockquote { + .tabset-panel blockquote, + .tabset-panel section.admonition { margin-left: calc(-1 * var(--tabsetPadding)) !important; margin-right: calc(-1 * var(--tabsetPadding)) !important; } From 30dc41b3309e71ab2ad6adf5c6544179d9d3d641 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 10:27:51 -0400 Subject: [PATCH 07/15] Simplify section.admonition to .admonition --- assets/css/content/admonition.css | 48 +++++++++++++------------- assets/css/content/epub-admonition.css | 32 ++++++++--------- assets/css/content/general.css | 4 +-- assets/css/print.css | 4 +-- assets/css/tabset.css | 2 +- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index ca59c338f..451934c1a 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -1,36 +1,36 @@ -.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { +.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { border-radius: 10px; border-left: 0; } -.content-inner section.admonition.warning { +.content-inner .admonition.warning { background-color: var(--warningBackground); } -.content-inner section.admonition.error { +.content-inner .admonition.error { background-color: var(--errorBackground); } -.content-inner section.admonition.info { +.content-inner .admonition.info { background-color: var(--infoBackground); } -.content-inner section.admonition.neutral { +.content-inner .admonition.neutral { background-color: var(--neutralBackground); } -.content-inner section.admonition.tip { +.content-inner .admonition.tip { background-color: var(--tipBackground); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--contrast); margin: 0 -1.2rem; padding: .7rem 1.2rem .7rem 3.3rem; font-weight: 700; font-style: normal; } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { +.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { color: var(--contrast); position: absolute; left: 1rem; @@ -41,74 +41,74 @@ -moz-osx-font-smoothing: grayscale; } -.content-inner section.admonition :is(h3, h4).warning { +.content-inner .admonition :is(h3, h4).warning { background-color: var(--warningHeadingBackground); color: var(--warningHeading); } -.content-inner section.admonition :is(h3, h4).warning::before { +.content-inner .admonition :is(h3, h4).warning::before { content: var(--icon-error-warning); color: var(--warningHeading); } -.content-inner section.admonition :is(h3, h4).error { +.content-inner .admonition :is(h3, h4).error { background-color: var(--errorHeadingBackground); color: var(--errorHeading); } -.content-inner section.admonition :is(h3, h4).error::before { +.content-inner .admonition :is(h3, h4).error::before { content: var(--icon-error-warning); color: var(--errorHeading); } -.content-inner section.admonition :is(h3, h4).info { +.content-inner .admonition :is(h3, h4).info { background-color: var(--infoHeadingBackground); color: var(--infoHeading); } -.content-inner section.admonition :is(h3, h4).info::before { +.content-inner .admonition :is(h3, h4).info::before { content: var(--icon-information); color: var(--infoHeading); } -.content-inner section.admonition :is(h3, h4).neutral { +.content-inner .admonition :is(h3, h4).neutral { background-color: var(--neutralHeadingBackground); color: var(--neutralHeading); } -.content-inner section.admonition :is(h3, h4).neutral::before { +.content-inner .admonition :is(h3, h4).neutral::before { content: var(--icon-double-quotes-l); color: var(--neutralHeading); } -.content-inner section.admonition :is(h3, h4).tip { +.content-inner .admonition :is(h3, h4).tip { background-color: var(--tipHeadingBackground); color: var(--tipHeading); } -.content-inner section.admonition :is(h3, h4).tip::before { +.content-inner .admonition :is(h3, h4).tip::before { content: var(--icon-information); color: var(--tipHeading); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { +.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { color: inherit; text-decoration-color: currentColor; } @media screen and (max-width: 768px) { - .content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { + .content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { margin-left: calc(-1 * var(--content-gutter)); margin-right: calc(-1 * var(--content-gutter)); padding-left: var(--content-gutter); @@ -116,7 +116,7 @@ border-radius: 0; } - .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { margin: 0 calc(-1 * var(--content-gutter)); } } diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index e36a60dce..fa5a991e8 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -1,4 +1,4 @@ -.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { +.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { border-left: solid 4px; color: var(--black); font-size: 0.9em; @@ -9,68 +9,68 @@ page-break-inside: avoid; } -.content-inner section.admonition.warning { +.content-inner .admonition.warning { background-color: var(--warningBackground); border-left-color: var(--warningHeadingBackground); } -.content-inner section.admonition.error { +.content-inner .admonition.error { background-color: var(--errorBackground); border-left-color: var(--errorHeadingBackground); } -.content-inner section.admonition.info { +.content-inner .admonition.info { background-color: var(--infoBackground); border-left-color: var(--infoHeadingBackground); } -.content-inner section.admonition.neutral { +.content-inner .admonition.neutral { background-color: var(--neutralBackground); border-left-color: var(--neutralHeadingBackground); } -.content-inner section.admonition.tip { +.content-inner .admonition.tip { background-color: var(--tipBackground); border-left-color: var(--tipHeadingBackground); } -.content-inner section.admonition :is(h3, h4) { +.content-inner .admonition :is(h3, h4) { font-weight: bold; margin: 0px 10px 5px 0px; } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { font-style: normal; font-weight: 700; } -.content-inner section.admonition :is(h3, h4).warning { +.content-inner .admonition :is(h3, h4).warning { color: var(--warningHeadingBackground); } -.content-inner section.admonition :is(h3, h4).error { +.content-inner .admonition :is(h3, h4).error { color: var(--errorHeadingBackground); } -.content-inner section.admonition :is(h3, h4).info { +.content-inner .admonition :is(h3, h4).info { color: var(--infoHeadingBackground); } -.content-inner section.admonition :is(h3, h4).neutral { +.content-inner .admonition :is(h3, h4).neutral { color: var(--neutralHeadingBackground); } -.content-inner section.admonition :is(h3, h4).tip { +.content-inner .admonition :is(h3, h4).tip { color: var(--tipHeadingBackground); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); diff --git a/assets/css/content/general.css b/assets/css/content/general.css index e00523a49..3ee60237c 100644 --- a/assets/css/content/general.css +++ b/assets/css/content/general.css @@ -97,7 +97,7 @@ font-weight: normal; } -.content-inner blockquote, .content-inner section.admonition { +.content-inner blockquote, .content-inner .admonition { border-left: 3px solid var(--blockquoteBorder); position: relative; margin: 1.5625em 0; @@ -106,7 +106,7 @@ background-color: var(--blockquoteBackground); border-radius: var(--borderRadius); } -.content-inner blockquote p:last-child, .content-inner section.admonition p:last-child { +.content-inner blockquote p:last-child, .content-inner .admonition p:last-child { padding-bottom: 1em; margin-bottom: 0; } diff --git a/assets/css/print.css b/assets/css/print.css index 6582f2947..4dea6cdfe 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -52,11 +52,11 @@ display: none; } - .content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { + .content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { border: 2px solid var(--gray400); } - .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--textHeaders); border-bottom: 2px solid var(--gray400); } diff --git a/assets/css/tabset.css b/assets/css/tabset.css index 697e0ef16..63354e676 100644 --- a/assets/css/tabset.css +++ b/assets/css/tabset.css @@ -59,7 +59,7 @@ .tabset-panel pre, .tabset-panel blockquote, - .tabset-panel section.admonition { + .tabset-panel .admonition { margin-left: calc(-1 * var(--tabsetPadding)) !important; margin-right: calc(-1 * var(--tabsetPadding)) !important; } From c586c8eaad00a0dcda1b0cc39081399b13b5d896 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 10:52:04 -0400 Subject: [PATCH 08/15] Simplify admonition styles further --- assets/css/content/admonition.css | 6 +++--- assets/css/content/epub-admonition.css | 6 +++--- assets/css/print.css | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index 451934c1a..ee0566c71 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -1,4 +1,4 @@ -.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { +.content-inner .admonition { border-radius: 10px; border-left: 0; } @@ -90,13 +90,13 @@ margin: 0 0.5ch; } -.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner .admonition code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner .admonition pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index fa5a991e8..96330675a 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -1,4 +1,4 @@ -.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { +.content-inner .admonition { border-left: solid 4px; color: var(--black); font-size: 0.9em; @@ -64,13 +64,13 @@ margin: 0 0.5ch; } -.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) code { +.content-inner .admonition code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) pre code { +.content-inner .admonition pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); diff --git a/assets/css/print.css b/assets/css/print.css index 4dea6cdfe..f2a38eca5 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -52,7 +52,7 @@ display: none; } - .content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { + .content-inner .admonition { border: 2px solid var(--gray400); } From b907d27ff4bfb08d4e0a6fa7c99e1d68aed7b39c Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 10:53:55 -0400 Subject: [PATCH 09/15] Replace .admonition with section.admonition --- assets/css/content/admonition.css | 48 +++++++++++++------------- assets/css/content/epub-admonition.css | 32 ++++++++--------- assets/css/content/general.css | 4 +-- assets/css/print.css | 4 +-- assets/css/tabset.css | 2 +- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index ee0566c71..24e6e8ff6 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -1,36 +1,36 @@ -.content-inner .admonition { +.content-inner section.admonition { border-radius: 10px; border-left: 0; } -.content-inner .admonition.warning { +.content-inner section.admonition.warning { background-color: var(--warningBackground); } -.content-inner .admonition.error { +.content-inner section.admonition.error { background-color: var(--errorBackground); } -.content-inner .admonition.info { +.content-inner section.admonition.info { background-color: var(--infoBackground); } -.content-inner .admonition.neutral { +.content-inner section.admonition.neutral { background-color: var(--neutralBackground); } -.content-inner .admonition.tip { +.content-inner section.admonition.tip { background-color: var(--tipBackground); } -.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--contrast); margin: 0 -1.2rem; padding: .7rem 1.2rem .7rem 3.3rem; font-weight: 700; font-style: normal; } -.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { color: var(--contrast); position: absolute; left: 1rem; @@ -41,74 +41,74 @@ -moz-osx-font-smoothing: grayscale; } -.content-inner .admonition :is(h3, h4).warning { +.content-inner section.admonition :is(h3, h4).warning { background-color: var(--warningHeadingBackground); color: var(--warningHeading); } -.content-inner .admonition :is(h3, h4).warning::before { +.content-inner section.admonition :is(h3, h4).warning::before { content: var(--icon-error-warning); color: var(--warningHeading); } -.content-inner .admonition :is(h3, h4).error { +.content-inner section.admonition :is(h3, h4).error { background-color: var(--errorHeadingBackground); color: var(--errorHeading); } -.content-inner .admonition :is(h3, h4).error::before { +.content-inner section.admonition :is(h3, h4).error::before { content: var(--icon-error-warning); color: var(--errorHeading); } -.content-inner .admonition :is(h3, h4).info { +.content-inner section.admonition :is(h3, h4).info { background-color: var(--infoHeadingBackground); color: var(--infoHeading); } -.content-inner .admonition :is(h3, h4).info::before { +.content-inner section.admonition :is(h3, h4).info::before { content: var(--icon-information); color: var(--infoHeading); } -.content-inner .admonition :is(h3, h4).neutral { +.content-inner section.admonition :is(h3, h4).neutral { background-color: var(--neutralHeadingBackground); color: var(--neutralHeading); } -.content-inner .admonition :is(h3, h4).neutral::before { +.content-inner section.admonition :is(h3, h4).neutral::before { content: var(--icon-double-quotes-l); color: var(--neutralHeading); } -.content-inner .admonition :is(h3, h4).tip { +.content-inner section.admonition :is(h3, h4).tip { background-color: var(--tipHeadingBackground); color: var(--tipHeading); } -.content-inner .admonition :is(h3, h4).tip::before { +.content-inner section.admonition :is(h3, h4).tip::before { content: var(--icon-information); color: var(--tipHeading); } -.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner .admonition code { +.content-inner section.admonition code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner .admonition pre code { +.content-inner section.admonition pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); } -.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { color: inherit; text-decoration-color: currentColor; } @media screen and (max-width: 768px) { - .content-inner .admonition:is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { margin-left: calc(-1 * var(--content-gutter)); margin-right: calc(-1 * var(--content-gutter)); padding-left: var(--content-gutter); @@ -116,7 +116,7 @@ border-radius: 0; } - .content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { margin: 0 calc(-1 * var(--content-gutter)); } } diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index 96330675a..a2be0dc54 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -1,4 +1,4 @@ -.content-inner .admonition { +.content-inner section.admonition { border-left: solid 4px; color: var(--black); font-size: 0.9em; @@ -9,68 +9,68 @@ page-break-inside: avoid; } -.content-inner .admonition.warning { +.content-inner section.admonition.warning { background-color: var(--warningBackground); border-left-color: var(--warningHeadingBackground); } -.content-inner .admonition.error { +.content-inner section.admonition.error { background-color: var(--errorBackground); border-left-color: var(--errorHeadingBackground); } -.content-inner .admonition.info { +.content-inner section.admonition.info { background-color: var(--infoBackground); border-left-color: var(--infoHeadingBackground); } -.content-inner .admonition.neutral { +.content-inner section.admonition.neutral { background-color: var(--neutralBackground); border-left-color: var(--neutralHeadingBackground); } -.content-inner .admonition.tip { +.content-inner section.admonition.tip { background-color: var(--tipBackground); border-left-color: var(--tipHeadingBackground); } -.content-inner .admonition :is(h3, h4) { +.content-inner section.admonition :is(h3, h4) { font-weight: bold; margin: 0px 10px 5px 0px; } -.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { font-style: normal; font-weight: 700; } -.content-inner .admonition :is(h3, h4).warning { +.content-inner section.admonition :is(h3, h4).warning { color: var(--warningHeadingBackground); } -.content-inner .admonition :is(h3, h4).error { +.content-inner section.admonition :is(h3, h4).error { color: var(--errorHeadingBackground); } -.content-inner .admonition :is(h3, h4).info { +.content-inner section.admonition :is(h3, h4).info { color: var(--infoHeadingBackground); } -.content-inner .admonition :is(h3, h4).neutral { +.content-inner section.admonition :is(h3, h4).neutral { color: var(--neutralHeadingBackground); } -.content-inner .admonition :is(h3, h4).tip { +.content-inner section.admonition :is(h3, h4).tip { color: var(--tipHeadingBackground); } -.content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { margin: 0 0.5ch; } -.content-inner .admonition code { +.content-inner section.admonition code { background-color: var(--admInlineCodeBackground); border: 1px solid var(--admInlineCodeBorder); color: var(--admInlineCodeColor); } -.content-inner .admonition pre code { +.content-inner section.admonition pre code { background-color: var(--admCodeBackground); border: 1px solid var(--admCodeBorder); color: var(--admCodeColor); diff --git a/assets/css/content/general.css b/assets/css/content/general.css index 3ee60237c..e00523a49 100644 --- a/assets/css/content/general.css +++ b/assets/css/content/general.css @@ -97,7 +97,7 @@ font-weight: normal; } -.content-inner blockquote, .content-inner .admonition { +.content-inner blockquote, .content-inner section.admonition { border-left: 3px solid var(--blockquoteBorder); position: relative; margin: 1.5625em 0; @@ -106,7 +106,7 @@ background-color: var(--blockquoteBackground); border-radius: var(--borderRadius); } -.content-inner blockquote p:last-child, .content-inner .admonition p:last-child { +.content-inner blockquote p:last-child, .content-inner section.admonition p:last-child { padding-bottom: 1em; margin-bottom: 0; } diff --git a/assets/css/print.css b/assets/css/print.css index f2a38eca5..deeb3868f 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -52,11 +52,11 @@ display: none; } - .content-inner .admonition { + .content-inner section.admonition { border: 2px solid var(--gray400); } - .content-inner .admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { color: var(--textHeaders); border-bottom: 2px solid var(--gray400); } diff --git a/assets/css/tabset.css b/assets/css/tabset.css index 63354e676..697e0ef16 100644 --- a/assets/css/tabset.css +++ b/assets/css/tabset.css @@ -59,7 +59,7 @@ .tabset-panel pre, .tabset-panel blockquote, - .tabset-panel .admonition { + .tabset-panel section.admonition { margin-left: calc(-1 * var(--tabsetPadding)) !important; margin-right: calc(-1 * var(--tabsetPadding)) !important; } From 8f4b5d813e45a5fe57101f611418903f8929e6cf Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Mon, 12 Aug 2024 12:09:54 -0400 Subject: [PATCH 10/15] Simplify remaining styles --- assets/css/content/admonition.css | 32 +++++++++++++------------- assets/css/content/epub-admonition.css | 17 ++++++-------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index 24e6e8ff6..4a7dd7062 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -23,14 +23,14 @@ background-color: var(--tipBackground); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { +.content-inner section.admonition > :first-child { color: var(--contrast); margin: 0 -1.2rem; padding: .7rem 1.2rem .7rem 3.3rem; font-weight: 700; font-style: normal; } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before { +.content-inner section.admonition > :first-child::before { color: var(--contrast); position: absolute; left: 1rem; @@ -41,52 +41,52 @@ -moz-osx-font-smoothing: grayscale; } -.content-inner section.admonition :is(h3, h4).warning { +.content-inner section.admonition > :first-child.warning { background-color: var(--warningHeadingBackground); color: var(--warningHeading); } -.content-inner section.admonition :is(h3, h4).warning::before { +.content-inner section.admonition > :first-child.warning::before { content: var(--icon-error-warning); color: var(--warningHeading); } -.content-inner section.admonition :is(h3, h4).error { +.content-inner section.admonition > :first-child.error { background-color: var(--errorHeadingBackground); color: var(--errorHeading); } -.content-inner section.admonition :is(h3, h4).error::before { +.content-inner section.admonition > :first-child.error::before { content: var(--icon-error-warning); color: var(--errorHeading); } -.content-inner section.admonition :is(h3, h4).info { +.content-inner section.admonition > :first-child.info { background-color: var(--infoHeadingBackground); color: var(--infoHeading); } -.content-inner section.admonition :is(h3, h4).info::before { +.content-inner section.admonition > :first-child.info::before { content: var(--icon-information); color: var(--infoHeading); } -.content-inner section.admonition :is(h3, h4).neutral { +.content-inner section.admonition > :first-child.neutral { background-color: var(--neutralHeadingBackground); color: var(--neutralHeading); } -.content-inner section.admonition :is(h3, h4).neutral::before { +.content-inner section.admonition > :first-child.neutral::before { content: var(--icon-double-quotes-l); color: var(--neutralHeading); } -.content-inner section.admonition :is(h3, h4).tip { +.content-inner section.admonition > :first-child.tip { background-color: var(--tipHeadingBackground); color: var(--tipHeading); } -.content-inner section.admonition :is(h3, h4).tip::before { +.content-inner section.admonition > :first-child.tip::before { content: var(--icon-information); color: var(--tipHeading); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition > :first-child code { margin: 0 0.5ch; } @@ -102,13 +102,13 @@ color: var(--admCodeColor); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) { +.content-inner section.admonition > :first-child :is(a, a:visited) { color: inherit; text-decoration-color: currentColor; } @media screen and (max-width: 768px) { - .content-inner section.admonition:is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition { margin-left: calc(-1 * var(--content-gutter)); margin-right: calc(-1 * var(--content-gutter)); padding-left: var(--content-gutter); @@ -116,7 +116,7 @@ border-radius: 0; } - .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition > :first-child { margin: 0 calc(-1 * var(--content-gutter)); } } diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index a2be0dc54..0151a7623 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -34,33 +34,30 @@ border-left-color: var(--tipHeadingBackground); } -.content-inner section.admonition :is(h3, h4) { +.content-inner section.admonition > :first-child { font-weight: bold; margin: 0px 10px 5px 0px; -} - -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { font-style: normal; font-weight: 700; } -.content-inner section.admonition :is(h3, h4).warning { +.content-inner section.admonition > :first-child.warning { color: var(--warningHeadingBackground); } -.content-inner section.admonition :is(h3, h4).error { +.content-inner section.admonition > :first-child.error { color: var(--errorHeadingBackground); } -.content-inner section.admonition :is(h3, h4).info { +.content-inner section.admonition > :first-child.info { color: var(--infoHeadingBackground); } -.content-inner section.admonition :is(h3, h4).neutral { +.content-inner section.admonition > :first-child.neutral { color: var(--neutralHeadingBackground); } -.content-inner section.admonition :is(h3, h4).tip { +.content-inner section.admonition > :first-child.tip { color: var(--tipHeadingBackground); } -.content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code { +.content-inner section.admonition > :first-child code { margin: 0 0.5ch; } From 3888ab07a25999112b33e8b2a60af88e7065138a Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Tue, 13 Aug 2024 02:59:41 -0400 Subject: [PATCH 11/15] Add missed selector --- assets/css/print.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/css/print.css b/assets/css/print.css index deeb3868f..8d7da6dd1 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -56,7 +56,7 @@ border: 2px solid var(--gray400); } - .content-inner section.admonition :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) { + .content-inner section.admonition > :first-child { color: var(--textHeaders); border-bottom: 2px solid var(--gray400); } From 0d9ad85a26a70ca4cd9e548e94bafc3161893e39 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Thu, 15 Aug 2024 09:20:05 -0400 Subject: [PATCH 12/15] Identifiy admonition headings with a class This is a common practice with other markdown/documentation generators. --- assets/css/content/admonition.css | 30 ++++++------- assets/css/content/epub-admonition.css | 14 +++---- assets/css/print.css | 2 +- lib/ex_doc/markdown/earmark.ex | 58 +++++++++++++------------- test/ex_doc/markdown/earmark_test.exs | 4 +- 5 files changed, 54 insertions(+), 54 deletions(-) diff --git a/assets/css/content/admonition.css b/assets/css/content/admonition.css index 4a7dd7062..a7ae3aee0 100644 --- a/assets/css/content/admonition.css +++ b/assets/css/content/admonition.css @@ -23,14 +23,14 @@ background-color: var(--tipBackground); } -.content-inner section.admonition > :first-child { +.content-inner section.admonition > .admonition-title { color: var(--contrast); margin: 0 -1.2rem; padding: .7rem 1.2rem .7rem 3.3rem; font-weight: 700; font-style: normal; } -.content-inner section.admonition > :first-child::before { +.content-inner section.admonition > .admonition-title::before { color: var(--contrast); position: absolute; left: 1rem; @@ -41,52 +41,52 @@ -moz-osx-font-smoothing: grayscale; } -.content-inner section.admonition > :first-child.warning { +.content-inner section.admonition > .admonition-title.warning { background-color: var(--warningHeadingBackground); color: var(--warningHeading); } -.content-inner section.admonition > :first-child.warning::before { +.content-inner section.admonition > .admonition-title.warning::before { content: var(--icon-error-warning); color: var(--warningHeading); } -.content-inner section.admonition > :first-child.error { +.content-inner section.admonition > .admonition-title.error { background-color: var(--errorHeadingBackground); color: var(--errorHeading); } -.content-inner section.admonition > :first-child.error::before { +.content-inner section.admonition > .admonition-title.error::before { content: var(--icon-error-warning); color: var(--errorHeading); } -.content-inner section.admonition > :first-child.info { +.content-inner section.admonition > .admonition-title.info { background-color: var(--infoHeadingBackground); color: var(--infoHeading); } -.content-inner section.admonition > :first-child.info::before { +.content-inner section.admonition > .admonition-title.info::before { content: var(--icon-information); color: var(--infoHeading); } -.content-inner section.admonition > :first-child.neutral { +.content-inner section.admonition > .admonition-title.neutral { background-color: var(--neutralHeadingBackground); color: var(--neutralHeading); } -.content-inner section.admonition > :first-child.neutral::before { +.content-inner section.admonition > .admonition-title.neutral::before { content: var(--icon-double-quotes-l); color: var(--neutralHeading); } -.content-inner section.admonition > :first-child.tip { +.content-inner section.admonition > .admonition-title.tip { background-color: var(--tipHeadingBackground); color: var(--tipHeading); } -.content-inner section.admonition > :first-child.tip::before { +.content-inner section.admonition > .admonition-title.tip::before { content: var(--icon-information); color: var(--tipHeading); } -.content-inner section.admonition > :first-child code { +.content-inner section.admonition > .admonition-title code { margin: 0 0.5ch; } @@ -102,7 +102,7 @@ color: var(--admCodeColor); } -.content-inner section.admonition > :first-child :is(a, a:visited) { +.content-inner section.admonition > .admonition-title :is(a, a:visited) { color: inherit; text-decoration-color: currentColor; } @@ -116,7 +116,7 @@ border-radius: 0; } - .content-inner section.admonition > :first-child { + .content-inner section.admonition > .admonition-title { margin: 0 calc(-1 * var(--content-gutter)); } } diff --git a/assets/css/content/epub-admonition.css b/assets/css/content/epub-admonition.css index 0151a7623..be7cefe62 100755 --- a/assets/css/content/epub-admonition.css +++ b/assets/css/content/epub-admonition.css @@ -34,30 +34,30 @@ border-left-color: var(--tipHeadingBackground); } -.content-inner section.admonition > :first-child { +.content-inner section.admonition > .admonition-title { font-weight: bold; margin: 0px 10px 5px 0px; font-style: normal; font-weight: 700; } -.content-inner section.admonition > :first-child.warning { +.content-inner section.admonition > .admonition-title.warning { color: var(--warningHeadingBackground); } -.content-inner section.admonition > :first-child.error { +.content-inner section.admonition > .admonition-title.error { color: var(--errorHeadingBackground); } -.content-inner section.admonition > :first-child.info { +.content-inner section.admonition > .admonition-title.info { color: var(--infoHeadingBackground); } -.content-inner section.admonition > :first-child.neutral { +.content-inner section.admonition > .admonition-title.neutral { color: var(--neutralHeadingBackground); } -.content-inner section.admonition > :first-child.tip { +.content-inner section.admonition > .admonition-title.tip { color: var(--tipHeadingBackground); } -.content-inner section.admonition > :first-child code { +.content-inner section.admonition > .admonition-title code { margin: 0 0.5ch; } diff --git a/assets/css/print.css b/assets/css/print.css index 8d7da6dd1..cc579c3f3 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -56,7 +56,7 @@ border: 2px solid var(--gray400); } - .content-inner section.admonition > :first-child { + .content-inner section.admonition > .admonition-title { color: var(--textHeaders); border-bottom: 2px solid var(--gray400); } diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index b6648844b..650af3880 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -76,38 +76,38 @@ defmodule ExDoc.Markdown.Earmark do "$$\n#{content}\n$$" end - # Convert admonition blockquotes to divs for screen reader accessibility + # Convert admonition blockquotes to sections for screen reader accessibility defp fixup( - {"blockquote", blockquote_attrs, - [{tag, h_attrs, _h_content, _h_meta} = h_elem | rest] = ast, blockquote_meta} + {"blockquote", blockquote_attrs, [{tag, h_attrs, h_content, h_meta} | rest] = ast, + blockquote_meta} ) when tag in ["h3", "h4"] do - h_admonition_classes = - h_attrs - |> Enum.find("", &match?({"class", _}, &1)) - |> then(fn - "" -> - "" - - {"class", classes} -> - classes - |> String.split(" ") - |> Enum.filter(&(&1 in @admonition_classes)) - |> Enum.join(" ") - end) - - if h_admonition_classes != "" do - admonition_classes = "admonition #{h_admonition_classes}" - - blockquote_attrs = - case Enum.split_with(blockquote_attrs, &match?({"class", _}, &1)) do - {[], attrs} -> - [{"class", admonition_classes}, {"role", "note"} | attrs] - - {[{"class", classes}], attrs} -> - classes = String.trim_trailing(classes) <> " #{admonition_classes}" - [{"class", classes}, {"role", "note"} | attrs] - end + {h_classes, h_attrs} = Enum.split_with(h_attrs, &match?({"class", _}, &1)) + + h_admonition = + with [{"class", classes}] <- h_classes, + class_list <- String.split(classes), + adm_classes = [_ | _] <- Enum.filter(class_list, &(&1 in @admonition_classes)) do + {Enum.join(adm_classes, " "), classes} + else + [] -> nil + end + + blockquote_attrs_fn = fn admonition_classes -> + case Enum.split_with(blockquote_attrs, &match?({"class", _}, &1)) do + {[], attrs} -> + [{"class", admonition_classes}, {"role", "note"} | attrs] + + {[{"class", classes}], attrs} -> + classes = String.trim_trailing(classes) <> " #{admonition_classes}" + [{"class", classes}, {"role", "note"} | attrs] + end + end + + if h_admonition do + {h_admonition_classes, h_classes} = h_admonition + blockquote_attrs = blockquote_attrs_fn.("admonition #{h_admonition_classes}") + h_elem = {tag, [{"class", "admonition-title #{h_classes}"} | h_attrs], h_content, h_meta} fixup({"section", blockquote_attrs, [h_elem | rest], blockquote_meta}) else diff --git a/test/ex_doc/markdown/earmark_test.exs b/test/ex_doc/markdown/earmark_test.exs index d0c8b1cee..720a51830 100644 --- a/test/ex_doc/markdown/earmark_test.exs +++ b/test/ex_doc/markdown/earmark_test.exs @@ -84,7 +84,7 @@ defmodule ExDoc.Markdown.EarmarkTest do assert Markdown.to_ast(info, []) == [ {:section, [class: "admonition info", role: "note"], [ - {:h4, [class: "ignore info"], ["Info"], %{}}, + {:h4, [class: "admonition-title ignore info"], ["Info"], %{}}, {:p, [], ["This is info."], %{}} ], %{}} ] @@ -110,7 +110,7 @@ defmodule ExDoc.Markdown.EarmarkTest do assert Markdown.to_ast(warning_error, []) == [ {:section, [class: "admonition error warning", role: "note"], [ - {:h3, [class: "error warning"], ["Warning! Error!"], %{}}, + {:h3, [class: "admonition-title error warning"], ["Warning! Error!"], %{}}, {:p, [], ["A warning and an error."], %{}} ], %{}} ] From bf032c39bebf0eaa1edac5cfdac94e89ecee9d04 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Fri, 16 Aug 2024 07:44:50 -0400 Subject: [PATCH 13/15] Use List.key*/*, re-organize tests keyfind/keystore didn't really improve things that much, so using keytake/prepend as originally suggested --- lib/ex_doc/markdown/earmark.ex | 35 +++++------ test/ex_doc/markdown/earmark_test.exs | 85 +++++++++++++++++++++------ 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index 650af3880..fc5910727 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -82,34 +82,35 @@ defmodule ExDoc.Markdown.Earmark do blockquote_meta} ) when tag in ["h3", "h4"] do - {h_classes, h_attrs} = Enum.split_with(h_attrs, &match?({"class", _}, &1)) - h_admonition = - with [{"class", classes}] <- h_classes, + with {{"class", classes}, attrs} <- List.keytake(h_attrs, "class", 0), class_list <- String.split(classes), adm_classes = [_ | _] <- Enum.filter(class_list, &(&1 in @admonition_classes)) do - {Enum.join(adm_classes, " "), classes} + {"admonition " <> Enum.join(adm_classes, " "), + [{"class", "admonition-title #{classes}"} | attrs]} else - [] -> nil + _ -> nil end - blockquote_attrs_fn = fn admonition_classes -> - case Enum.split_with(blockquote_attrs, &match?({"class", _}, &1)) do - {[], attrs} -> - [{"class", admonition_classes}, {"role", "note"} | attrs] + section_attrs_fn = fn admonition_classes -> + {classes, attrs} = + case List.keytake(blockquote_attrs, "class", 0) do + nil -> + {admonition_classes, blockquote_attrs} - {[{"class", classes}], attrs} -> - classes = String.trim_trailing(classes) <> " #{admonition_classes}" - [{"class", classes}, {"role", "note"} | attrs] - end + {{"class", classes}, attrs} -> + {"#{admonition_classes} #{classes}", attrs} + end + + [{"role", "note"}, {"class", classes} | attrs] end if h_admonition do - {h_admonition_classes, h_classes} = h_admonition - blockquote_attrs = blockquote_attrs_fn.("admonition #{h_admonition_classes}") - h_elem = {tag, [{"class", "admonition-title #{h_classes}"} | h_attrs], h_content, h_meta} + {admonition_classes, h_attrs} = h_admonition + section_attrs = section_attrs_fn.(admonition_classes) + h_elem = {tag, h_attrs, h_content, h_meta} - fixup({"section", blockquote_attrs, [h_elem | rest], blockquote_meta}) + fixup({"section", section_attrs, [h_elem | rest], blockquote_meta}) else # regular blockquote, copied fixup/1 here to avoid infinite loop {:blockquote, Enum.map(blockquote_attrs, &fixup_attr/1), fixup(ast), blockquote_meta} diff --git a/test/ex_doc/markdown/earmark_test.exs b/test/ex_doc/markdown/earmark_test.exs index 720a51830..31649241e 100644 --- a/test/ex_doc/markdown/earmark_test.exs +++ b/test/ex_doc/markdown/earmark_test.exs @@ -75,20 +75,7 @@ defmodule ExDoc.Markdown.EarmarkTest do ] end - test "converts blockquote admonitions to regular divs" do - info = """ - > #### Info {: .info .ignore} - > This is info. - """ - - assert Markdown.to_ast(info, []) == [ - {:section, [class: "admonition info", role: "note"], - [ - {:h4, [class: "admonition-title ignore info"], ["Info"], %{}}, - {:p, [], ["This is info."], %{}} - ], %{}} - ] - + test "leaves blockquotes with the wrong markup as is" do not_admonition = """ > ### H3 {: .xyz} > This is NOT an admonition! @@ -102,18 +89,80 @@ defmodule ExDoc.Markdown.EarmarkTest do ], %{}} ] + no_h_tag_beginning = """ + > {: .warning} + > #### Warning {: .warning} + > This blockquote didn't start with the h4 tag, so it wasn't converted. + """ + + assert Markdown.to_ast(no_h_tag_beginning, []) == [ + {:blockquote, [], + [ + {:p, [], ["{: .warning}"], %{}}, + {:h4, [class: "warning"], ["Warning"], %{}}, + {:p, [], + ["This blockquote didn't start with the h4 tag, so it wasn't converted."], %{}} + ], %{}} + ] + end + + test "converts blockquotes with the appropriate markup to admonition sections" do + info = """ + > #### Info {: .info .ignore} + > This is info. + """ + + assert [ + {:section, section_attrs, + [ + {:h4, h_attrs, ["Info"], %{}}, + {:p, [], ["This is info."], %{}} + ], %{}} + ] = Markdown.to_ast(info, []) + + assert section_attrs[:role] == "note" + assert section_attrs[:class] == "admonition info" + + assert h_attrs[:class] == "admonition-title ignore info" + warning_error = """ > ### Warning! Error! {: .warning .error} > A warning and an error. """ - assert Markdown.to_ast(warning_error, []) == [ - {:section, [class: "admonition error warning", role: "note"], + assert [ + {:section, section_attrs, [ - {:h3, [class: "admonition-title error warning"], ["Warning! Error!"], %{}}, + {:h3, h_attrs, ["Warning! Error!"], %{}}, {:p, [], ["A warning and an error."], %{}} ], %{}} - ] + ] = Markdown.to_ast(warning_error, []) + + assert section_attrs[:role] == "note" + assert section_attrs[:class] == "admonition error warning" + + assert h_attrs[:class] == "admonition-title error warning" + + with_blockquote_level_attrs = """ + > ### Eggs and baskets {: .tip} + > Don't put all your eggs in one basket, especially if they're golden. + {: .egg-basket-bg #egg-basket-tip} + """ + + assert [ + {:section, section_attrs, + [ + {:h3, h_attrs, ["Eggs and baskets"], %{}}, + {:p, [], + ["Don't put all your eggs in one basket, especially if they're golden."], %{}} + ], %{}} + ] = Markdown.to_ast(with_blockquote_level_attrs, []) + + assert section_attrs[:role] == "note" + assert section_attrs[:class] == "admonition tip egg-basket-bg" + assert section_attrs[:id] == "egg-basket-tip" + + assert h_attrs[:class] == "admonition-title tip" end test "keeps math syntax without interpreting math as markdown" do From 5fd1ca8138a10a80f46db255fe05f90f24b77532 Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Fri, 16 Aug 2024 07:45:50 -0400 Subject: [PATCH 14/15] Update JS, fix bug caused by missing a CSS selector --- assets/css/content/general.css | 2 +- assets/js/content.js | 15 --------------- assets/js/entry/epub.js | 2 -- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/assets/css/content/general.css b/assets/css/content/general.css index e00523a49..5f727446a 100644 --- a/assets/css/content/general.css +++ b/assets/css/content/general.css @@ -178,7 +178,7 @@ } } -.content-inner blockquote .section-heading i { +.content-inner :is(blockquote, section.admonition) .section-heading i { display: none; } diff --git a/assets/js/content.js b/assets/js/content.js index 6e20f4c3a..49d14af28 100644 --- a/assets/js/content.js +++ b/assets/js/content.js @@ -16,7 +16,6 @@ export function initialize (isPreview) { setLivebookBadgeUrl() fixLinks() - fixBlockquotes() } /** @@ -30,20 +29,6 @@ function fixLinks () { }) } -/** - * Add CSS classes to `blockquote` elements when those are used to - * support admonition text blocks - */ -export function fixBlockquotes () { - const classes = ['warning', 'info', 'error', 'neutral', 'tip'] - - classes.forEach(element => { - qsAll(`blockquote h3.${element}, blockquote h4.${element}`).forEach(header => { - header.closest('blockquote').classList.add(element) - }) - }) -} - /** * Focuses the content element. * diff --git a/assets/js/entry/epub.js b/assets/js/entry/epub.js index abf37e456..e48454c9c 100644 --- a/assets/js/entry/epub.js +++ b/assets/js/entry/epub.js @@ -1,8 +1,6 @@ import { onDocumentReady } from '../helpers' -import { fixBlockquotes } from '../content' import { initialize as initMakeup } from '../makeup' onDocumentReady(() => { initMakeup() - fixBlockquotes() }) From 7a5ad41c57f31411be14b1f15c059f72a2f903cb Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Fri, 16 Aug 2024 07:57:04 -0400 Subject: [PATCH 15/15] Minor tweak: add " " as second arg to split/2 --- lib/ex_doc/markdown/earmark.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ex_doc/markdown/earmark.ex b/lib/ex_doc/markdown/earmark.ex index fc5910727..2e04bffcd 100644 --- a/lib/ex_doc/markdown/earmark.ex +++ b/lib/ex_doc/markdown/earmark.ex @@ -84,7 +84,7 @@ defmodule ExDoc.Markdown.Earmark do when tag in ["h3", "h4"] do h_admonition = with {{"class", classes}, attrs} <- List.keytake(h_attrs, "class", 0), - class_list <- String.split(classes), + class_list <- String.split(classes, " "), adm_classes = [_ | _] <- Enum.filter(class_list, &(&1 in @admonition_classes)) do {"admonition " <> Enum.join(adm_classes, " "), [{"class", "admonition-title #{classes}"} | attrs]}