+197 -77 +/-8 browse
1 | diff --git a/web/src/main.rs b/web/src/main.rs |
2 | index 52b7a00..c2bc13f 100644 |
3 | --- a/web/src/main.rs |
4 | +++ b/web/src/main.rs |
5 | @@ -162,7 +162,7 @@ async fn root( |
6 | name => &list.name, |
7 | posts => &posts, |
8 | months => &months, |
9 | - body => &list.description.as_deref().unwrap_or_default(), |
10 | + description => &list.description.as_deref().unwrap_or_default(), |
11 | root_url_prefix => &state.root_url_prefix, |
12 | list => Value::from_object(MailingList::from(list.clone())), |
13 | }) |
14 | diff --git a/web/src/templates/auth.html b/web/src/templates/auth.html |
15 | index 7df3deb..570c38e 100644 |
16 | --- a/web/src/templates/auth.html |
17 | +++ b/web/src/templates/auth.html |
18 | @@ -1,10 +1,10 @@ |
19 | {% include "header.html" %} |
20 | <div class="body body-grid"> |
21 | - <p>Sign <mark><code>{{ ssh_challenge }}</code></mark> with your previously configured key within <time title="{{ timeout_left }}" datetime="{{ timeout_left }}">{{ timeout_left }} minutes</time>. Example:</p> |
22 | - <pre class="command-line-example">printf '<ruby><mark>{{ ssh_challenge }}</mark><rp>(</rp><rt>signin challenge</rt><rp>)</rp></ruby>' | ssh-keygen -Y sign -f <ruby>~/.ssh/id_rsa <rp>(</rp><rt>your account's key</rt><rp>)</rp></ruby> -n <ruby>{{ namespace }}<rp>(</rp><rt>namespace</rt><rp>)</rp></ruby></pre> |
23 | - <form method="post" class="login-form login-ssh"> |
24 | - <label for="id_address">Email address:</label> |
25 | - <input type="text" name="address" required="" id="id_address"> |
26 | + <p aria-label="instructions">Sign <mark class="ssh-challenge-token" title="challenge token">{{ ssh_challenge }}</mark> with your previously configured key within <time title="{{ timeout_left }} minutes left" datetime="{{ timeout_left }}">{{ timeout_left }} minutes</time>. Example:</p> |
27 | + <pre class="command-line-example" title="example terminal command for UNIX shells that signs the challenge token with a public SSH key" >printf <ruby>'<mark>{{ ssh_challenge }}</mark>'<rp>(</rp><rt>signin challenge</rt><rp>)</rp></ruby> | ssh-keygen -Y sign -f <ruby>~/.ssh/id_rsa <rp>(</rp><rt>your account's key</rt><rp>)</rp></ruby> -n <ruby>{{ namespace }}<rp>(</rp><rt>namespace</rt><rp>)</rp></ruby></pre> |
28 | + <form method="post" class="login-form login-ssh" aria-label="login form"> |
29 | + <label for="id_address" id="id_address_label">Email address:</label> |
30 | + <input type="text" name="address" required="" id="id_address" aria-labelledby="id_address_label"> |
31 | <label for="id_password">SSH signature:</label> |
32 | <textarea class="key-or-sig-input" name="password" cols="15" rows="5" placeholder="-----BEGIN SSH SIGNATURE----- changemechangemechangemechangemechangemechangemechangemechangemechange mechangemechangemechangemechangemechangemechangemechangemechangemechan gemechangemechangemechangemechangemechangemechangemechangemechangemech angemechangemechangemechangemechangemechangemechangemechangemechangeme changemechangemechangemechangemechangemechangemechangemechangemechange mechangemechangemechangemechangemechangemechangemechangemechangemechan gemechangemechangemechangemechangemechangemechangemechangemechangemech angemechangemechangemechangemechangemechangemechangemechangemechangeme changemechangemechangemechangemechangemechangemechangemechangemechange mechangemechangemechangemechangemechangemechangemechangemechangemechan gemechangemechangemechangemechangemechangemechangemechangemechangemech angemechangemechangemechangemechangemechangemechangemechangemechangeme changemechangemechangemechangemechangemechangemechangemechangemechange mechangemechangemechangemechangemechangemechangemechangemechangemechan gemechangemechangemechangemechangemechangemechangemechangemechangemech angemechangemechangemechangemechangemechangemechangemechangemechangeme changemechangemechangemechangemechangemechangemechangemechangemechange mechangemechangemechangemechangemechangemechangemechangemechangemechan gemechangemechangemechangemechangemechangemechangemechangemechangemech angemechangemechangemechangemechangemechangemechangemechangemechangeme changemechangemechangemechangemechangemechangemechangemechangemechange chang= -----END SSH SIGNATURE----- " required="" id="id_password"></textarea> |
33 | <input type="submit" value="login"> |
34 | diff --git a/web/src/templates/css.html b/web/src/templates/css.html |
35 | index 5c06388..534d1a1 100644 |
36 | --- a/web/src/templates/css.html |
37 | +++ b/web/src/templates/css.html |
38 | @@ -50,7 +50,7 @@ |
39 | -webkit-font-smoothing:antialiased; |
40 | -moz-osx-font-smoothing:grayscale; |
41 | font-family:-apple-system,BlinkMacSystemFont,Roboto,Roboto Slab,Droid Serif,Segoe UI,system-ui,Arial,sans-serif; |
42 | - font-size:1.125em |
43 | + font-size:100%; |
44 | } |
45 | |
46 | /* Remove unintuitive behaviour such as gaps around media elements. */ |
47 | @@ -63,6 +63,10 @@ |
48 | overflow-wrap: break-word; |
49 | } |
50 | |
51 | + p { |
52 | + line-height: 1.4; |
53 | + } |
54 | + |
55 | h1, |
56 | h2, |
57 | h3, |
58 | @@ -82,6 +86,9 @@ |
59 | |
60 | a.self-link::before { |
61 | content: "ยง"; |
62 | + /* increase surface area for clicks */ |
63 | + padding: 1rem; |
64 | + margin: -1rem; |
65 | } |
66 | |
67 | a.self-link { |
68 | @@ -120,9 +127,14 @@ |
69 | } |
70 | |
71 | code { |
72 | + font-family: var(--monospace-system-stack); |
73 | overflow-wrap: anywhere; |
74 | } |
75 | |
76 | + pre { |
77 | + font-family: var(--monospace-system-stack); |
78 | + } |
79 | + |
80 | input { |
81 | border: none; |
82 | } |
83 | @@ -137,7 +149,14 @@ |
84 | } |
85 | |
86 | :root { |
87 | + --monospace-system-stack: /* apple */ ui-monospace, SFMono-Regular, Menlo, Monaco, |
88 | + /* windows */ "Cascadia Mono", "Segoe UI Mono", Consolas, |
89 | + /* free unixes */ "Liberation Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; |
90 | --text-primary: CanvasText; |
91 | + --text-faded: GrayText; |
92 | + --horizontal-rule: #88929d; |
93 | + --code-foreground: #124; |
94 | + --code-background: #8fbcbb; |
95 | --a-visited-text: var(--a-normal-text); |
96 | } |
97 | |
98 | @@ -303,7 +322,7 @@ |
99 | |
100 | body>main.layout { |
101 | width: 100%; |
102 | - height: 99%; |
103 | + height: 100%; |
104 | overflow-wrap: anywhere; |
105 | |
106 | display: grid; |
107 | @@ -328,6 +347,19 @@ |
108 | grid-area: footer; |
109 | border-top: 2px inset; |
110 | margin-block-start: 1rem; |
111 | + border-color: var(--text-link); |
112 | + background-color: var(--text-primary-blue); |
113 | + color: var(--text-invert); |
114 | + } |
115 | + |
116 | + main.layout>footer a[href] { |
117 | + box-shadow: 2px 2px 2px black; |
118 | + background: Canvas; |
119 | + border: .3rem solid Canvas; |
120 | + border-radius: 3px; |
121 | + font-weight: bold; |
122 | + font-family: var(--monospace-system-stack); |
123 | + font-size: small; |
124 | } |
125 | |
126 | main.layout>footer>* { |
127 | @@ -349,6 +381,11 @@ |
128 | margin-top: 1rem; |
129 | } |
130 | |
131 | + main.layout>div.body *:is(h2,h3,h4,h5,h6) { |
132 | + padding-bottom: .3em; |
133 | + border-bottom: 1px solid var(--horizontal-rule); |
134 | + } |
135 | + |
136 | nav.main-nav { |
137 | padding: 0rem 1rem; |
138 | border: 1px solid var(--border-secondary); |
139 | @@ -377,8 +414,8 @@ |
140 | margin-left: auto; |
141 | } |
142 | |
143 | - main.layout>div.body h2 { |
144 | - margin: 1rem; |
145 | + main.layout>div.header h2.page-title { |
146 | + margin: 1rem 0px; |
147 | } |
148 | |
149 | nav.breadcrumbs { |
150 | @@ -388,11 +425,12 @@ |
151 | nav.breadcrumbs ol { |
152 | list-style-type: none; |
153 | padding-left: 0; |
154 | + font-size: small; |
155 | } |
156 | |
157 | /* If only the root crumb is visible, hide it to avoid unnecessary visual clutter */ |
158 | li.crumb:only-child>span[aria-current="page"] { |
159 | - --secs: 500ms; |
160 | + --secs: 150ms; |
161 | transition: all var(--secs) linear; |
162 | color: transparent; |
163 | } |
164 | @@ -403,23 +441,24 @@ |
165 | } |
166 | |
167 | .crumb, .crumb>a { |
168 | - display: contents; |
169 | + display: inline; |
170 | } |
171 | |
172 | .crumb a::after { |
173 | display: inline-block; |
174 | color: var(--text-primary); |
175 | content: '>'; |
176 | + content: '>' / ''; |
177 | font-size: 80%; |
178 | font-weight: bold; |
179 | padding: 0 3px; |
180 | } |
181 | |
182 | .crumb span[aria-current="page"] { |
183 | - color: GrayText; |
184 | + color: var(--text-faded); |
185 | padding: 0.4rem; |
186 | margin-left: -0.4rem; |
187 | - display: contents; |
188 | + display: inline; |
189 | } |
190 | |
191 | ul.messagelist { |
192 | @@ -434,12 +473,14 @@ |
193 | } |
194 | |
195 | ul.messagelist>li { |
196 | - padding: 0.5rem 1rem; |
197 | + padding: 1rem 0.7rem; |
198 | + --message-background: var(--icon-secondary); |
199 | background: var(--message-background); |
200 | - border: .1rem solid var(--border-secondary); |
201 | - border-radius: 0.2rem; |
202 | + border: 1px outset var(--message-background); |
203 | + border-radius: 2px; |
204 | font-weight: 400; |
205 | margin-block-end: 1.0rem; |
206 | + color: #0d0b0b; |
207 | } |
208 | |
209 | ul.messagelist>li>span.label { |
210 | @@ -448,37 +489,63 @@ |
211 | } |
212 | |
213 | ul.messagelist>li.error { |
214 | - --message-background: var(--background-critical); |
215 | + --message-background: var(--icon-critical); |
216 | } |
217 | |
218 | ul.messagelist>li.success { |
219 | - --message-background: var(--background-success); |
220 | + --message-background: var(--icon-success); |
221 | } |
222 | |
223 | ul.messagelist>li.warning { |
224 | - --message-background: var(--background-warning); |
225 | + --message-background: var(--icon-warning); |
226 | } |
227 | |
228 | ul.messagelist>li.info { |
229 | - --message-background: var(--background-information); |
230 | + --message-background: var(--icon-information); |
231 | } |
232 | |
233 | - div.preamble { |
234 | + div.body>section { |
235 | display: flex; |
236 | flex-direction: column; |
237 | gap: 1rem; |
238 | } |
239 | |
240 | + div.body>section+section{ |
241 | + margin-top: 1rem; |
242 | + } |
243 | + |
244 | + div.calendar rt { |
245 | + white-space: nowrap; |
246 | + font-size: 50%; |
247 | + -moz-min-font-size-ratio: 50%; |
248 | + line-height: 1; |
249 | + } |
250 | + @supports not (display: ruby-text) { |
251 | + /* Chrome seems to display it at regular size, so scale it down */ |
252 | + div.calendar rt { |
253 | + scale: 50%; |
254 | + font-size: 100%; |
255 | + } |
256 | + } |
257 | + |
258 | + div.calendar rt { |
259 | + display: ruby-text; |
260 | + } |
261 | + |
262 | div.calendar th { |
263 | padding: 0.5rem; |
264 | opacity: 0.7; |
265 | + text-align: center; |
266 | + } |
267 | + |
268 | + div.calendar tr { |
269 | + text-align: right; |
270 | } |
271 | |
272 | div.calendar tr, |
273 | div.calendar th { |
274 | - text-align: right; |
275 | font-variant-numeric: tabular-nums; |
276 | - font-family: monospace; |
277 | + font-family: var(--monospace-system-stack); |
278 | } |
279 | |
280 | div.calendar table { |
281 | @@ -488,10 +555,14 @@ |
282 | |
283 | div.calendar td { |
284 | padding: 0.1rem 0.4rem; |
285 | + font-size: 80%; |
286 | + width: 2.3rem; |
287 | + height: 2.3rem; |
288 | + text-align: center; |
289 | } |
290 | |
291 | div.calendar td.empty { |
292 | - color: GrayText; |
293 | + color: var(--text-faded); |
294 | } |
295 | |
296 | div.calendar td:not(.empty) { |
297 | @@ -499,11 +570,11 @@ |
298 | } |
299 | |
300 | div.calendar td:not(:empty) { |
301 | - border: 1px solid var(--text-primary); |
302 | + border: 1px solid var(--text-faded); |
303 | } |
304 | |
305 | div.calendar td:empty { |
306 | - background: GrayText; |
307 | + background: var(--text-faded); |
308 | opacity: 0.2; |
309 | } |
310 | |
311 | @@ -540,6 +611,12 @@ |
312 | border-top:none; |
313 | } |
314 | |
315 | + div.entries>div.entry>span.subject>a { |
316 | + /* increase surface area for clicks */ |
317 | + padding: 1rem; |
318 | + margin: -1rem; |
319 | + } |
320 | + |
321 | div.entries>div.entry span.metadata.replies { |
322 | background: CanvasText; |
323 | border-radius: .6rem; |
324 | @@ -551,7 +628,7 @@ |
325 | |
326 | div.entries>div.entry>span.metadata { |
327 | font-size: small; |
328 | - color: GrayText; |
329 | + color: var(--text-faded); |
330 | word-break: break-all; |
331 | } |
332 | |
333 | @@ -565,7 +642,7 @@ |
334 | } |
335 | |
336 | div.entries>div.entry span.value.empty { |
337 | - color: GrayText; |
338 | + color: var(--text-faded); |
339 | } |
340 | |
341 | div.posts>div.entry>span.metadata>span.from { |
342 | @@ -574,7 +651,7 @@ |
343 | |
344 | table.headers tr>th { |
345 | text-align: right; |
346 | - color: GrayText; |
347 | + color: var(--text-faded); |
348 | } |
349 | |
350 | table.headers th[scope="row"] { |
351 | @@ -601,7 +678,7 @@ |
352 | |
353 | td.message-id, |
354 | span.message-id{ |
355 | - color: GrayText; |
356 | + color: var(--text-faded); |
357 | } |
358 | td.message-id:before, |
359 | span.message-id:before{ |
360 | @@ -622,7 +699,7 @@ |
361 | } |
362 | td.faded, |
363 | span.faded { |
364 | - color: GrayText; |
365 | + color: var(--text-faded); |
366 | } |
367 | td.faded:is(:focus, :hover, :focus-visible, :focus-within), |
368 | span.faded:is(:focus, :hover, :focus-visible, :focus-within) { |
369 | @@ -638,11 +715,32 @@ |
370 | } |
371 | |
372 | ul.lists li + li { |
373 | - margin-top: 1rem; |
374 | + margin-top: 0.2rem; |
375 | + } |
376 | + |
377 | + dl.lists dt { |
378 | + font-weight: bold; |
379 | + } |
380 | + |
381 | + dl.lists dl, |
382 | + dl.lists dd { |
383 | + font-size: small; |
384 | + } |
385 | + |
386 | + dl.lists dd { |
387 | + /* fallback in case margin-block-* is not supported */ |
388 | + margin-bottom: 1rem; |
389 | + margin-block-start: 0.3rem; |
390 | + margin-block-end: 1rem; |
391 | + } |
392 | + |
393 | + dl.lists dd.no-description { |
394 | + color: var(--text-faded); |
395 | } |
396 | |
397 | hr { |
398 | margin: 1rem 0rem; |
399 | + border-bottom: 1px solid #88929d; |
400 | } |
401 | |
402 | .command-line-example { |
403 | @@ -650,25 +748,27 @@ |
404 | display: inline-block; |
405 | ruby-align: center; |
406 | ruby-position: under; |
407 | - padding: 0; |
408 | |
409 | - background: var(--background-information); |
410 | - outline: 5px solid var(--background-information); |
411 | - width: min-content; |
412 | + background: var(--code-background); |
413 | + outline: 1px inset var(--code-background); |
414 | + border-radius: 1px; |
415 | + color: var(--code-foreground); |
416 | + font-weight: 500; |
417 | + width: auto; |
418 | max-width: 90vw; |
419 | - padding: 2px 7px; |
420 | + padding: 1.2rem 0.8rem 1rem 0.8rem; |
421 | overflow-wrap: break-word; |
422 | overflow: auto; |
423 | white-space: pre; |
424 | } |
425 | |
426 | textarea.key-or-sig-input { |
427 | - font-family: monospace; |
428 | + font-family: var(--monospace-system-stack); |
429 | font-size: 0.5rem; |
430 | font-weight: 400; |
431 | width: auto; |
432 | - height: 29rem; |
433 | - max-width: min(71ch, 75vw); |
434 | + height: 26rem; |
435 | + max-width: min(71ch, 100%); |
436 | overflow-wrap: break-word; |
437 | overflow: auto; |
438 | white-space: pre; |
439 | @@ -686,9 +786,16 @@ |
440 | line-height: 1rem; |
441 | } |
442 | |
443 | + mark.ssh-challenge-token { |
444 | + font-family: var(--monospace-system-stack); |
445 | + overflow-wrap: anywhere; |
446 | + } |
447 | + |
448 | .body-grid { |
449 | display: grid; |
450 | + /* fallback */ |
451 | grid-template-columns: 1fr; |
452 | + grid-template-columns: fit-content(100%); |
453 | grid-auto-rows: min-content; |
454 | row-gap: min(6vw, 1rem); |
455 | width: 100%; |
456 | @@ -726,7 +833,7 @@ |
457 | |
458 | form.settings-form>fieldset>legend { |
459 | padding: .5rem 1rem; |
460 | - border: 1px ridge GrayText; |
461 | + border: 1px ridge var(--text-faded); |
462 | font-weight: bold; |
463 | font-size: small; |
464 | margin-left: 0.8rem; |
465 | @@ -808,6 +915,11 @@ |
466 | cursor: text; |
467 | } |
468 | |
469 | + input[type="text"], textarea { |
470 | + outline: 3px inset #6969694a; |
471 | + outline-offset: -5px; |
472 | + } |
473 | + |
474 | button, ::file-selector-button, input:is([type="color"], [type="reset"], [type="button"], [type="submit"]) { |
475 | appearance: auto; |
476 | -moz-default-appearance: button; |
477 | @@ -825,7 +937,7 @@ |
478 | } |
479 | |
480 | button:disabled, input:is([type="color"], [type="reset"], [type="button"], [type="submit"]):disabled { |
481 | - color: GrayText; |
482 | + color: var(--text-faded); |
483 | background: Field; |
484 | cursor: not-allowed; |
485 | } |
486 | @@ -834,4 +946,13 @@ |
487 | list-style: decimal outside; |
488 | padding-inline-start: 4rem; |
489 | } |
490 | + |
491 | + .screen-reader-only { |
492 | + position:absolute; |
493 | + left:-500vw; |
494 | + top:auto; |
495 | + width:1px; |
496 | + height:1px; |
497 | + overflow:hidden; |
498 | + } |
499 | </style> |
500 | diff --git a/web/src/templates/header.html b/web/src/templates/header.html |
501 | index eef1a3b..d86dcf3 100644 |
502 | --- a/web/src/templates/header.html |
503 | +++ b/web/src/templates/header.html |
504 | @@ -15,12 +15,12 @@ |
505 | {% endif %} |
506 | {% include "menu.html" %} |
507 | <div class="page-header"> |
508 | + {% if crumbs|length > 1 %}<nav aria-labelledby="breadcrumb-menu" class="breadcrumbs"> |
509 | + <ol id="breadcrumb-menu" role="menu" aria-label="Breadcrumb menu">{% for crumb in crumbs %}<li class="crumb" aria-describedby="bread_{{ loop.index }}">{% if loop.last %}<span role="menuitem" id="bread_{{ loop.index }}" aria-current="page" title="current page">{{ crumb.label }}</span>{% else %}<a role="menuitem" id="bread_{{ loop.index }}" href="{{ root_url_prefix }}{{ crumb.url }}" tabindex="0">{{ crumb.label }}</a>{% endif %}</li>{% endfor %}</ol> |
510 | + </nav>{% endif %} |
511 | {% if page_title %} |
512 | - <h2>{{ page_title }}</h2> |
513 | + <h2 class="page-title">{{ page_title }}</h2> |
514 | {% endif %} |
515 | - <nav aria-label="Breadcrumb" class="breadcrumbs"> |
516 | - <ol>{% for crumb in crumbs %}{% if loop.last %}<li class="crumb"><span aria-current="page" title="current page">{{ crumb.label }}</span></li>{% else %}<li class="crumb"><a href="{{ root_url_prefix }}{{ crumb.url }}">{{ crumb.label }}</a></li>{% endif %}{% endfor %}</ol> |
517 | - </nav> |
518 | {% if messages %} |
519 | <ul class="messagelist"> |
520 | {% for message in messages %} |
521 | diff --git a/web/src/templates/lists.html b/web/src/templates/lists.html |
522 | index 768884c..eb47baa 100644 |
523 | --- a/web/src/templates/lists.html |
524 | +++ b/web/src/templates/lists.html |
525 | @@ -1,12 +1,13 @@ |
526 | {% include "header.html" %} |
527 | <div class="body"> |
528 | - <p>{{ lists|length }} lists</p> |
529 | + <!-- {{ lists|length }} lists --> |
530 | <div class="entry"> |
531 | - <ul class="lists"> |
532 | + <dl class="lists" aria-label="list of mailing lists"> |
533 | {% for l in lists %} |
534 | - <li><a href="{{ root_url_prefix }}{{ list_path(l.list.id) }}">{{ l.list.name }}</a></li> |
535 | + <dt aria-label="mailing list name"><a href="{{ root_url_prefix }}{{ list_path(l.list.id) }}">{{ l.list.name }}</a></dt> |
536 | + <dd aria-label="mailing list description"{% if not l.list.description %} class="no-description"{% endif %}>{{ l.list.description if l.list.description else "no description" }}</dd> |
537 | {% endfor %} |
538 | - </ul> |
539 | + </dl> |
540 | </div> |
541 | </div> |
542 | {% include "footer.html" %} |
543 | diff --git a/web/src/templates/lists/list.html b/web/src/templates/lists/list.html |
544 | index 00c246a..113b326 100644 |
545 | --- a/web/src/templates/lists/list.html |
546 | +++ b/web/src/templates/lists/list.html |
547 | @@ -1,11 +1,11 @@ |
548 | {% include "header.html" %} |
549 | <div class="body"> |
550 | {% if list.description %} |
551 | - <p>List description: {{ list.description }}</p> |
552 | + <p title="mailing list description">List description: {{ list.description }}</p> |
553 | {% else %} |
554 | - <p>No list description.</p> |
555 | + <p title="mailing list description">No list description.</p> |
556 | {% endif %} |
557 | - <br> |
558 | + <br aria-hidden="true"> |
559 | {% if current_user and not post_policy.no_subscriptions and subscription_policy.open %} |
560 | {% if user_context %} |
561 | <form method="post" action="{{ root_url_prefix }}{{ settings_path() }}" class="settings-form"> |
562 | @@ -22,8 +22,7 @@ |
563 | {% endif %} |
564 | {% endif %} |
565 | {% if preamble %} |
566 | - <hr> |
567 | - <div id="preamble" class="preamble"> |
568 | + <section id="preamble" class="preamble" aria-label="mailing list instructions"> |
569 | {% if preamble.custom %} |
570 | {{ preamble.custom|safe }} |
571 | {% else %} |
572 | @@ -76,12 +75,9 @@ |
573 | <p>List is not open for submissions.</p> |
574 | {% endif %} |
575 | {% endif %} |
576 | - </div> |
577 | - <hr> |
578 | - {% else %} |
579 | - <hr> |
580 | + </section> |
581 | {% endif %} |
582 | - <div class="list"> |
583 | + <section class="list" aria-hidden="true"> |
584 | <h3 id="calendar">Calendar<a class="self-link" href="#calendar"></a></h3> |
585 | <div class="calendar"> |
586 | {%- from "calendar.html" import cal %} |
587 | @@ -89,18 +85,19 @@ |
588 | {{ cal(date, hists, root_url_prefix, list.pk) }} |
589 | {% endfor %} |
590 | </div> |
591 | - <hr> |
592 | + </section> |
593 | + <section aria-label="mailing list posts"> |
594 | <h3 id="posts">Posts<a class="self-link" href="#posts"></a></h3> |
595 | - <div class="posts entries"> |
596 | - <p>{{ posts | length }} post(s)</p> |
597 | + <div class="posts entries" role="list" aria-label="list of mailing list posts"> |
598 | + <p>{{ posts | length }} post{{ posts|length|pluralize }}</p> |
599 | {% for post in posts %} |
600 | - <div class="entry"> |
601 | - <span class="subject"><a href="{{ root_url_prefix }}{{ list_post_path(list.id, post.message_id) }}">{{ post.subject }}</a></span> |
602 | - <span class="metadata">๐ค <span class="from">{{ post.address }}</span> ๐ <span class="date">{{ post.datetime }}</span></span> |
603 | - <span class="metadata">๐ชช <span class="message-id">{{ post.message_id }}</span> ↺ <span class="replies">{{ post.replies }}</span> |
604 | - </div> |
605 | + <div class="entry" role="listitem" aria-labelledby="post_link_{{ loop.index }}"> |
606 | + <span class="subject"><a id="post_link_{{ loop.index }}" href="{{ root_url_prefix }}{{ list_post_path(list.id, post.message_id) }}">{{ post.subject }}</a> <span class="metadata replies" title="reply count">{{ post.replies }} repl{{ post.replies|pluralize("y","ies") }}</span></span> |
607 | + <span class="metadata"><span aria-hidden="true">๐ค </span><span class="from" title="post author">{{ post.address }}</span><span aria-hidden="true"> ๐ </span><span class="date" title="post date">{{ post.datetime }}</span></span> |
608 | + <span class="metadata"><span aria-hidden="true">๐ชช </span><span class="message-id" title="e-mail Message-ID">{{ post.message_id }}</span></span> |
609 | + </div> |
610 | {% endfor %} |
611 | </div> |
612 | - </div> |
613 | + </section> |
614 | </div> |
615 | {% include "footer.html" %} |
616 | diff --git a/web/src/templates/lists/post.html b/web/src/templates/lists/post.html |
617 | index 18611bc..27d0c68 100644 |
618 | --- a/web/src/templates/lists/post.html |
619 | +++ b/web/src/templates/lists/post.html |
620 | @@ -1,6 +1,7 @@ |
621 | {% include "header.html" %} |
622 | <div class="body"> |
623 | - <table class="headers"> |
624 | + <table class="headers" title="E-mail headers"> |
625 | + <caption class="screen-reader-only">E-mail headers</caption> |
626 | <tr> |
627 | <th scope="row">List:</th> |
628 | <td class="faded">{{ list.id }}</td> |
629 | @@ -35,7 +36,7 @@ |
630 | {% endif %} |
631 | </table> |
632 | <div class="post-body"> |
633 | - <pre>{{ body }}</pre> |
634 | + <pre title="E-mail text content">{{ body }}</pre> |
635 | </div> |
636 | </div> |
637 | {% include "footer.html" %} |
638 | diff --git a/web/src/templates/menu.html b/web/src/templates/menu.html |
639 | index ead407e..646615c 100644 |
640 | --- a/web/src/templates/menu.html |
641 | +++ b/web/src/templates/menu.html |
642 | @@ -1,11 +1,11 @@ |
643 | - <nav class="main-nav"> |
644 | + <nav class="main-nav" aria-label="main menu" role="menu"> |
645 | <ul> |
646 | - <li><a href="{{ root_url_prefix }}/">Index</a></li> |
647 | - <li><a href="{{ root_url_prefix }}{{ help_path() }}">Help & Documentation</a></li> |
648 | + <li><a role="menuitem" href="{{ root_url_prefix }}/">Index</a></li> |
649 | + <li><a role="menuitem" href="{{ root_url_prefix }}{{ help_path() }}">Help & Documentation</a></li> |
650 | {% if current_user %} |
651 | - <li class="push">Settings: <a href="{{ root_url_prefix }}{{ settings_path() }}">{{ current_user.address }}</a></li> |
652 | + <li class="push">Settings: <a role="menuitem" href="{{ root_url_prefix }}{{ settings_path() }}" title="User settings">{{ current_user.address }}</a></li> |
653 | {% else %} |
654 | - <li class="push"><a href="{{ root_url_prefix }}{{ login_path() }}">Login with SSH OTP</a></li> |
655 | + <li class="push"><a role="menuitem" href="{{ root_url_prefix }}{{ login_path() }}" title="login with one time password using your SSH key">Login with SSH OTP</a></li> |
656 | {% endif %} |
657 | </ul> |
658 | </nav> |