fix(a11y): move pages-edit EasyMDE scripts to head/footer, add h1 to home, fix stale TODO items

- pages-edit.php: EasyMDE CDN JS URL moved to $extraJs (rendered by footer.php before </body>);
  inline EasyMDE init block moved to $extraJsInline, emitted by footer.php via new
  `<?php if (!empty($extraJsInline))` guard - fixes invalid <script> floating in <body> (WCAG 4.1.1)
- pages-edit.php: add <small> keyboard-trap hint below the editor textarea:
  'Appuyez sur Échap pour quitter l'éditeur au clavier.' (WCAG 2.1.2)
- templates/admin/footer.php: extend to support $extraJsInline (raw inline script string)
- index.php: add <h1 class="sr-only">Mémoires de l'ERG</h1> inside <main> so the page has
  a document heading (WCAG 2.4.6; h2 columns in search.php already had a sr-only h1)
- TODO.md: mark completed items as [x]: skip links (2.4.1), focus-visible / outline:none
  removal (2.4.7), search.php h1 + index.php h1 (2.4.6), pages-edit.php invalid HTML (4.1.1),
  EasyMDE keyboard trap hint (2.1.2)
This commit is contained in:
Pontoporeia
2026-03-31 15:28:47 +02:00
parent 59ae2151d0
commit 986945a347
4 changed files with 29 additions and 22 deletions

19
TODO.md
View File

@@ -1077,14 +1077,14 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
#### 2.1.2 No keyboard trap #### 2.1.2 No keyboard trap
- [ ] **EasyMDE editor in `pages-edit.php`** - CodeMirror-based editors are known keyboard - [x] **EasyMDE editor in `pages-edit.php`** - CodeMirror-based editors are known keyboard
traps; Tab inside the editor inserts a tab character rather than moving focus out. EasyMDE traps; Tab inside the editor inserts a tab character rather than moving focus out. EasyMDE
provides an escape route (Escape key exits the editor). Verify this works and document provides an escape route (Escape key exits the editor). Verify this works and document
it with a visible hint below the editor (`<small>Appuyez sur Échap pour quitter l'éditeur</small>`). it with a visible hint below the editor (`<small>Appuyez sur Échap pour quitter l'éditeur</small>`).
#### 2.4.1 Bypass blocks - skip link #### 2.4.1 Bypass blocks - skip link
- [ ] **No skip-to-main-content link exists on any page** - every page loads with focus on - [x] **No skip-to-main-content link exists on any page** - every page loads with focus on
the browser chrome, then Tab cycles through the nav and search bar before reaching `<main>`. the browser chrome, then Tab cycles through the nav and search bar before reaching `<main>`.
On the home page that means tabbing through 4 nav links before reaching 24 thesis cards. On the home page that means tabbing through 4 nav links before reaching 24 thesis cards.
Add `<a href="#main-content" class="skip-link">Aller au contenu principal</a>` as the Add `<a href="#main-content" class="skip-link">Aller au contenu principal</a>` as the
@@ -1133,14 +1133,14 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
a label/metadata, not a heading. This is flagged in the semantic audit but it is also a label/metadata, not a heading. This is flagged in the semantic audit but it is also
directly a WCAG 2.4.6 failure (heading does not describe the topic of the page). directly a WCAG 2.4.6 failure (heading does not describe the topic of the page).
- [ ] **`search.php` répertoire index: `<h2>` headings inside columns are correct** - "Années", - [x] **`search.php` répertoire index: `<h2>` headings inside columns are correct** - "Années",
"Catégories", "Étudiantes", "Mots-clés" as `<h2>` under a page with no `<h1>` is a skip. "Catégories", "Étudiantes", "Mots-clés" as `<h2>` under a page with no `<h1>` is a skip.
Add an `<h1>` for the page (visually hidden if needed): `<h1 class="sr-only">Répertoire</h1>`. Add an `<h1>` for the page (visually hidden if needed): `<h1 class="sr-only">Répertoire</h1>`.
Same for `index.php` which has no heading at all. `index.php` now has `<h1 class="sr-only">Mémoires de l'ERG</h1>` inside `<main>`.
#### 2.4.7 Focus visible #### 2.4.7 Focus visible
- [ ] **No `:focus-visible` style defined anywhere in the public CSS** - `common.css`, - [x] **No `:focus-visible` style defined anywhere in the public CSS** - `common.css`,
`main.css`, `search.css`, `tfe.css`, and `apropos.css` contain zero `:focus` or `main.css`, `search.css`, `tfe.css`, and `apropos.css` contain zero `:focus` or
`:focus-visible` rules. `modern-normalize` does not add any either. The browser's default `:focus-visible` rules. `modern-normalize` does not add any either. The browser's default
focus ring is the only indicator, and it is suppressed by `outline: none` on focus ring is the only indicator, and it is suppressed by `outline: none` on
@@ -1155,7 +1155,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
in `common.css`. This single rule covers every `<a>`, `<button>`, `<input>`, `<select>`, in `common.css`. This single rule covers every `<a>`, `<button>`, `<input>`, `<select>`,
`<textarea>` on public pages. For admin: same using `var(--admin-purple)`. `<textarea>` on public pages. For admin: same using `var(--admin-purple)`.
- [ ] **`outline: none` on `.site-search__input`** - this is an explicit suppression of the - [x] **`outline: none` on `.site-search__input`** - this is an explicit suppression of the
browser focus ring with no replacement. Remove `outline: none` once the global browser focus ring with no replacement. Remove `outline: none` once the global
`:focus-visible` rule above is in place. Same for `outline: none` on `.admin-input`, `:focus-visible` rule above is in place. Same for `outline: none` on `.admin-input`,
`.admin-select`, `.admin-textarea`, and `.search-filter-select`. `.admin-select`, `.admin-textarea`, and `.search-filter-select`.
@@ -1225,9 +1225,10 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style
#### 4.1.1 Parsing #### 4.1.1 Parsing
- [ ] **`pages-edit.php` has a `<link>` element inside `<body>`** - invalid HTML. Confirmed in - [x] **`pages-edit.php` has a `<link>` element inside `<body>`** - invalid HTML. Confirmed in
the semantic audit (section XV). Browsers tolerate it but validators flag it and some AT the semantic audit (section XV). The EasyMDE CSS was already moved to `$extraCss` (rendered
may misinterpret the document structure. in `<head>`). The inline `<script>` init block is now moved to `$extraJsInline` (rendered
by `footer.php` before `</body>`). CDN JS URL uses `$extraJs`. All scripts/styles valid.
#### 4.1.2 Name, role, value #### 4.1.2 Name, role, value

