-
Notifications
You must be signed in to change notification settings - Fork 1
Added the glossary page #53
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
Open
G0dwin
wants to merge
11
commits into
main
Choose a base branch
from
feature/glossary
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
69f4051
Added the glossary page
G0dwin d8787e6
Fixed an issue with the Glossary nav link
G0dwin 31cfb5a
Updated URL
G0dwin 1f3b836
Reused code between get-glossary and generate-glossary
G0dwin d6387cc
Update src/cli/generate-glossary.ts
G0dwin 91f0816
Update src/cli/generate-glossary.ts
G0dwin 82f624f
Update src/cli/generate-glossary.ts
G0dwin 61dea9c
Update src/cli/generate-glossary.ts
G0dwin 037816c
Update src/cli/generate-glossary.ts
G0dwin 6aa3ade
Removed unused function and added tests
G0dwin 0b17c67
Merge origin/main into feature/glossary - resolve yarn.lock conflict
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { normalizeHeadingLevels } from "../generate-glossary"; | ||
|
|
||
| describe("normalizeHeadingLevels", () => { | ||
| it("decrements all heading hashes by one level, preserves #", () => { | ||
| const input = `#### Item\n##### Subitem\n###### Deep`; | ||
| const output = normalizeHeadingLevels(input); | ||
|
|
||
| expect(output).toBe(`### Item\n#### Subitem\n##### Deep`); | ||
| }); | ||
|
|
||
| it("preserves leading spaces before headings", () => { | ||
| const input = ` #### indented`; | ||
| const output = normalizeHeadingLevels(input); | ||
|
|
||
| expect(output).toBe(" ### indented"); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| #!/usr/bin/env ts-node | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
| import yaml from "js-yaml"; | ||
| import { Command } from "commander"; | ||
| import { DefinitionPage } from "../types"; | ||
| import { getRulePages, getDefinitionPages } from "../utils/get-page-data"; | ||
| import { | ||
| getGlossaryBody, | ||
| getGlossaryHeading, | ||
| normalizeHeadingLevels as normalizeGlossaryHeadingLevels, | ||
| } from "../utils/glossary"; | ||
| import { getRuleDefinitions } from "../act/get-rule-definitions"; | ||
|
|
||
| interface GlossaryOptions { | ||
| rulesDir: string; | ||
| glossaryDir: string; | ||
| testAssetsDir?: string; | ||
| outDir: string; | ||
| wcagActRulesDir?: string; | ||
| } | ||
|
|
||
| const program = new Command(); | ||
| program | ||
| .requiredOption("-r, --rulesDir <dirname>", "Path to _rules directory") | ||
| .requiredOption("-g, --glossaryDir <dirname>", "Path to glossary directory") | ||
| .option("-t, --testAssetsDir <dirname>", "Path to test-assets directory", "") | ||
| .requiredOption("-o, --outDir <dirname>", "Path to output directory") | ||
| .option( | ||
| "--wcagActRulesDir <dirname>", | ||
| "Path to wcag-act-rules checkout directory for config nav injection", | ||
| ); | ||
|
|
||
| function buildUsedInRulesMap( | ||
| rulesDir: string, | ||
| glossaryDir: string, | ||
| testAssetsDir: string, | ||
| ) { | ||
| const rules = getRulePages(rulesDir, testAssetsDir || "."); | ||
| const glossary = getDefinitionPages(glossaryDir); | ||
|
|
||
| const usedInRules = new Map<string, Set<{ id: string; name: string }>>(); | ||
| glossary.forEach((definition) => { | ||
| usedInRules.set(definition.frontmatter.key, new Set()); | ||
| }); | ||
|
|
||
| rules.forEach((rule) => { | ||
| const ruleDefinitions = getRuleDefinitions(rule, glossary); | ||
| const ruleDefKeys = new Set( | ||
| ruleDefinitions.map((def) => def.frontmatter.key), | ||
| ); | ||
|
|
||
| ruleDefKeys.forEach((key) => { | ||
| if (!usedInRules.has(key)) return; | ||
| usedInRules | ||
| .get(key) | ||
| ?.add({ id: rule.frontmatter.id, name: rule.frontmatter.name }); | ||
| }); | ||
| }); | ||
|
|
||
| return { glossary, usedInRules }; | ||
| } | ||
|
|
||
| export function normalizeHeadingLevels(body: string): string { | ||
| return normalizeGlossaryHeadingLevels(body); | ||
| } | ||
|
|
||
| function generateGlossaryContent( | ||
| glossaryDefinitions: DefinitionPage[], | ||
| usedInRules: Map<string, Set<{ id: string; name: string }>>, | ||
| ): string { | ||
| const lines: string[] = []; | ||
|
|
||
| lines.push("---"); | ||
| lines.push("layout: standalone_resource"); | ||
| lines.push('title: "ACT Rules Glossary"'); | ||
| lines.push("permalink: /standards-guidelines/act/rules/terms/"); | ||
| lines.push("ref: /standards-guidelines/act/rules/terms/"); | ||
| lines.push("lang: en"); | ||
| lines.push('type_of_guidance: ""'); | ||
| lines.push("feedbackmail: public-wcag-act@w3.org"); | ||
| lines.push('footer: ""'); | ||
| lines.push("github:"); | ||
| lines.push(" repository: w3c/wcag-act-rules"); | ||
| lines.push(" path: content/terms.md"); | ||
| lines.push("---"); | ||
| lines.push(""); | ||
|
|
||
| glossaryDefinitions.forEach((def) => { | ||
| const key = def.frontmatter.key; | ||
| const title = def.frontmatter.title; | ||
| const body = getGlossaryBody(def, { | ||
| mode: "full", | ||
| normalizeHeadings: true, | ||
| }); | ||
|
|
||
| lines.push(getGlossaryHeading({ title, key }, 2)); | ||
| lines.push(""); | ||
| lines.push(body); | ||
| lines.push(""); | ||
| lines.push("### Used in rules"); | ||
|
|
||
| const rules = [...(usedInRules.get(key) || new Set())].sort((a, b) => | ||
| a.id.localeCompare(b.id), | ||
| ); | ||
| if (rules.length === 0) { | ||
| lines.push("- None"); | ||
| } else { | ||
| rules.forEach((rule) => { | ||
| lines.push( | ||
| `- [${rule.name}](/standards-guidelines/act/rules/${rule.id}/proposed/)`, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| lines.push(""); | ||
| }); | ||
|
|
||
|
|
||
| return lines.join("\n"); | ||
| } | ||
|
|
||
| async function generateFile(options: GlossaryOptions): Promise<void> { | ||
| const { glossary, usedInRules } = buildUsedInRulesMap( | ||
| options.rulesDir, | ||
| options.glossaryDir, | ||
| options.testAssetsDir || "", | ||
| ); | ||
|
|
||
| const content = generateGlossaryContent(glossary, usedInRules); | ||
| const outputDir = path.join(options.outDir, "content"); | ||
| const outputFile = path.join(outputDir, "terms.md"); | ||
|
|
||
| await fs.promises.mkdir(outputDir, { recursive: true }); | ||
| await fs.promises.writeFile(outputFile, content, "utf8"); | ||
| console.log(`Created glossary at ${outputFile}`); | ||
|
|
||
| await updateWcagConfigNav(options.outDir); | ||
| } | ||
|
|
||
| async function updateWcagConfigNav(outputDir: string) { | ||
| const configPath = path.join(outputDir, "_config.yml"); | ||
| const configContent = await fs.promises.readFile(configPath, "utf8"); | ||
| const configData: any = yaml.load(configContent); | ||
|
|
||
| if (!configData?.defaults) return; | ||
|
|
||
| const defaultValues = configData.defaults.find( | ||
| (item: any) => item?.values?.standalone_resource_nav_links, | ||
| ); | ||
| if (!defaultValues) return; | ||
|
|
||
| const navLinks = defaultValues.values.standalone_resource_nav_links; | ||
| const hasGlossary = navLinks.some( | ||
| (link: any) => link.ref === "/standards-guidelines/act/rules/terms/", | ||
| ); | ||
|
|
||
| if (!hasGlossary) { | ||
| navLinks.push({ | ||
| name: "Glossary", | ||
| ref: "/standards-guidelines/act/rules/terms/", | ||
| }); | ||
| await fs.promises.writeFile(configPath, yaml.dump(configData), "utf8"); | ||
| console.log( | ||
| "Updated wcag-act-rules _config.yml to include glossary nav link.", | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| if (require.main === module) { | ||
| program.parse(process.argv); | ||
| const options = program.opts<GlossaryOptions>(); | ||
|
|
||
| generateFile(options) | ||
| .then(() => process.exit(0)) | ||
| .catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import outdent from "outdent"; | ||
| import { | ||
| getGlossaryHeading, | ||
| normalizeHeadingLevels, | ||
| getGlossaryBody, | ||
| } from "../glossary"; | ||
|
|
||
| describe("utils", () => { | ||
| describe("getGlossaryHeading", () => { | ||
| it("returns a heading with the correct level", () => { | ||
| const heading = getGlossaryHeading( | ||
| { title: "Outcome", key: "outcome" }, | ||
| 2, | ||
| ); | ||
| expect(heading).toBe("## Outcome {#outcome}"); | ||
| }); | ||
|
|
||
| it("returns a level-3 heading when level is 3", () => { | ||
| const heading = getGlossaryHeading( | ||
| { title: "Visible", key: "visible" }, | ||
| 3, | ||
| ); | ||
| expect(heading).toBe("### Visible {#visible}"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("normalizeHeadingLevels", () => { | ||
| it("decrements all heading levels by one", () => { | ||
| const input = "#### Item\n##### Subitem\n###### Deep"; | ||
| expect(normalizeHeadingLevels(input)).toBe( | ||
| "### Item\n#### Subitem\n##### Deep", | ||
| ); | ||
| }); | ||
|
|
||
| it("does not decrement level-1 headings", () => { | ||
| const input = "# Top\n## Section"; | ||
| expect(normalizeHeadingLevels(input)).toBe("# Top\n# Section"); | ||
| }); | ||
|
|
||
| it("preserves leading spaces before headings", () => { | ||
| expect(normalizeHeadingLevels(" #### indented")).toBe(" ### indented"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("getGlossaryBody", () => { | ||
| const noMarkdownAST = { children: [] }; | ||
|
|
||
| describe('mode: "full"', () => { | ||
| it("returns the trimmed full body", () => { | ||
| const definition = { | ||
| body: " Some content\n\n### Sub\n\nMore. ", | ||
| markdownAST: noMarkdownAST, | ||
| }; | ||
| expect(getGlossaryBody(definition, { mode: "full" })).toBe( | ||
| "Some content\n\n### Sub\n\nMore.", | ||
| ); | ||
| }); | ||
|
|
||
| it("normalizes heading levels when normalizeHeadings is true", () => { | ||
| const definition = { | ||
| body: "First.\n\n### Sub\n", | ||
| markdownAST: noMarkdownAST, | ||
| }; | ||
| expect( | ||
| getGlossaryBody(definition, { | ||
| mode: "full", | ||
| normalizeHeadings: true, | ||
| }), | ||
| ).toBe("First.\n\n## Sub"); | ||
| }); | ||
| }); | ||
|
|
||
| describe('mode: "rule"', () => { | ||
| it("strips content from the first ## heading onwards", () => { | ||
| const definition = { | ||
| body: outdent` | ||
| You can see it. | ||
|
|
||
| ## References | ||
|
|
||
| Ignore me | ||
| `, | ||
| markdownAST: noMarkdownAST, | ||
| }; | ||
| expect(getGlossaryBody(definition, { mode: "rule" })).toBe( | ||
| "You can see it.", | ||
| ); | ||
| }); | ||
|
|
||
| it("strips trailing reference definitions when there is no heading", () => { | ||
| const refOffset = "You can see [it][].\n\n".length; | ||
| const definition = { | ||
| body: "You can see [it][].\n\n[it]: https://w3.org/\n", | ||
| markdownAST: { | ||
| children: [ | ||
| { | ||
| type: "definition", | ||
| position: { start: { offset: refOffset } }, | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
| expect(getGlossaryBody(definition, { mode: "rule" })).toBe( | ||
| "You can see [it][].", | ||
| ); | ||
| }); | ||
|
|
||
| it("returns full trimmed body when there is no heading and no reference definitions", () => { | ||
| const definition = { | ||
| body: " Plain content. ", | ||
| markdownAST: noMarkdownAST, | ||
| }; | ||
| expect(getGlossaryBody(definition, { mode: "rule" })).toBe( | ||
| "Plain content.", | ||
| ); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Used in Rules" list can become quite big and require a lot of scrolling from users. For example, the list for the "Outcome" term consists of 94 items.
Before considering possible solutions, could you clarify who this section is intended for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this adds a large amount of visual space. I think embedding this content in summary and details elements is probably the best solution but I that would be assuming use cases myself. This came from the issue requirements:
I think it may also be a good option to move forward with the page as it is and adjust this later if it holds us up.
@WilcoFiers , do you have any context or suggestions to add here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi.
I am not even sure if we should have this "used in rules" at all.
I think the primary use case for a dedicated (self-contained) terms page is for rule authors to know whether or not something is or is not currently defined by ACT.
If you want to know which rules use which terms, you could get to the specific rule pages and see the terms at the bottom. I see this as a secondary need that is not worth the extra clutter.
If folks think strongly about keeping this, then I would support the details/summary alternative, but that should be done in alignment with how the expand/collapse single and expand/collapse multiple currently work on the wai website theme
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep this section. Yeah it's more for rule authors than other people, but this page is mostly for rule authors anyway. I'm okay with using a details / summary thing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second (or third?) keeping the "used in rules" section. It is a common use case for me to search all rules using a given definition, typically to assess which rules will be impacted by changes in the definition. I'm currently doing that somewhat manually through
grep, which is not perfect due to transitive definition inclusion, … (and going through all rules pages is not a real possibility).A details/summary is perfect for that, indeed.
(for a recent case: searching all rules that use "marked as decorative" to evaluate impact of "whitespace alt is decorative")