Skip to content

Commit 42597e3

Browse files
authored
Support marking obsolete techniques and restore removed ones (#3975)
This PR supersedes #3961; it accomplishes the same output, plus resolves a leftover empty section remaining in that PR. Related issues: #3651, #3758 ## Note to WG I would appreciate eyes on `obsoleteMessage` wording, seen at the end of each technique's About box (or at the top of HTML files in this PR) for restored obsolete techniques: - Parsing techniques (obsolete in 2.2): [F70](https://deploy-preview-3975--wcag2.netlify.app/techniques/failures/f70), [F77](https://deploy-preview-3975--wcag2.netlify.app/techniques/failures/f77), [G134](https://deploy-preview-3975--wcag2.netlify.app/techniques/general/g134), [G192](https://deploy-preview-3975--wcag2.netlify.app/techniques/general/g192), [H74](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h74), [H75](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h75), [H93](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h93), [H94](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h94) (all should have the same message) - Other previously-removed techniques: [H4](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h4), [H35](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h35), [H46](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h46), [H60](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h60), [H73](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h73), [H45](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h45), [H59](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h59), [H70](https://deploy-preview-3975--wcag2.netlify.app/techniques/html/h70), [SCR37](https://deploy-preview-3975--wcag2.netlify.app/techniques/client-side-script/scr37), [G187](https://deploy-preview-3975--wcag2.netlify.app/techniques/general/g187), [F87](https://deploy-preview-3975--wcag2.netlify.app/techniques/failures/f87) - [Flash](https://deploy-preview-3975--wcag2.netlify.app/techniques/flash/flash1) and [Silverlight](https://deploy-preview-3975--wcag2.netlify.app/techniques/silverlight/sl1) (Message is implemented centrally for each of these types, inherited from the XSLT build system) You don't need to worry about the implementation parts of the PR (unless you want to); I expect to ask @iadawn to review that part. ## Background Historically, techniques have been removed from this repository when they become obsolete. However, they are not removed from the WAI site in order to avoid link rot. This is not ideal, as it leaves those pages in isolation with no indication of being obsolete (with the exception of Flash and Silverlight pages, which had a message added by the build process). ## Summary This PR adds support for marking techniques as obsolete via front-matter. When a technique is marked as obsolete, it has the following effects: - Obsolete techniques contain an extra message at the bottom of their "About this technique" paragraph (roughly following the existing example from Flash and Silverlight techniques) - Links to the technique are omitted (e.g. from the [techniques index](https://deploy-preview-3975--wcag2.netlify.app/techniques/) and related techniques sections) - The one exception to this is links from one obsolete technique to another, in which case the links remain intact, to preserve the current WAI site behavior of obsolete technique pages remaining effectively untouched This PR also adds considerations for WCAG 2.2's obsolete SC, 4.1.1 Parsing: - Obsolete SC will no longer be referenced within the About this Technique box regarding applicable SC - The Related Techniques section in Understanding pages for obsolete SC is excluded, since those techniques will also no longer reference the SC - As with the exception to links above, these considerations only apply to pages that are not also obsolete themselves ## Changes - Implements 2 data fields for techniques pages: - `obsoleteSince` - indicates version upon which the technique became obsolete (in XY notation e.g. `22`) - `obsoleteMessage` - Message to display in About this Technique box - These can be set in front-matter or in directory-level Eleventy data files (e.g. for Flash and Silverlight) - Updates some transformation logic to account for new cases where list items or sections may become empty due to removed links to obsolete techniques - Adds back almost all obsolete techniques I could find in git history, with `obsoleteSince` and `obsoleteMessage` set - To avoid cruft and confusion, I have intentionally _not_ added back cross-links between previously-removed techniques, with the exception of H75 (which will still be relevant in WCAG 2.1, which I am also working on supporting in the new build system) ## Exclusions There are 2 techniques I have not restored; if folks have suggestions on wording for `obsoleteMessage` for them, I can add them: - [SCR21](https://www.w3.org/WAI/WCAG22/Techniques/client-side-script/SCR21): this was [removed](#3652) for seemingly having more to do with subjective coding preference than accessibility - [G222](https://www.w3.org/WAI/WCAG21/Techniques/general/G222): this only exists in 2.1 docs and probably shouldn't have ever been backported there, as it pertains to a SC that was initially added in 2.2 (not 2.1!) then removed before 2.2 was published ## Note: XSLT build system deprecation This PR introduces front-matter in some HTML files, which the previous XSLT build system won't know how to handle. Now that we have the Eleventy build system in use for WAI site updates as well as [gh-pages deploys](#3955), there should be no further dependencies on it, and I plan to remove it and think about updating the main README (update: in #3985).
1 parent 1ce9f3e commit 42597e3

File tree

132 files changed

+11571
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+11571
-42
lines changed

11ty/CustomLiquid.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,6 @@ export class CustomLiquid extends Liquid {
130130
});
131131

