Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support marking obsolete techniques and restore removed ones #3975

Merged
merged 20 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
107139d
Add directory-level obsolescence data for Flash and Silverlight
kfranqueiro Jul 11, 2024
917c813
Restore Flash and Silverlight techniques
kfranqueiro Jul 12, 2024
d8005e4
Mark 4.1.1-only techniques as obsolete
kfranqueiro Jul 11, 2024
938c15f
H74: Remove duplicate list items
kfranqueiro Jul 18, 2024
ff6d04c
Revert "Remove H75 (#3650)" and add obsolete front-matter
kfranqueiro Jul 12, 2024
42d1ea6
Restore techniques removed in #2490, with obsolete front-matter
kfranqueiro Jul 12, 2024
8c3164b
Restore H45 from #2503 and add obsolete front-matter
kfranqueiro Jul 12, 2024
26f18c3
Restore H59 from #2970 and add obsolete front-matter
kfranqueiro Jul 12, 2024
d457181
Restore H70 from #3648 and add obsolete front-matter
kfranqueiro Jul 12, 2024
f3fe7b7
Restore SCR37 from #3024 and add obsolete front-matter
kfranqueiro Jul 12, 2024
afaf1dc
SCR37: Fix formatting and related resources; remove working-example link
kfranqueiro Jul 17, 2024
f40cbc2
Restore G187 from #3736 and add obsolete front-matter
kfranqueiro Jul 12, 2024
7ccbe0a
Restore F87 from #3818 and add obsolete front-matter
kfranqueiro Jul 13, 2024
55fab3c
Add support for obsolete techniques
kfranqueiro Jul 17, 2024
4b42750
Make Obsolete section stand out more
kfranqueiro Jul 19, 2024
ea69ac8
Update obsolete styles; add body class and prepend to title
iadawn Jul 22, 2024
6ebc359
Update DOM for obsolete notice and move to _includes
kfranqueiro Jul 22, 2024
3d668b3
Formatting + variable reuse
kfranqueiro Jul 22, 2024
971b55d
Add links to Flash/Silverlight EOL
kfranqueiro Aug 6, 2024
407efdf
Add detail as to when table summary became obsolete
kfranqueiro Aug 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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