Skip to content

Commit 95056f7

Browse files
authored
Convert admonition blockquotes to sections for screen reader users (#1938)
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.
1 parent bae8378 commit 95056f7

File tree

9 files changed

+179
-65
lines changed

9 files changed

+179
-65
lines changed

assets/css/content/admonition.css

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) {
1+
.content-inner section.admonition {
22
border-radius: 10px;
33
border-left: 0;
44
}
55

6-
.content-inner blockquote.warning {
6+
.content-inner section.admonition.warning {
77
background-color: var(--warningBackground);
88
}
99

10-
.content-inner blockquote.error {
10+
.content-inner section.admonition.error {
1111
background-color: var(--errorBackground);
1212
}
1313

14-
.content-inner blockquote.info {
14+
.content-inner section.admonition.info {
1515
background-color: var(--infoBackground);
1616
}
1717

18-
.content-inner blockquote.neutral {
18+
.content-inner section.admonition.neutral {
1919
background-color: var(--neutralBackground);
2020
}
2121

22-
.content-inner blockquote.tip {
22+
.content-inner section.admonition.tip {
2323
background-color: var(--tipBackground);
2424
}
2525

26-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) {
26+
.content-inner section.admonition > .admonition-title {
2727
color: var(--contrast);
2828
margin: 0 -1.2rem;
2929
padding: .7rem 1.2rem .7rem 3.3rem;
3030
font-weight: 700;
3131
font-style: normal;
3232
}
33-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip)::before {
33+
.content-inner section.admonition > .admonition-title::before {
3434
color: var(--contrast);
3535
position: absolute;
3636
left: 1rem;
@@ -41,82 +41,82 @@
4141
-moz-osx-font-smoothing: grayscale;
4242
}
4343

44-
.content-inner blockquote :is(h3, h4).warning {
44+
.content-inner section.admonition > .admonition-title.warning {
4545
background-color: var(--warningHeadingBackground);
4646
color: var(--warningHeading);
4747
}
48-
.content-inner blockquote :is(h3, h4).warning::before {
48+
.content-inner section.admonition > .admonition-title.warning::before {
4949
content: var(--icon-error-warning);
5050
color: var(--warningHeading);
5151
}
5252

53-
.content-inner blockquote :is(h3, h4).error {
53+
.content-inner section.admonition > .admonition-title.error {
5454
background-color: var(--errorHeadingBackground);
5555
color: var(--errorHeading);
5656
}
57-
.content-inner blockquote :is(h3, h4).error::before {
57+
.content-inner section.admonition > .admonition-title.error::before {
5858
content: var(--icon-error-warning);
5959
color: var(--errorHeading);
6060
}
6161

62-
.content-inner blockquote :is(h3, h4).info {
62+
.content-inner section.admonition > .admonition-title.info {
6363
background-color: var(--infoHeadingBackground);
6464
color: var(--infoHeading);
6565
}
66-
.content-inner blockquote :is(h3, h4).info::before {
66+
.content-inner section.admonition > .admonition-title.info::before {
6767
content: var(--icon-information);
6868
color: var(--infoHeading);
6969
}
7070

71-
.content-inner blockquote :is(h3, h4).neutral {
71+
.content-inner section.admonition > .admonition-title.neutral {
7272
background-color: var(--neutralHeadingBackground);
7373
color: var(--neutralHeading);
7474
}
75-
.content-inner blockquote :is(h3, h4).neutral::before {
75+
.content-inner section.admonition > .admonition-title.neutral::before {
7676
content: var(--icon-double-quotes-l);
7777
color: var(--neutralHeading);
7878
}
7979

80-
.content-inner blockquote :is(h3, h4).tip {
80+
.content-inner section.admonition > .admonition-title.tip {
8181
background-color: var(--tipHeadingBackground);
8282
color: var(--tipHeading);
8383
}
84-
.content-inner blockquote :is(h3, h4).tip::before {
84+
.content-inner section.admonition > .admonition-title.tip::before {
8585
content: var(--icon-information);
8686
color: var(--tipHeading);
8787
}
8888

89-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code {
89+
.content-inner section.admonition > .admonition-title code {
9090
margin: 0 0.5ch;
9191
}
9292

93-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) code {
93+
.content-inner section.admonition code {
9494
background-color: var(--admInlineCodeBackground);
9595
border: 1px solid var(--admInlineCodeBorder);
9696
color: var(--admInlineCodeColor);
9797
}
9898

99-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) pre code {
99+
.content-inner section.admonition pre code {
100100
background-color: var(--admCodeBackground);
101101
border: 1px solid var(--admCodeBorder);
102102
color: var(--admCodeColor);
103103
}
104104

105-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) :is(a, a:visited) {
105+
.content-inner section.admonition > .admonition-title :is(a, a:visited) {
106106
color: inherit;
107107
text-decoration-color: currentColor;
108108
}
109109

110110
@media screen and (max-width: 768px) {
111-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) {
111+
.content-inner section.admonition {
112112
margin-left: calc(-1 * var(--content-gutter));
113113
margin-right: calc(-1 * var(--content-gutter));
114114
padding-left: var(--content-gutter);
115115
padding-right: var(--content-gutter);
116116
border-radius: 0;
117117
}
118118

119-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) {
119+
.content-inner section.admonition > .admonition-title {
120120
margin: 0 calc(-1 * var(--content-gutter));
121121
}
122122
}

assets/css/content/epub-admonition.css

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) {
1+
.content-inner section.admonition {
22
border-left: solid 4px;
33
color: var(--black);
44
font-size: 0.9em;
@@ -9,68 +9,65 @@
99
page-break-inside: avoid;
1010
}
1111

12-
.content-inner blockquote.warning {
12+
.content-inner section.admonition.warning {
1313
background-color: var(--warningBackground);
1414
border-left-color: var(--warningHeadingBackground);
1515
}
1616

17-
.content-inner blockquote.error {
17+
.content-inner section.admonition.error {
1818
background-color: var(--errorBackground);
1919
border-left-color: var(--errorHeadingBackground);
2020
}
2121

22-
.content-inner blockquote.info {
22+
.content-inner section.admonition.info {
2323
background-color: var(--infoBackground);
2424
border-left-color: var(--infoHeadingBackground);
2525
}
2626

27-
.content-inner blockquote.neutral {
27+
.content-inner section.admonition.neutral {
2828
background-color: var(--neutralBackground);
2929
border-left-color: var(--neutralHeadingBackground);
3030
}
3131

32-
.content-inner blockquote.tip {
32+
.content-inner section.admonition.tip {
3333
background-color: var(--tipBackground);
3434
border-left-color: var(--tipHeadingBackground);
3535
}
3636

37-
.content-inner blockquote :is(h3, h4) {
37+
.content-inner section.admonition > .admonition-title {
3838
font-weight: bold;
3939
margin: 0px 10px 5px 0px;
40-
}
41-
42-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) {
4340
font-style: normal;
4441
font-weight: 700;
4542
}
4643

47-
.content-inner blockquote :is(h3, h4).warning {
44+
.content-inner section.admonition > .admonition-title.warning {
4845
color: var(--warningHeadingBackground);
4946
}
50-
.content-inner blockquote :is(h3, h4).error {
47+
.content-inner section.admonition > .admonition-title.error {
5148
color: var(--errorHeadingBackground);
5249
}
53-
.content-inner blockquote :is(h3, h4).info {
50+
.content-inner section.admonition > .admonition-title.info {
5451
color: var(--infoHeadingBackground);
5552
}
56-
.content-inner blockquote :is(h3, h4).neutral {
53+
.content-inner section.admonition > .admonition-title.neutral {
5754
color: var(--neutralHeadingBackground);
5855
}
59-
.content-inner blockquote :is(h3, h4).tip {
56+
.content-inner section.admonition > .admonition-title.tip {
6057
color: var(--tipHeadingBackground);
6158
}
6259

63-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) code {
60+
.content-inner section.admonition > .admonition-title code {
6461
margin: 0 0.5ch;
6562
}
6663

67-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) code {
64+
.content-inner section.admonition code {
6865
background-color: var(--admInlineCodeBackground);
6966
border: 1px solid var(--admInlineCodeBorder);
7067
color: var(--admInlineCodeColor);
7168
}
7269

73-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) pre code {
70+
.content-inner section.admonition pre code {
7471
background-color: var(--admCodeBackground);
7572
border: 1px solid var(--admCodeBorder);
7673
color: var(--admCodeColor);

assets/css/content/general.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
font-weight: normal;
9898
}
9999

100-
.content-inner blockquote {
100+
.content-inner blockquote, .content-inner section.admonition {
101101
border-left: 3px solid var(--blockquoteBorder);
102102
position: relative;
103103
margin: 1.5625em 0;
@@ -106,7 +106,7 @@
106106
background-color: var(--blockquoteBackground);
107107
border-radius: var(--borderRadius);
108108
}
109-
.content-inner blockquote p:last-child {
109+
.content-inner blockquote p:last-child, .content-inner section.admonition p:last-child {
110110
padding-bottom: 1em;
111111
margin-bottom: 0;
112112
}
@@ -178,7 +178,7 @@
178178
}
179179
}
180180

181-
.content-inner blockquote .section-heading i {
181+
.content-inner :is(blockquote, section.admonition) .section-heading i {
182182
display: none;
183183
}
184184

assets/css/print.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@
5252
display: none;
5353
}
5454

55-
.content-inner blockquote:is(.warning, .error, .info, .neutral, .tip) {
55+
.content-inner section.admonition {
5656
border: 2px solid var(--gray400);
5757
}
5858

59-
.content-inner blockquote :is(h3, h4):is(.warning, .error, .info, .neutral, .tip) {
59+
.content-inner section.admonition > .admonition-title {
6060
color: var(--textHeaders);
6161
border-bottom: 2px solid var(--gray400);
6262
}

assets/css/tabset.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
}
5959

6060
.tabset-panel pre,
61-
.tabset-panel blockquote {
61+
.tabset-panel blockquote,
62+
.tabset-panel section.admonition {
6263
margin-left: calc(-1 * var(--tabsetPadding)) !important;
6364
margin-right: calc(-1 * var(--tabsetPadding)) !important;
6465
}

assets/js/content.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export function initialize (isPreview) {
1616

1717
setLivebookBadgeUrl()
1818
fixLinks()
19-
fixBlockquotes()
2019
}
2120

2221
/**
@@ -30,20 +29,6 @@ function fixLinks () {
3029
})
3130
}
3231

33-
/**
34-
* Add CSS classes to `blockquote` elements when those are used to
35-
* support admonition text blocks
36-
*/
37-
export function fixBlockquotes () {
38-
const classes = ['warning', 'info', 'error', 'neutral', 'tip']
39-
40-
classes.forEach(element => {
41-
qsAll(`blockquote h3.${element}, blockquote h4.${element}`).forEach(header => {
42-
header.closest('blockquote').classList.add(element)
43-
})
44-
})
45-
}
46-
4732
/**
4833
* Focuses the content element.
4934
*

assets/js/entry/epub.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { onDocumentReady } from '../helpers'
2-
import { fixBlockquotes } from '../content'
32
import { initialize as initMakeup } from '../makeup'
43

54
onDocumentReady(() => {
65
initMakeup()
7-
fixBlockquotes()
86
})

lib/ex_doc/markdown/earmark.ex

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ defmodule ExDoc.Markdown.Earmark do
55

66
@behaviour ExDoc.Markdown
77

8+
@admonition_classes ~w(warning error info tip neutral)
9+
810
@impl true
911
def available? do
1012
match?({:ok, _}, Application.ensure_all_started(:earmark_parser)) and
@@ -74,6 +76,47 @@ defmodule ExDoc.Markdown.Earmark do
7476
"$$\n#{content}\n$$"
7577
end
7678

79+
# Convert admonition blockquotes to sections for screen reader accessibility
80+
defp fixup(
81+
{"blockquote", blockquote_attrs, [{tag, h_attrs, h_content, h_meta} | rest] = ast,
82+
blockquote_meta}
83+
)
84+
when tag in ["h3", "h4"] do
85+
h_admonition =
86+
with {{"class", classes}, attrs} <- List.keytake(h_attrs, "class", 0),
87+
class_list <- String.split(classes, " "),
88+
adm_classes = [_ | _] <- Enum.filter(class_list, &(&1 in @admonition_classes)) do
89+
{"admonition " <> Enum.join(adm_classes, " "),
90+
[{"class", "admonition-title #{classes}"} | attrs]}
91+
else
92+
_ -> nil
93+
end
94+
95+
section_attrs_fn = fn admonition_classes ->
96+
{classes, attrs} =
97+
case List.keytake(blockquote_attrs, "class", 0) do
98+
nil ->
99+
{admonition_classes, blockquote_attrs}
100+
101+
{{"class", classes}, attrs} ->
102+
{"#{admonition_classes} #{classes}", attrs}
103+
end
104+
105+
[{"role", "note"}, {"class", classes} | attrs]
106+
end
107+
108+
if h_admonition do
109+
{admonition_classes, h_attrs} = h_admonition
110+
section_attrs = section_attrs_fn.(admonition_classes)
111+
h_elem = {tag, h_attrs, h_content, h_meta}
112+
113+
fixup({"section", section_attrs, [h_elem | rest], blockquote_meta})
114+
else
115+
# regular blockquote, copied fixup/1 here to avoid infinite loop
116+
{:blockquote, Enum.map(blockquote_attrs, &fixup_attr/1), fixup(ast), blockquote_meta}
117+
end
118+
end
119+
77120
defp fixup({tag, attrs, ast, meta}) when is_binary(tag) and is_list(attrs) and is_map(meta) do
78121
{fixup_tag(tag), Enum.map(attrs, &fixup_attr/1), fixup(ast), meta}
79122
end

0 commit comments

Comments
 (0)