Skip to content

Commit aa96a60

Browse files
committed
Add PREFER_MATHML preference for screenreader users
Currently we fall back to MathML if we fail to generate screenreader text. But some accessibility tool users find the MathML alternative more useful than the generated text. Using a user preference will allow us to test this before committing to any changes.
1 parent 933fd1c commit aa96a60

File tree

4 files changed

+30
-20
lines changed

4 files changed

+30
-20
lines changed

src/IsaacAppTypes.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ export interface BooleanNotation {
243243

244244
export interface DisplaySettings {
245245
HIDE_QUESTION_ATTEMPTS?: boolean;
246+
PREFER_MATHML?: boolean;
246247
}
247248

248249
export interface UserConsent {

src/app/components/elements/markup/latexRendering.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import {useContext} from "react";
2-
import {selectors, useAppSelector, useGetSegueEnvironmentQuery} from "../../../state";
3-
import {FigureNumberingContext, FigureNumbersById, PotentialUser} from "../../../../IsaacAppTypes";
2+
import {useGetSegueEnvironmentQuery} from "../../../state";
3+
import {FigureNumberingContext, FigureNumbersById} from "../../../../IsaacAppTypes";
44
import he from "he";
5-
import {dropZoneRegex, renderA11yString, BOOLEAN_NOTATION, isAda, useUserPreferences} from "../../../services";
5+
import {BOOLEAN_NOTATION, dropZoneRegex, isAda, renderA11yString, useUserPreferences} from "../../../services";
66
import katex, {KatexOptions} from "katex";
77
import 'katex/dist/contrib/mhchem.mjs';
8-
import {Immutable} from "immer";
98

109
type MathJaxMacro = string|[string, number];
1110

@@ -220,7 +219,13 @@ const ENDREF = "==ENDREF==";
220219
const REF_REGEXP = new RegExp(REF + "(.*?)" + ENDREF, "g");
221220
const SR_REF_REGEXP = new RegExp("start text, " + REF_REGEXP.source + ", end text,", "g");
222221

223-
export function katexify(html: string, user: Immutable<PotentialUser> | null, booleanNotation : BOOLEAN_NOTATION | undefined, showScreenReaderHoverText: boolean, figureNumbers: FigureNumbersById) {
222+
export function katexify(
223+
html: string,
224+
booleanNotation : BOOLEAN_NOTATION | undefined,
225+
showScreenReaderHoverText: boolean,
226+
preferMathML: boolean,
227+
figureNumbers: FigureNumbersById
228+
) {
224229
start.lastIndex = 0;
225230
let match: RegExpExecArray | null;
226231
let output = "";
@@ -287,8 +292,8 @@ export function katexify(html: string, user: Immutable<PotentialUser> | null, bo
287292
// Until https://github.com/KaTeX/KaTeX/issues/3668 is resolved, apply suggested fix ourselves, awfully:
288293
katexRenderResult = katexRenderResult.replaceAll("color:transparent;", "color:transparent;visibility:hidden;");
289294

290-
// If katex-a11y fails, generate MathML using KaTeX for accessibility
291-
if (screenReaderText) {
295+
// If katex-a11y fails, or MathML preferred, generate MathML using KaTeX for accessibility:
296+
if (!preferMathML && screenReaderText) {
292297
katexRenderResult = katexRenderResult.replace('<span class="katex">',
293298
`<span class="katex"><span class="visually-hidden" aria-label="${screenReaderText}" role="text"></span>`);
294299
} else {
@@ -333,10 +338,9 @@ export function katexify(html: string, user: Immutable<PotentialUser> | null, bo
333338

334339
// A hook wrapper around katexify that gets its required parameters from the current redux state and existing figure numbering context
335340
export const useRenderKatex = () => {
336-
const user = useAppSelector(selectors.user.orNull);
337341
const {data: segueEnvironment} = useGetSegueEnvironmentQuery();
338-
const {preferredBooleanNotation} = useUserPreferences();
342+
const {preferredBooleanNotation, preferMathML} = useUserPreferences();
339343
const figureNumbers = useContext(FigureNumberingContext);
340344

341-
return (markup: string) => katexify(markup, user, preferredBooleanNotation && BOOLEAN_NOTATION[preferredBooleanNotation], segueEnvironment === "DEV", figureNumbers);
345+
return (markup: string) => katexify(markup, preferredBooleanNotation && BOOLEAN_NOTATION[preferredBooleanNotation], segueEnvironment === "DEV", preferMathML ?? false, figureNumbers);
342346
};

src/app/services/userPreferences.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { AppState, useAppSelector } from '../state';
44
interface UseUserPreferencesReturnType {
55
preferredProgrammingLanguage?: PROGRAMMING_LANGUAGE;
66
preferredBooleanNotation?: BOOLEAN_NOTATION;
7+
preferMathML?: boolean;
78
}
89

910
export function useUserPreferences(): UseUserPreferencesReturnType {
1011
const {examBoard} = useUserViewingContext();
1112

12-
const {PROGRAMMING_LANGUAGE: programmingLanguage, BOOLEAN_NOTATION: booleanNotation} =
13+
const {PROGRAMMING_LANGUAGE: programmingLanguage, BOOLEAN_NOTATION: booleanNotation, DISPLAY_SETTING: displaySettings} =
1314
useAppSelector((state: AppState) => state?.userPreferences) || {};
1415

1516
// Programming language preference -
@@ -28,8 +29,12 @@ export function useUserPreferences(): UseUserPreferencesReturnType {
2829
preferredBooleanNotation = examBoardBooleanNotationMap[examBoard];
2930
}
3031

32+
// Accessibility preferences:
33+
const preferMathML = displaySettings?.PREFER_MATHML;
34+
3135
return {
3236
preferredProgrammingLanguage,
33-
preferredBooleanNotation
37+
preferredBooleanNotation,
38+
preferMathML
3439
};
35-
}
40+
}

src/test/components/TrustedHtml.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('TrustedHtml LaTeX locator', () => {
3131
it('can find basic delimiters', () => {
3232
delimiters.forEach(([open, displayMode, close]) => {
3333
const testcase = html[0] + wrapIn(open, math[0], close) + html[1];
34-
const result = katexify(testcase, null, undefined, false, {});
34+
const result = katexify(testcase, undefined, false, false, {});
3535

3636
expect(result).toEqual(html[0] + LATEX + html[1]);
3737
// @ts-ignore
@@ -48,7 +48,7 @@ describe('TrustedHtml LaTeX locator', () => {
4848
it("unbalanced delimiters don't break everything but instead are just skipped", () => {
4949
delimiters.forEach(([open, , ]) => {
5050
const testcase = html[0] + wrapIn(open, math[0], "") + html[1];
51-
const result = katexify(testcase, null, undefined, false, {});
51+
const result = katexify(testcase, undefined, false, false, {});
5252

5353
expect(result).toEqual(html[0] + open + math[0] + html[1]);
5454
expect(katex.renderToString).not.toHaveBeenCalled();
@@ -59,7 +59,7 @@ describe('TrustedHtml LaTeX locator', () => {
5959
nestedDollars.forEach((dollarMath) => {
6060
delimiters.forEach(([open, displayMode, close]) => {
6161
const testcase = html[0] + wrapIn(open, dollarMath, close) + html[1];
62-
const result = katexify(testcase, null, undefined, false, {});
62+
const result = katexify(testcase, undefined, false, false, {});
6363

6464
expect(result).toEqual(html[0] + LATEX + html[1]);
6565
// @ts-ignore
@@ -77,7 +77,7 @@ describe('TrustedHtml LaTeX locator', () => {
7777
it('can render environments', () => {
7878
const env = "\\begin{aligned}" + math[0] + "\\end{aligned}";
7979
const testcase = html[0] + env + html[1];
80-
const result = katexify(testcase, null, undefined, false, {});
80+
const result = katexify(testcase, undefined, false, false, {});
8181

8282
expect(result).toEqual(html[0] + LATEX + html[1]);
8383
// @ts-ignore
@@ -93,7 +93,7 @@ describe('TrustedHtml LaTeX locator', () => {
9393
it('missing refs show an inline error', () => {
9494
const ref = "\\ref{foo[234o89tdgfiuno34£\"$%^Y}";
9595
const testcase = html[0] + ref + html[1];
96-
const result = katexify(testcase, null, undefined, false, {});
96+
const result = katexify(testcase, undefined, false, false, {});
9797

9898
expect(result).toEqual(html[0] + "unknown reference " + ref + html[1]);
9999
expect(katex.renderToString).not.toHaveBeenCalled();
@@ -102,7 +102,7 @@ describe('TrustedHtml LaTeX locator', () => {
102102
it('found refs show their figure number', () => {
103103
const ref = "\\ref{foo}";
104104
const testcase = html[0] + ref + html[1];
105-
const result = katexify(testcase, null, undefined, false, {foo: 42});
105+
const result = katexify(testcase, undefined, false, false, {foo: 42});
106106

107107
const expectedFigureRef = "Figure" + "&nbsp;" + "42";
108108
const expectedFigureRefWithFormatting = `<strong class="text-secondary figure-reference">${expectedFigureRef}</strong>`;
@@ -114,7 +114,7 @@ describe('TrustedHtml LaTeX locator', () => {
114114
const escapedDollar = "\\$";
115115
const unescapedDollar = "$";
116116
const testcase = html[0] + escapedDollar + html[1];
117-
const result = katexify(testcase, null, undefined, false, {});
117+
const result = katexify(testcase, undefined, false, false, {});
118118

119119
expect(result).toEqual(html[0] + unescapedDollar + html[1]);
120120
expect(katex.renderToString).not.toHaveBeenCalled();

0 commit comments

Comments
 (0)