Skip to content

Commit

Permalink
Add support for obsolete techniques
Browse files Browse the repository at this point in the history
  • Loading branch information
kfranqueiro committed Jul 18, 2024
1 parent 80a9b3f commit 6fdd695
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 19 deletions.
29 changes: 17 additions & 12 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,7 +301,10 @@ 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 {
if (scope.isTechniques) {
$("title").text(`${scope.technique.id}: ${scope.technique.title}${titleSuffix}`);
const aboutBoxSelector = "section#technique .box-i";
Expand Down Expand Up @@ -358,18 +358,20 @@ 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") {
if (scope.technique.obsoleteSince && scope.technique.obsoleteSince <= scope.version) {
$(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>"
"\n",
scope.technique.obsoleteMessage
? `<p><em><strong>Obsolete:</strong> ${scope.technique.obsoleteMessage}</em></p>`
: "<p><em><strong>Obsolete</strong></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) => {
Expand All @@ -387,6 +389,9 @@ 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,
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
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 @@ -159,6 +198,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 @@ -186,7 +242,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
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"module": "ES2022",
"moduleResolution": "Node",
Expand Down

0 comments on commit 6fdd695

Please sign in to comment.