132132
if (isTechniques) {
133-
// Remove any effectively-empty techniques/resources sections (from template)
134-
$("section#related:not(:has(a))").remove();
135-
$("section#resources:not(:has(a, li))").remove();
136133
// Expand related technique links to include full title
137134
// (the XSLT process didn't handle this in this particular context)
138135
const siblingCode = basename(filepath).replace(/^([A-Z]+).*$/, "$1");
@@ -304,9 +301,22 @@ export class CustomLiquid extends Liquid {
304301

305302
const $ = load(html);
306303

307-
if (!indexPattern.test(scope.page.inputPath)) {
304+
if (indexPattern.test(scope.page.inputPath)) {
305+
// Remove empty list items due to obsolete technique link removal
306+
if (scope.isTechniques) $("ul.toc-wcag-docs li:empty").remove();
307+
} else {
308+
const $title = $("title");
309+
308310
if (scope.isTechniques) {
309-
$("title").text(`${scope.technique.id}: ${scope.technique.title}${titleSuffix}`);
311+
const isObsolete =
312+
scope.technique.obsoleteSince && scope.technique.obsoleteSince <= scope.version;
313+
if (isObsolete) $("body").addClass("obsolete");
314+
315+
$title.text(
316+
(isObsolete ? "[Obsolete] " : "") +
317+
`${scope.technique.id}: ${scope.technique.title}${titleSuffix}`
318+
);
319+
310320
const aboutBoxSelector = "section#technique .box-i";
311321

312322
// Strip applicability paragraphs with metadata IDs (e.g. H99)
@@ -358,25 +368,17 @@ export class CustomLiquid extends Liquid {
358368
}
359369
$("section#applicability").remove();
360370

361-
if (scope.technique.technology === "flash") {
362-
$(aboutBoxSelector).append(
363-
"<p><em>Note: Adobe has plans to stop updating and distributing the Flash Player at the end of 2020, " +
364-
"and encourages authors interested in creating accessible web content to use HTML.</em></p>"
365-
);
366-
} else if (scope.technique.technology === "silverlight") {
367-
$(aboutBoxSelector).append(
368-
"<p><em>Note: Microsoft has stopped updating and distributing Silverlight, " +
369-
"and authors are encouraged to use HTML for accessible web content.</em></p>"
370-
);
371-
}
371+
// Remove any effectively-empty techniques/resources sections,
372+
// due to template boilerplate or obsolete technique removal
373+
$("section#related:not(:has(a))").remove();
374+
$("section#resources:not(:has(a, li))").remove();
372375

373376
// Update understanding links to always use base URL
374377
// (mainly to avoid any case-sensitivity issues)
375378
$(techniqueToUnderstandingLinkSelector).each((_, el) => {
376379
el.attribs.href = el.attribs.href.replace(/^.*\//, scope.understandingUrl);
377380
});
378381
} else if (scope.isUnderstanding) {
379-
const $title = $("title");
380382
if (scope.guideline) {
381383
const type = scope.guideline.type === "SC" ? "Success Criterion" : scope.guideline.type;
382384
$title.text(
@@ -387,13 +389,20 @@ export class CustomLiquid extends Liquid {
387389
$title.text().replace(/WCAG 2( |$)/, `WCAG ${scope.versionDecimal}$1`) + titleSuffix
388390
);
389391
}
392+
393+
// Remove Techniques section from obsolete SCs (e.g. Parsing in 2.2)
394+
if (scope.guideline?.level === "") $("section#techniques").remove();
390395
}
391396

392397
// Process defined terms within #render,
393398
// where we have access to global data and the about box's HTML
394399
const $termLinks = $(termLinkSelector);
395400
const extractTermName = ($el: Cheerio<Element>) => {
396-
const name = $el.text().toLowerCase().trim().replace(/\s*\n+\s*/, " ");
401+
const name = $el
402+
.text()
403+
.toLowerCase()
404+
.trim()
405+
.replace(/\s*\n+\s*/, " ");
397406
const term = termsMap[name];
398407
if (!term) {
399408
console.warn(`${scope.page.inputPath}: Term not found: ${name}`);
@@ -525,7 +534,7 @@ export class CustomLiquid extends Liquid {
525534

526535
// Allow autogenerating missing top-level section IDs in understanding docs,
527536
// but don't pick up incorrectly-nested sections in some techniques pages (e.g. H91)
528-
const sectionSelector = scope.isUnderstanding ? "section" : "section[id]";
537+
const sectionSelector = scope.isUnderstanding ? "section" : "section[id]:not(.obsolete)";
529538
const sectionH2Selector = "h2:first-child";
530539
const $h2Sections = $(`${sectionSelector}:has(${sectionH2Selector})`);
531540
if ($h2Sections.length) {

11ty/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ Indicates top-level path of W3C CVS checkout, for WAI site updates (via `publish
3838

3939
**Default:** `../../../w3ccvs` (same as in Ant/XSLT build process)
4040

41+
### `WCAG_VERBOSE`
42+
43+
**Usage context:** Local development, debugging
44+
45+
Prints more log messages that are typically noisy and uninteresting,
46+
but may be useful if you're not seeing what you expect in the output files.
47+
48+
**Default:** Unset (set to any non-empty value to enable)
49+
4150
### `WCAG_VERSION`
4251

4352
**Usage context:** currently this should not be changed, pending future improvements to `21` support

11ty/guidelines.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,15 @@ export interface Principle extends DocNode {
8181
num: `${number}`; // typed as string for consistency with guidelines/SC
8282
version: "WCAG20";
8383
guidelines: Guideline[];
84+
type: "Principle";
8485
}
8586

8687
export interface Guideline extends DocNode {
8788
content: string;
8889
num: `${Principle["num"]}.${number}`;
8990
version: `WCAG${"20" | "21"}`;
9091
successCriteria: SuccessCriterion[];
92+
type: "Guideline";
9193
}
9294

9395
export interface SuccessCriterion extends DocNode {
@@ -96,6 +98,7 @@ export interface SuccessCriterion extends DocNode {
9698
/** Level may be empty for obsolete criteria */
9799
level: "A" | "AA" | "AAA" | "";
98100
version: `WCAG${WcagVersion}`;
101+
type: "SC";
99102
}
100103

101104
export function isSuccessCriterion(criterion: any): criterion is SuccessCriterion {

11ty/techniques.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import type { Cheerio } from "cheerio";
22
import { glob } from "glob";
3+
import matter from "gray-matter";
34
import capitalize from "lodash-es/capitalize";
45
import uniqBy from "lodash-es/uniqBy";
56

67
import { readFile } from "fs/promises";
78
import { basename } from "path";
89

910
import { load, loadFromFile } from "./cheerio";
10-
import { isSuccessCriterion, type FlatGuidelinesMap, type SuccessCriterion } from "./guidelines";
11+
import {
12+
assertIsWcagVersion,
13+
isSuccessCriterion,
14+
type FlatGuidelinesMap,
15+
type SuccessCriterion,
16+
type WcagVersion,
17+
} from "./guidelines";
1118
import { wcagSort } from "./common";
1219

1320
/** Maps each technology to its title for index.html */
@@ -25,7 +32,7 @@ export const technologyTitles = {
2532
silverlight: "Silverlight Techniques", // Deprecated in 2020
2633
text: "Plain-Text Techniques",
2734
};
28-
type Technology = keyof typeof technologyTitles;
35+
export type Technology = keyof typeof technologyTitles;
2936
export const technologies = Object.keys(technologyTitles) as Technology[];
3037

3138
function assertIsTechnology(
@@ -163,7 +170,14 @@ export async function getTechniqueAssociations(guidelines: FlatGuidelinesMap) {
163170
return associations;
164171
}
165172

166-
interface Technique {
173+
interface TechniqueFrontMatter {
174+
/** May be specified via front-matter; message to display RE a technique's obsolescence. */
175+
obsoleteMessage?: string;
176+
/** May be specified via front-matter to indicate a technique is obsolete as of this version. */
177+
obsoleteSince?: WcagVersion;
178+
}
179+
180+
export interface Technique extends TechniqueFrontMatter {
167181
/** Letter(s)-then-number technique code; corresponds to source HTML filename */
168182
id: string;
169183
/** Technology this technique is filed under */
@@ -195,17 +209,37 @@ export async function getTechniquesByTechnology() {
195209
{} as Record<Technology, Technique[]>
196210
);
197211

212+
// Check directory data files (we don't have direct access to 11ty's data cascade here)
213+
const technologyData: Partial<Record<Technology, any>> = {};
214+
for (const technology of technologies) {
215+
try {
216+
const data = JSON.parse(
217+
await readFile(`techniques/${technology}/${technology}.11tydata.json`, "utf8")
218+
);
219+
if (data) technologyData[technology] = data;
220+
} catch {}
221+
}
222+
198223
for (const path of paths) {
199224
const [technology, filename] = path.split("/");
200225
assertIsTechnology(technology);
226+
// Support front-matter within HTML files
227+
const { content, data: frontMatterData } = matter(await readFile(`techniques/${path}`, "utf8"));
228+
const data = { ...technologyData[technology], ...frontMatterData };
229+
230+
if (data.obsoleteSince) {
231+
data.obsoleteSince = "" + data.obsoleteSince;
232+
assertIsWcagVersion(data.obsoleteSince);
233+
}
201234

202235
// Isolate h1 from each file before feeding into Cheerio to save ~300ms total
203-
const match = (await readFile(`techniques/${path}`, "utf8")).match(/<h1[^>]*>([\s\S]+?)<\/h1>/);
204-
if (!match || !match[1]) throw new Error(`No h1 found in techniques/${path}`);
205-
const $h1 = load(match[1], null, false);
236+
const h1Match = content.match(/<h1[^>]*>([\s\S]+?)<\/h1>/);
237+
if (!h1Match || !h1Match[1]) throw new Error(`No h1 found in techniques/${path}`);
238+
const $h1 = load(h1Match[1], null, false);
206239

207240
const title = $h1.text();
208241
techniques[technology].push({
242+
...data, // Include front-matter
209243
id: basename(filename, ".html"),
210244
technology,
211245
title,

_includes/techniques/about.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
{% if technique.obsoleteSince <= version %}
2+
<section class="box obsolete-message">
3+
<h2 class="box-h">Obsolete</h2>
4+
{% if technique.obsoleteMessage %}<div class="box-i">{{ technique.obsoleteMessage }}</div>{% endif %}
5+
</section>
6+
{% endif %}
17
{% sectionbox "technique" "About this Technique" %}
28
{% include "techniques/applicability.html" %}
39
{% case technique.technology %}

css/base.css

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,21 @@ dt div {
155155
margin-top: 0;
156156
}
157157

158+
.obsolete {
159+
border-left: solid 5px var(--faded-red);
160+
}
161+
162+
.obsolete-message {
163+
background-color: var(--red-subtle);
164+
border-color: var(--faded-red);
165+
}
166+
167+
.obsolete-message h2 {
168+
color: #fff;
169+
background-color: var(--faded-red);
170+
margin: 0;
171+
}
172+
158173
/* import inline styles from https://www.w3.org/WAI/drafts/WCAG-understanding-redesign-hack.html */
159174
.nav a:link {
160175
text-decoration: none;
@@ -442,4 +457,4 @@ margin-right:.8em;
442457
.minimal-header-logo svg {
443458
margin: 1em 0 0 0;
444459
}
445-
}
460+
}

eleventy.config.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@ import compact from "lodash-es/compact";
33
import { copyFile } from "fs/promises";
44

55
import { CustomLiquid } from "11ty/CustomLiquid";
6-
import { actRules, assertIsWcagVersion, getFlatGuidelines, getPrinciples } from "11ty/guidelines";
6+
import {
7+
actRules,
8+
assertIsWcagVersion,
9+
getFlatGuidelines,
10+
getPrinciples,
11+
type Guideline,
12+
type Principle,
13+
type SuccessCriterion,
14+
} from "11ty/guidelines";
715
import {
816
getFlatTechniques,
917
getTechniqueAssociations,
1018
getTechniquesByTechnology,
1119
technologies,
1220
technologyTitles,
21+
type Technique,
22+
type Technology,
1323
} from "11ty/techniques";
1424
import { generateUnderstandingNavMap, getUnderstandingDocs } from "11ty/understanding";
1525
import type { EleventyContext, EleventyData, EleventyEvent } from "11ty/types";
@@ -18,11 +28,40 @@ import type { EleventyContext, EleventyData, EleventyEvent } from "11ty/types";
1828
const version = process.env.WCAG_VERSION || "22";
1929
assertIsWcagVersion(version);
2030

31+
/**
32+
* Returns boolean indicating whether a technique is obsolete for the given version.
33+
* Tolerates undefined for use with hash lookups.
34+
*/
35+
const isTechniqueObsolete = (technique: Technique | undefined) =>
36+
!!technique?.obsoleteSince && technique.obsoleteSince <= version;
37+
38+
/**
39+
* Returns boolean indicating whether an SC is obsolete for the given version.
40+
* Tolerates other types for use with hash lookups.
41+
*/
42+
const isGuidelineObsolete = (guideline: Principle | Guideline | SuccessCriterion | undefined) =>
43+
guideline?.type === "SC" && guideline.level === "";
44+
2145
const principles = await getPrinciples();
2246
const flatGuidelines = getFlatGuidelines(principles);
2347
const techniques = await getTechniquesByTechnology();
2448
const flatTechniques = getFlatTechniques(techniques);
49+
50+
for (const [technology, list] of Object.entries(techniques)) {
51+
// Prune obsolete techniques from ToC
52+
techniques[technology as Technology] = list.filter(
53+
(technique) => !technique.obsoleteSince || technique.obsoleteSince > version
54+
);
55+
}
56+
2557
const techniqueAssociations = await getTechniqueAssociations(flatGuidelines);
58+
for (const [id, associations] of Object.entries(techniqueAssociations)) {
59+
// Prune associations from non-obsolete techniques to obsolete SCs
60+
techniqueAssociations[id] = associations.filter(
61+
({ criterion }) => criterion.level !== "" || isTechniqueObsolete(flatTechniques[id])
62+
);
63+
}
64+
2665
const understandingDocs = await getUnderstandingDocs(version);
2766
const understandingNav = await generateUnderstandingNavMap(principles, understandingDocs);
2867

@@ -164,6 +203,23 @@ export default function (eleventyConfig: any) {
164203
);
165204
return;
166205
}
206+
207+
// Omit links to obsolete techniques, when not also linked from one
208+
if (
209+
isTechniqueObsolete(technique) &&
210+
!isTechniqueObsolete(flatTechniques[this.page.fileSlug]) &&
211+
!isGuidelineObsolete(flatGuidelines[this.page.fileSlug])
212+
) {
213+
if (process.env.WCAG_VERBOSE) {
214+
const since = technique.obsoleteSince!.split("").join(".");
215+
console.warn(
216+
`linkTechniques in ${this.page.inputPath}: ` +
217+
`skipping obsolete technique ${id} (as of ${since})`
218+
);
219+
}
220+
return;
221+
}
222+
167223
// Support relative technique links from other techniques or from techniques/index.html,
168224
// otherwise path-absolute when cross-linked from understanding/*
169225
const urlBase = /^\/techniques\/.*\//.test(this.page.filePathStem)
@@ -191,7 +247,24 @@ export default function (eleventyConfig: any) {
191247
`linkUnderstanding in ${this.page.inputPath}: ` +
192248
`skipping unresolvable guideline shortname ${id}`
193249
);
250+
return;
194251
}
252+
253+
// Warn of links to obsolete SCs, when not also linked from one.
254+
// This is intentionally not behind WCAG_VERBOSE, and does not remove,
255+
// as links to Understanding docs are more likely to be in the middle
256+
// of prose requiring manual adjustments
257+
if (
258+
isGuidelineObsolete(guideline) &&
259+
!isGuidelineObsolete(flatGuidelines[this.page.fileSlug]) &&
260+
!isTechniqueObsolete(flatTechniques[this.page.fileSlug])
261+
) {
262+
console.warn(
263+
`linkUnderstanding in ${this.page.inputPath}: ` +
264+
`reference to obsolete ${guideline.type} ${id}`
265+
);
266+
}
267+
195268
const urlBase = this.page.filePathStem.startsWith("/understanding/")
196269
? ""
197270
: baseUrls.understanding;

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@11ty/eleventy": "^3.0.0-alpha.14",
1818
"cheerio": "^1.0.0-rc.12",
1919
"glob": "^10.3.16",
20+
"gray-matter": "^4.0.3",
2021
"liquidjs": "^10.14.0",
2122
"lodash-es": "^4.17.21",
2223
"mkdirp": "^3.0.1",

0 commit comments

Comments
 (0)