krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit 2ff6947870

2ff694787076f19a71fb0378186033fa937fb278

parent: 768e668f7a

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-11 21:12 UTC

0.19.1: name the footnote back-links

`<a class="footnote-back" href="#fnr-1">↩</a>` has that glyph as its entire
accessible name, so a screen reader announces "left arrow with hook" once per
note and the reader cannot tell which reference any of them returns to. Each one
now carries `aria-label="Back to reference N"`, and the section carries
`aria-label="Footnotes"` — a `<hr>` is a picture of a section break rather than
a section, and the landmark was otherwise announced as an unnamed "section".

No visual change, so no stylesheet has to move.

Also documents template partials in the guide: a snippet that is not really
layout — an invitation to reply, a licence line — is better as its own file, and
the render key follows `{% include %}` so editing one re-renders exactly its
users.

Layout: unified · split

CHANGELOG.md +7
@@ -15,6 +15,13 @@ Two conventions worth knowing before reading:
1515Versions follow the compatibility promise in the README: config keys, template variables,
1616CLI flags and URLs are the stable surface.
1717
18## 0.19.1
19
20- Footnote back-links carry `aria-label="Back to reference N"`, and the notes section is
21 labelled. A link whose only visible content is `↩` has that glyph as its whole
22 accessible name, so a screen reader announced "left arrow with hook" once per note with
23 no way to tell them apart.
24
1825## 0.19.0
1926
2027- **Full-content collections.** `include_content = true` gives a listing template each
Cargo.lock +1 −1
@@ -675,7 +675,7 @@ dependencies = [
675675
676676[[package]]
677677name = "org-ssg"
678version = "0.19.0"
678version = "0.19.1"
679679dependencies = [
680680 "anyhow",
681681 "blake3",
Cargo.toml +1 −1
@@ -1,6 +1,6 @@
11[package]
22name = "org-ssg"
3version = "0.19.0"
3version = "0.19.1"
44edition = "2021"
55description = "Org-mode static site generator that renders the org element tree straight to HTML"
66license = "MIT"
docs/guide/04-templates.org +18
@@ -77,6 +77,24 @@ page.
7777
7878Subdirectories work, so ={% include "partials/header.html" %}= does what you expect.
7979
80** Partials keep a snippet out of a layout
81
82A block of content that is not really layout — an invitation to reply, a licence line, a
83donation ask — is better as its own file than as a line inside =base.html=:
84
85#+BEGIN_SRC html
86{% extends "base.html" %}
87{% block content %}
88{{ body | safe }}
89{% include "reply.html" %}
90{% endblock %}
91#+END_SRC
92
93Emptying =reply.html= removes it everywhere; editing it re-renders exactly the pages that
94include it, because the render key follows includes. And because the *page* chooses its
95layout, an individual page opts in with =#+TEMPLATE: post.html= or out with
96=#+TEMPLATE: base.html=, without a rule deciding for a whole directory.
97
8098* Variables
8199
82100** body
src/render.rs +11 −2
@@ -708,7 +708,12 @@ impl Renderer<'_> {
708708 let order = self.order.clone();
709709 let inline_defs = self.inline_defs.clone();
710710 let block_defs = self.block_defs.clone();
711 out.push_str("<section class=\"footnotes\">\n<hr>\n<ol>\n");
711 // Named, because a `<hr>` is a picture of a section break rather than a section:
712 // without the label this landmark is announced as "section" and the reader has to
713 // guess what they have reached.
714 out.push_str(
715 "<section class=\"footnotes\" aria-label=\"Footnotes\">\n<hr>\n<ol>\n",
716 );
712717 for (idx, label) in order.iter().enumerate() {
713718 let n = idx + 1;
714719 out.push_str(&format!("<li id=\"fn-{n}\">"));
@@ -719,8 +724,12 @@ impl Renderer<'_> {
719724 self.render_element(el, out);
720725 }
721726 }
727 // The glyph is the whole visible link, so it is also the whole accessible
728 // name: a screen reader would otherwise announce "left arrow with hook",
729 // identically, once per note.
722730 out.push_str(&format!(
723 " <a class=\"footnote-back\" href=\"#fnr-{n}\">&#8617;</a></li>\n"
731 " <a class=\"footnote-back\" href=\"#fnr-{n}\" \
732 aria-label=\"Back to reference {n}\">&#8617;</a></li>\n"
724733 ));
725734 }
726735 out.push_str("</ol>\n</section>\n");
tests/constructs.rs +17
@@ -752,3 +752,20 @@ fn an_unexpanded_include_reports_itself() {
752752 doc.diagnostics[0]
753753 );
754754}
755
756/// A back-link whose whole visible content is `↩` has that glyph as its whole accessible
757/// name, so a screen reader announces "left arrow with hook" once per note and the reader
758/// cannot tell which reference each one returns to.
759#[test]
760fn footnote_links_and_section_are_labelled() {
761 let html = html_of("Text[fn:1] and more[fn:2].\n\n[fn:1] First.\n\n[fn:2] Second.\n");
762 assert!(
763 html.contains("<section class=\"footnotes\" aria-label=\"Footnotes\">"),
764 "the landmark is named:\n{html}"
765 );
766 assert!(
767 html.contains("aria-label=\"Back to reference 1\"")
768 && html.contains("aria-label=\"Back to reference 2\""),
769 "each back-link says where it goes:\n{html}"
770 );
771}
tests/snapshots/site__footnote_render.snap +4 −4
@@ -4,13 +4,13 @@ expression: "render_fragment(\"footnote.org\")"
44---
55<p>Text with a reference.<sup class="footnote-ref"><a id="fnr-1" href="#fn-1">1</a></sup> And a second one.<sup class="footnote-ref"><a id="fnr-2" href="#fn-2">2</a></sup></p>
66<p>An inline footnote.<sup class="footnote-ref"><a id="fnr-3" href="#fn-3">3</a></sup></p>
7<section class="footnotes">
7<section class="footnotes" aria-label="Footnotes">
88<hr>
99<ol>
1010<li id="fn-1"><p>The first definition.</p>
11 <a class="footnote-back" href="#fnr-1">&#8617;</a></li>
11 <a class="footnote-back" href="#fnr-1" aria-label="Back to reference 1">&#8617;</a></li>
1212<li id="fn-2"><p>The second definition, with <em>emphasis</em>.</p>
13 <a class="footnote-back" href="#fnr-2">&#8617;</a></li>
14<li id="fn-3">defined right here <a class="footnote-back" href="#fnr-3">&#8617;</a></li>
13 <a class="footnote-back" href="#fnr-2" aria-label="Back to reference 2">&#8617;</a></li>
14<li id="fn-3">defined right here <a class="footnote-back" href="#fnr-3" aria-label="Back to reference 3">&#8617;</a></li>
1515</ol>
1616</section>
tests/snapshots/site__site_guide_html.snap +2 −2
@@ -40,11 +40,11 @@ expression: "page(&pages, \"guide.org\").html"
4040<tr><td>beta</td><td>20</td></tr>
4141</tbody>
4242</table>
43<section class="footnotes">
43<section class="footnotes" aria-label="Footnotes">
4444<hr>
4545<ol>
4646<li id="fn-1"><p>Read the manual before you begin.</p>
47 <a class="footnote-back" href="#fnr-1">&#8617;</a></li>
47 <a class="footnote-back" href="#fnr-1" aria-label="Back to reference 1">&#8617;</a></li>
4848</ol>
4949</section>
5050</main>