Skip to content

Commit

Permalink
Support marking obsolete techniques and restore removed ones (#3975)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
kfranqueiro authored Aug 7, 2024
1 parent 1ce9f3e commit 42597e3
Show file tree
Hide file tree
Showing 132 changed files with 11,571 additions and 42 deletions.
47 changes: 28 additions & 19 deletions 11ty/CustomLiquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,6 @@ export class CustomLiquid extends Liquid {
});

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

const $ = load(html);

if (!indexPattern.test(scope.page.inputPath)) {
if (indexPattern.test(scope.page.inputPath)) {
// Remove empty list items due to obsolete technique link removal
if (scope.isTechniques) $("ul.toc-wcag-docs li:empty").remove();
} else {
const $title = $("title");

if (scope.isTechniques) {
$("title").text(`${scope.technique.id}: ${scope.technique.title}${titleSuffix}`);
const isObsolete =
scope.technique.obsoleteSince && scope.technique.obsoleteSince <= scope.version;
if (isObsolete) $("body").addClass("obsolete");

$title.text(
(isObsolete ? "[Obsolete] " : "") +
`${scope.technique.id}: ${scope.technique.title}${titleSuffix}`
);

const aboutBoxSelector = "section#technique .box-i";

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

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

// Update understanding links to always use base URL
// (mainly to avoid any case-sensitivity issues)
$(techniqueToUnderstandingLinkSelector).each((_, el) => {
el.attribs.href = el.attribs.href.replace(/^.*\//, scope.understandingUrl);
});
} else if (scope.isUnderstanding) {
const $title = $("title");
if (scope.guideline) {
const type = scope.guideline.type === "SC" ? "Success Criterion" : scope.guideline.type;
$title.text(
Expand All @@ -387,13 +389,20 @@ export class CustomLiquid extends Liquid {
$title.text().replace(/WCAG 2( |$)/, `WCAG ${scope.versionDecimal}$1`) + titleSuffix
);
}

// Remove Techniques section from obsolete SCs (e.g. Parsing in 2.2)
if (scope.guideline?.level === "") $("section#techniques").remove();
}

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

// Allow autogenerating missing top-level section IDs in understanding docs,
// but don't pick up incorrectly-nested sections in some techniques pages (e.g. H91)
const sectionSelector = scope.isUnderstanding ? "section" : "section[id]";
const sectionSelector = scope.isUnderstanding ? "section" : "section[id]:not(.obsolete)";
const sectionH2Selector = "h2:first-child";
const $h2Sections = $(`${sectionSelector}:has(${sectionH2Selector})`);
if ($h2Sections.length) {
Expand Down
9 changes: 9 additions & 0 deletions 11ty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ Indicates top-level path of W3C CVS checkout, for WAI site updates (via `publish

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

### `WCAG_VERBOSE`

**Usage context:** Local development, debugging

Prints more log messages that are typically noisy and uninteresting,
but may be useful if you're not seeing what you expect in the output files.

**Default:** Unset (set to any non-empty value to enable)

### `WCAG_VERSION`

**Usage context:** currently this should not be changed, pending future improvements to `21` support
Expand Down
3 changes: 3 additions & 0 deletions 11ty/guidelines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ export interface Principle extends DocNode {
num: `${number}`; // typed as string for consistency with guidelines/SC
version: "WCAG20";
guidelines: Guideline[];
type: "Principle";
}

export interface Guideline extends DocNode {
content: string;
num: `${Principle["num"]}.${number}`;
version: `WCAG${"20" | "21"}`;
successCriteria: SuccessCriterion[];
type: "Guideline";
}

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

export function isSuccessCriterion(criterion: any): criterion is SuccessCriterion {
Expand Down
46 changes: 40 additions & 6 deletions 11ty/techniques.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import type { Cheerio } from "cheerio";
import { glob } from "glob";
import matter from "gray-matter";
import capitalize from "lodash-es/capitalize";
import uniqBy from "lodash-es/uniqBy";

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

import { load, loadFromFile } from "./cheerio";
import { isSuccessCriterion, type FlatGuidelinesMap, type SuccessCriterion } from "./guidelines";
import {
assertIsWcagVersion,
isSuccessCriterion,
type FlatGuidelinesMap,
type SuccessCriterion,
type WcagVersion,
} from "./guidelines";
import { wcagSort } from "./common";

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

function assertIsTechnology(
Expand Down Expand Up @@ -163,7 +170,14 @@ export async function getTechniqueAssociations(guidelines: FlatGuidelinesMap) {
return associations;
}

interface Technique {
interface TechniqueFrontMatter {
/** May be specified via front-matter; message to display RE a technique's obsolescence. */
obsoleteMessage?: string;
/** May be specified via front-matter to indicate a technique is obsolete as of this version. */
obsoleteSince?: WcagVersion;
}

export interface Technique extends TechniqueFrontMatter {
/** Letter(s)-then-number technique code; corresponds to source HTML filename */
id: string;
/** Technology this technique is filed under */
Expand Down Expand Up @@ -195,17 +209,37 @@ export async function getTechniquesByTechnology() {
{} as Record<Technology, Technique[]>
);

// Check directory data files (we don't have direct access to 11ty's data cascade here)
const technologyData: Partial<Record<Technology, any>> = {};
for (const technology of technologies) {
try {
const data = JSON.parse(
await readFile(`techniques/${technology}/${technology}.11tydata.json`, "utf8")
);
if (data) technologyData[technology] = data;
} catch {}
}

for (const path of paths) {
const [technology, filename] = path.split("/");
assertIsTechnology(technology);
// Support front-matter within HTML files
const { content, data: frontMatterData } = matter(await readFile(`techniques/${path}`, "utf8"));
const data = { ...technologyData[technology], ...frontMatterData };

if (data.obsoleteSince) {
data.obsoleteSince = "" + data.obsoleteSince;
assertIsWcagVersion(data.obsoleteSince);
}

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

const title = $h1.text();
techniques[technology].push({
...data, // Include front-matter
id: basename(filename, ".html"),
technology,
title,
Expand Down
6 changes: 6 additions & 0 deletions _includes/techniques/about.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{% if technique.obsoleteSince <= version %}
<section class="box obsolete-message">
<h2 class="box-h">Obsolete</h2>
{% if technique.obsoleteMessage %}<div class="box-i">{{ technique.obsoleteMessage }}</div>{% endif %}
</section>
{% endif %}
{% sectionbox "technique" "About this Technique" %}
{% include "techniques/applicability.html" %}
{% case technique.technology %}
Expand Down
17 changes: 16 additions & 1 deletion css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,21 @@ dt div {
margin-top: 0;
}

.obsolete {
border-left: solid 5px var(--faded-red);
}

.obsolete-message {
background-color: var(--red-subtle);
border-color: var(--faded-red);
}

.obsolete-message h2 {
color: #fff;
background-color: var(--faded-red);
margin: 0;
}

/* import inline styles from https://www.w3.org/WAI/drafts/WCAG-understanding-redesign-hack.html */
.nav a:link {
text-decoration: none;
Expand Down Expand Up @@ -442,4 +457,4 @@ margin-right:.8em;
.minimal-header-logo svg {
margin: 1em 0 0 0;
}
}
}
75 changes: 74 additions & 1 deletion eleventy.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ import compact from "lodash-es/compact";
import { copyFile } from "fs/promises";

import { CustomLiquid } from "11ty/CustomLiquid";
import { actRules, assertIsWcagVersion, getFlatGuidelines, getPrinciples } from "11ty/guidelines";
import {
actRules,
assertIsWcagVersion,
getFlatGuidelines,
getPrinciples,
type Guideline,
type Principle,
type SuccessCriterion,
} from "11ty/guidelines";
import {
getFlatTechniques,
getTechniqueAssociations,
getTechniquesByTechnology,
technologies,
technologyTitles,
type Technique,
type Technology,
} from "11ty/techniques";
import { generateUnderstandingNavMap, getUnderstandingDocs } from "11ty/understanding";
import type { EleventyContext, EleventyData, EleventyEvent } from "11ty/types";
Expand All @@ -18,11 +28,40 @@ import type { EleventyContext, EleventyData, EleventyEvent } from "11ty/types";
const version = process.env.WCAG_VERSION || "22";
assertIsWcagVersion(version);

/**
* Returns boolean indicating whether a technique is obsolete for the given version.
* Tolerates undefined for use with hash lookups.
*/
const isTechniqueObsolete = (technique: Technique | undefined) =>
!!technique?.obsoleteSince && technique.obsoleteSince <= version;

/**
* Returns boolean indicating whether an SC is obsolete for the given version.
* Tolerates other types for use with hash lookups.
*/
const isGuidelineObsolete = (guideline: Principle | Guideline | SuccessCriterion | undefined) =>
guideline?.type === "SC" && guideline.level === "";

const principles = await getPrinciples();
const flatGuidelines = getFlatGuidelines(principles);
const techniques = await getTechniquesByTechnology();
const flatTechniques = getFlatTechniques(techniques);

for (const [technology, list] of Object.entries(techniques)) {
// Prune obsolete techniques from ToC
techniques[technology as Technology] = list.filter(
(technique) => !technique.obsoleteSince || technique.obsoleteSince > version
);
}

const techniqueAssociations = await getTechniqueAssociations(flatGuidelines);
for (const [id, associations] of Object.entries(techniqueAssociations)) {
// Prune associations from non-obsolete techniques to obsolete SCs
techniqueAssociations[id] = associations.filter(
({ criterion }) => criterion.level !== "" || isTechniqueObsolete(flatTechniques[id])
);
}

const understandingDocs = await getUnderstandingDocs(version);
const understandingNav = await generateUnderstandingNavMap(principles, understandingDocs);

Expand Down Expand Up @@ -164,6 +203,23 @@ export default function (eleventyConfig: any) {
);
return;
}

// Omit links to obsolete techniques, when not also linked from one
if (
isTechniqueObsolete(technique) &&
!isTechniqueObsolete(flatTechniques[this.page.fileSlug]) &&
!isGuidelineObsolete(flatGuidelines[this.page.fileSlug])
) {
if (process.env.WCAG_VERBOSE) {
const since = technique.obsoleteSince!.split("").join(".");
console.warn(
`linkTechniques in ${this.page.inputPath}: ` +
`skipping obsolete technique ${id} (as of ${since})`
);
}
return;
}

// Support relative technique links from other techniques or from techniques/index.html,
// otherwise path-absolute when cross-linked from understanding/*
const urlBase = /^\/techniques\/.*\//.test(this.page.filePathStem)
Expand Down Expand Up @@ -191,7 +247,24 @@ export default function (eleventyConfig: any) {
`linkUnderstanding in ${this.page.inputPath}: ` +
`skipping unresolvable guideline shortname ${id}`
);
return;
}

// Warn of links to obsolete SCs, when not also linked from one.
// This is intentionally not behind WCAG_VERBOSE, and does not remove,
// as links to Understanding docs are more likely to be in the middle
// of prose requiring manual adjustments
if (
isGuidelineObsolete(guideline) &&
!isGuidelineObsolete(flatGuidelines[this.page.fileSlug]) &&
!isTechniqueObsolete(flatTechniques[this.page.fileSlug])
) {
console.warn(
`linkUnderstanding in ${this.page.inputPath}: ` +
`reference to obsolete ${guideline.type} ${id}`
);
}

const urlBase = this.page.filePathStem.startsWith("/understanding/")
? ""
: baseUrls.understanding;
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@11ty/eleventy": "^3.0.0-alpha.14",
"cheerio": "^1.0.0-rc.12",
"glob": "^10.3.16",
"gray-matter": "^4.0.3",
"liquidjs": "^10.14.0",
"lodash-es": "^4.17.21",
"mkdirp": "^3.0.1",
Expand Down
Loading

0 comments on commit 42597e3

Please sign in to comment.