Skip to content

Commit 6ec050d

Browse files
committed
spruce up the in-document output
1 parent 8e764f6 commit 6ec050d

File tree

6 files changed

+181
-33
lines changed

6 files changed

+181
-33
lines changed

src/format/html/format-html-axe.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ export function axeFormatDependencies(
1414
temp: TempContext,
1515
options?: unknown,
1616
): FormatExtras {
17-
if (!options) {
18-
return {};
19-
}
17+
if (!options) return {};
2018

2119
return {
2220
[kIncludeInHeader]: [
@@ -26,5 +24,41 @@ export function axeFormatDependencies(
2624
}</script>`,
2725
),
2826
],
27+
html: {
28+
"sass-bundles": [
29+
{
30+
key: "axe",
31+
dependency: "bootstrap",
32+
user: [{
33+
uses: "",
34+
defaults: "",
35+
functions: "",
36+
mixins: "",
37+
rules: `
38+
body div.quarto-axe-report {
39+
position: fixed;
40+
bottom: 3rem;
41+
right: 3rem;
42+
padding: 1rem;
43+
border: 1px solid $body-color;
44+
}
45+
46+
.quarto-axe-violation-help { padding-left: 0.5rem; }
47+
.quarto-axe-violation-selector { padding-left: 1rem; }
48+
.quarto-axe-violation-target {
49+
padding: 0.5rem;
50+
color: $link-color;
51+
text-decoration: underline;
52+
cursor: pointer;
53+
}
54+
55+
.quarto-axe-hover-highlight {
56+
background-color: red;
57+
border: 1px solid $body-color;
58+
}`,
59+
}],
60+
},
61+
],
62+
},
2963
};
3064
}

src/resources/editor/tools/vs-code.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24334,12 +24334,12 @@ var require_yaml_intelligence_resources = __commonJS({
2433424334
mermaid: "%%"
2433524335
},
2433624336
"handlers/mermaid/schema.yml": {
24337-
_internalId: 196509,
24337+
_internalId: 196437,
2433824338
type: "object",
2433924339
description: "be an object",
2434024340
properties: {
2434124341
"mermaid-format": {
24342-
_internalId: 196501,
24342+
_internalId: 196429,
2434324343
type: "enum",
2434424344
enum: [
2434524345
"png",
@@ -24355,7 +24355,7 @@ var require_yaml_intelligence_resources = __commonJS({
2435524355
exhaustiveCompletions: true
2435624356
},
2435724357
theme: {
24358-
_internalId: 196508,
24358+
_internalId: 196436,
2435924359
type: "anyOf",
2436024360
anyOf: [
2436124361
{
@@ -24407,9 +24407,11 @@ var require_yaml_intelligence_resources = __commonJS({
2440724407
properties: {
2440824408
output: {
2440924409
enum: [
24410-
"json"
24410+
"json",
24411+
"console",
24412+
"document"
2441124413
],
24412-
description: "If true, output axe-core results on console in JSON format."
24414+
description: "If set, output axe-core results on console. `json`: produce structured output; `console`: print output to javascript console; `document`: produce a visual report of violations in the document itself."
2441324415
}
2441424416
}
2441524417
}

src/resources/editor/tools/yaml/web-worker.js

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/resources/editor/tools/yaml/yaml-intelligence-resources.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17306,12 +17306,12 @@
1730617306
"mermaid": "%%"
1730717307
},
1730817308
"handlers/mermaid/schema.yml": {
17309-
"_internalId": 196509,
17309+
"_internalId": 196437,
1731017310
"type": "object",
1731117311
"description": "be an object",
1731217312
"properties": {
1731317313
"mermaid-format": {
17314-
"_internalId": 196501,
17314+
"_internalId": 196429,
1731517315
"type": "enum",
1731617316
"enum": [
1731717317
"png",
@@ -17327,7 +17327,7 @@
1732717327
"exhaustiveCompletions": true
1732817328
},
1732917329
"theme": {
17330-
"_internalId": 196508,
17330+
"_internalId": 196436,
1733117331
"type": "anyOf",
1733217332
"anyOf": [
1733317333
{
@@ -17379,9 +17379,11 @@
1737917379
"properties": {
1738017380
"output": {
1738117381
"enum": [
17382-
"json"
17382+
"json",
17383+
"console",
17384+
"document"
1738317385
],
17384-
"description": "If true, output axe-core results on console in JSON format."
17386+
"description": "If set, output axe-core results on console. `json`: produce structured output; `console`: print output to javascript console; `document`: produce a visual report of violations in the document itself."
1738517387
}
1738617388
}
1738717389
}

src/resources/formats/html/axe/axe-check.js

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
class QuartoAxeChecker {
2-
constructor(opts) {
3-
this.options = opts;
1+
class QuartoAxeReporter {
2+
constructor(axeResult, options) {
3+
this.axeResult = axeResult;
4+
this.options = options;
45
}
5-
async init() {
6-
const axe = (await import("https://cdn.skypack.dev/pin/[email protected]/mode=imports,min/optimized/axe-core.js")).default;
7-
const result = await axe.run({
8-
preload: { assets: ['cssom'], timeout: 50000 }
9-
});
10-
if (this.options.output === "json") {
11-
console.log(JSON.stringify(result, null, 2));
12-
return;
13-
}
14-
for (const violation of result.violations) {
6+
7+
report() {
8+
throw new Error("report() is an abstract method");
9+
}
10+
}
11+
12+
class QuartoAxeJsonReporter extends QuartoAxeReporter {
13+
constructor(axeResult, options) {
14+
super(axeResult, options);
15+
}
16+
17+
report() {
18+
console.log(JSON.stringify(this.axeResult, null, 2));
19+
}
20+
}
21+
22+
class QuartoAxeConsoleReporter extends QuartoAxeReporter {
23+
constructor(axeResult, options) {
24+
super(axeResult, options);
25+
}
26+
27+
report() {
28+
for (const violation of this.axeResult.violations) {
1529
console.log(violation.description);
1630
for (const node of violation.nodes) {
1731
for (const target of node.target) {
@@ -23,6 +37,100 @@ class QuartoAxeChecker {
2337
}
2438
}
2539

40+
class QuartoAxeDocumentReporter extends QuartoAxeReporter {
41+
constructor(axeResult, options) {
42+
super(axeResult, options);
43+
}
44+
45+
createViolationElement(violation) {
46+
const violationElement = document.createElement("div");
47+
48+
const descriptionElement = document.createElement("div");
49+
descriptionElement.className = "quarto-axe-violation-description";
50+
descriptionElement.innerText = `${violation.impact.replace(/^[a-z]/, match => match.toLocaleUpperCase())}: ${violation.description}`;
51+
violationElement.appendChild(descriptionElement);
52+
53+
const helpElement = document.createElement("div");
54+
helpElement.className = "quarto-axe-violation-help";
55+
helpElement.innerText = violation.help;
56+
violationElement.appendChild(helpElement);
57+
58+
const nodesElement = document.createElement("div");
59+
nodesElement.className = "quarto-axe-violation-nodes";
60+
violationElement.appendChild(nodesElement);
61+
const nodeElement = document.createElement("div");
62+
nodeElement.className = "quarto-axe-violation-selector";
63+
for (const node of violation.nodes) {
64+
for (const target of node.target) {
65+
const targetElement = document.createElement("span");
66+
targetElement.className = "quarto-axe-violation-target";
67+
targetElement.innerText = target;
68+
nodeElement.appendChild(targetElement);
69+
nodeElement.addEventListener("mouseenter", () => {
70+
const element = document.querySelector(target);
71+
if (element) {
72+
element.scrollIntoView({ behavior: "smooth", block: "center" });
73+
element.classList.add("quarto-axe-hover-highlight");
74+
setTimeout(() => {
75+
element.style.border = "";
76+
}, 2000);
77+
}
78+
});
79+
nodeElement.addEventListener("mouseleave", () => {
80+
const element = document.querySelector(target);
81+
if (element) {
82+
element.classList.remove("quarto-axe-hover-highlight");
83+
}
84+
});
85+
nodeElement.addEventListener("click", () => {
86+
console.log(document.querySelector(target));
87+
});
88+
nodeElement.appendChild(targetElement);
89+
}
90+
nodesElement.appendChild(nodeElement);
91+
}
92+
return violationElement;
93+
}
94+
95+
report() {
96+
const violations = this.axeResult.violations;
97+
const reportElement = document.createElement("div");
98+
reportElement.className = "quarto-axe-report";
99+
violations.forEach((violation) => {
100+
reportElement.appendChild(this.createViolationElement(violation));
101+
});
102+
document.body.appendChild(reportElement);
103+
}
104+
}
105+
106+
const reporters = {
107+
json: QuartoAxeJsonReporter,
108+
console: QuartoAxeConsoleReporter,
109+
document: QuartoAxeDocumentReporter,
110+
};
111+
112+
class QuartoAxeChecker {
113+
constructor(opts) {
114+
this.options = opts;
115+
}
116+
async init() {
117+
const axe = (await import("https://cdn.skypack.dev/pin/[email protected]/mode=imports,min/optimized/axe-core.js")).default;
118+
119+
120+
// https://github.com/microsoft/tabster/issues/288
121+
// MS has claimed they won't fix this, so we need to add an exclusion to
122+
// all tabster elements
123+
const result = await axe.run({
124+
exclude: [
125+
"[data-tabster-dummy]"
126+
],
127+
preload: { assets: ['cssom'], timeout: 50000 }
128+
});
129+
const reporter = this.options === true ? new QuartoAxeConsoleReporter(result) : new reporters[this.options.output](result, this.options);
130+
reporter.report();
131+
}
132+
}
133+
26134
export async function init() {
27135
const opts = document.querySelector("#quarto-axe-checker-options");
28136
if (opts) {

src/resources/schema/document-a11y.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
- object:
66
properties:
77
output:
8-
enum: [json]
9-
description: "If true, output axe-core results on console in JSON format."
8+
enum: [json, console, document]
9+
description: "If set, output axe-core results on console. `json`: produce structured output; `console`: print output to javascript console; `document`: produce a visual report of violations in the document itself."
1010
description: "When defined, run axe-core accessibility tests on the document."

0 commit comments

Comments
 (0)