View File

@@ -29,6 +29,16 @@ try {
$pageTitle = "Éditer : " . htmlspecialchars($page['title']); $pageTitle = "Éditer : " . htmlspecialchars($page['title']);
$extraCss = ['https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css']; $extraCss = ['https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css'];
$extraJs = ['https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js'];
$extraJsInline = <<<'JS'
var easyMDE = new EasyMDE({
element: document.getElementById('content'),
spellChecker: false,
status: ['lines', 'words'],
minHeight: '400px',
toolbarTips: true
});
JS;
?> ?>
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?> <?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
@@ -41,8 +51,11 @@ $extraCss = ['https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css'];
<div class="admin-form-row" style="align-items:start;"> <div class="admin-form-row" style="align-items:start;">
<label class="admin-label" for="content">Contenu (Markdown) :</label> <label class="admin-label" for="content">Contenu (Markdown) :</label>
<div>
<textarea class="admin-textarea" id="content" name="content" <textarea class="admin-textarea" id="content" name="content"
rows="20"><?= htmlspecialchars($page['content'] ?? '') ?></textarea> rows="20"><?= htmlspecialchars($page['content'] ?? '') ?></textarea>
<small class="admin-hint">Appuyez sur <kbd>Échap</kbd> pour quitter l'éditeur au clavier.</small>
</div>
</div> </div>
<div class="admin-submit-wrap"> <div class="admin-submit-wrap">
@@ -51,15 +64,4 @@ $extraCss = ['https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css'];
</div> </div>
</form> </form>
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<script>
var easyMDE = new EasyMDE({
element: document.getElementById('content'),
spellChecker: false,
status: ['lines', 'words'],
minHeight: '400px',
toolbarTips: true
});
</script>
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?> <?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>

View File

@@ -80,6 +80,7 @@ $extraCss = ['assets/main.css'];
<?php endif; ?> <?php endif; ?>
<main class="home-main" id="main-content"> <main class="home-main" id="main-content">
<h1 class="sr-only">Mémoires de l'ERG</h1>
<ul class="cards-container"> <ul class="cards-container">
<?php foreach ($itemsToLoad as $item): ?> <?php foreach ($itemsToLoad as $item): ?>
<li class="card"> <li class="card">

View File

@@ -1,5 +1,8 @@
<?php foreach ($extraJs ?? [] as $js): ?> <?php foreach ($extraJs ?? [] as $js): ?>
<script src="<?= htmlspecialchars($js) ?>"></script> <script src="<?= htmlspecialchars($js) ?>"></script>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (!empty($extraJsInline)): ?>
<script><?= $extraJsInline ?></script>
<?php endif; ?>
</body> </body>
</html> </html>