diff --git a/src/IsaacAppTypes.tsx b/src/IsaacAppTypes.tsx index 64a40c3ae1..60a8711457 100644 --- a/src/IsaacAppTypes.tsx +++ b/src/IsaacAppTypes.tsx @@ -243,6 +243,7 @@ export interface BooleanNotation { export interface DisplaySettings { HIDE_QUESTION_ATTEMPTS?: boolean; + PREFER_MATHML?: boolean; } export interface UserConsent { diff --git a/src/app/components/elements/markup/latexRendering.ts b/src/app/components/elements/markup/latexRendering.ts index cd5ecf87b6..eb3c41672b 100644 --- a/src/app/components/elements/markup/latexRendering.ts +++ b/src/app/components/elements/markup/latexRendering.ts @@ -1,11 +1,10 @@ import {useContext} from "react"; -import {selectors, useAppSelector, useGetSegueEnvironmentQuery} from "../../../state"; -import {FigureNumberingContext, FigureNumbersById, PotentialUser} from "../../../../IsaacAppTypes"; +import {useGetSegueEnvironmentQuery} from "../../../state"; +import {FigureNumberingContext, FigureNumbersById} from "../../../../IsaacAppTypes"; import he from "he"; -import {dropZoneRegex, renderA11yString, BOOLEAN_NOTATION, isAda, useUserPreferences} from "../../../services"; +import {BOOLEAN_NOTATION, dropZoneRegex, isAda, renderA11yString, useUserPreferences} from "../../../services"; import katex, {KatexOptions} from "katex"; import 'katex/dist/contrib/mhchem.mjs'; -import {Immutable} from "immer"; type MathJaxMacro = string|[string, number]; @@ -220,7 +219,13 @@ const ENDREF = "==ENDREF=="; const REF_REGEXP = new RegExp(REF + "(.*?)" + ENDREF, "g"); const SR_REF_REGEXP = new RegExp("start text, " + REF_REGEXP.source + ", end text,", "g"); -export function katexify(html: string, user: Immutable | null, booleanNotation : BOOLEAN_NOTATION | undefined, showScreenReaderHoverText: boolean, figureNumbers: FigureNumbersById) { +export function katexify( + html: string, + booleanNotation : BOOLEAN_NOTATION | undefined, + showScreenReaderHoverText: boolean, + preferMathML: boolean, + figureNumbers: FigureNumbersById +) { start.lastIndex = 0; let match: RegExpExecArray | null; let output = ""; @@ -287,8 +292,8 @@ export function katexify(html: string, user: Immutable | null, bo // Until https://github.com/KaTeX/KaTeX/issues/3668 is resolved, apply suggested fix ourselves, awfully: katexRenderResult = katexRenderResult.replaceAll("color:transparent;", "color:transparent;visibility:hidden;"); - // If katex-a11y fails, generate MathML using KaTeX for accessibility - if (screenReaderText) { + // If katex-a11y fails, or MathML preferred, generate MathML using KaTeX for accessibility: + if (!preferMathML && screenReaderText) { katexRenderResult = katexRenderResult.replace('', ``); } else { @@ -333,10 +338,9 @@ export function katexify(html: string, user: Immutable | null, bo // A hook wrapper around katexify that gets its required parameters from the current redux state and existing figure numbering context export const useRenderKatex = () => { - const user = useAppSelector(selectors.user.orNull); const {data: segueEnvironment} = useGetSegueEnvironmentQuery(); - const {preferredBooleanNotation} = useUserPreferences(); + const {preferredBooleanNotation, preferMathML} = useUserPreferences(); const figureNumbers = useContext(FigureNumberingContext); - return (markup: string) => katexify(markup, user, preferredBooleanNotation && BOOLEAN_NOTATION[preferredBooleanNotation], segueEnvironment === "DEV", figureNumbers); + return (markup: string) => katexify(markup, preferredBooleanNotation && BOOLEAN_NOTATION[preferredBooleanNotation], segueEnvironment === "DEV", preferMathML ?? false, figureNumbers); }; diff --git a/src/app/components/elements/panels/UserBetaFeatures.tsx b/src/app/components/elements/panels/UserBetaFeatures.tsx index 75f2ca2ae3..2b28b8919e 100644 --- a/src/app/components/elements/panels/UserBetaFeatures.tsx +++ b/src/app/components/elements/panels/UserBetaFeatures.tsx @@ -18,7 +18,7 @@ export const RevisionModeInput = ({displaySettings, setDisplaySettings}: Revisio return { setDisplaySettings((oldDs) => ({...oldDs, HIDE_QUESTION_ATTEMPTS: e.target.checked})); - }} + }} label={

Hide previous question attempts

} id={"hide-previous-q-attempts"} />; @@ -35,6 +35,16 @@ export const UserBetaFeatures = ({ displaySettings, setDisplaySettings, consentS

{`This feature lets you answer questions ${siteSpecific("that you have answered before, without seeing your old answer.", "again, even if you've answered them before.")} It's useful if you are reviewing a topic before a test or exam.`}

+ <> + { + setDisplaySettings((oldDs) => ({...oldDs, PREFER_MATHML: e.target.checked})); + }} + label={

Use MathML for accessible maths

} + id={"prefer-mathml"} + />
+

{`With this setting you can toggle between using alternative text or MathML for mathematical equations.`}

+ {isAda && <> { diff --git a/src/app/services/userPreferences.ts b/src/app/services/userPreferences.ts index 1647035484..419006926c 100644 --- a/src/app/services/userPreferences.ts +++ b/src/app/services/userPreferences.ts @@ -4,12 +4,13 @@ import { AppState, useAppSelector } from '../state'; interface UseUserPreferencesReturnType { preferredProgrammingLanguage?: PROGRAMMING_LANGUAGE; preferredBooleanNotation?: BOOLEAN_NOTATION; + preferMathML?: boolean; } export function useUserPreferences(): UseUserPreferencesReturnType { const {examBoard} = useUserViewingContext(); - const {PROGRAMMING_LANGUAGE: programmingLanguage, BOOLEAN_NOTATION: booleanNotation} = + const {PROGRAMMING_LANGUAGE: programmingLanguage, BOOLEAN_NOTATION: booleanNotation, DISPLAY_SETTING: displaySettings} = useAppSelector((state: AppState) => state?.userPreferences) || {}; // Programming language preference - @@ -28,8 +29,12 @@ export function useUserPreferences(): UseUserPreferencesReturnType { preferredBooleanNotation = examBoardBooleanNotationMap[examBoard]; } + // Accessibility preferences: + const preferMathML = displaySettings?.PREFER_MATHML; + return { preferredProgrammingLanguage, - preferredBooleanNotation + preferredBooleanNotation, + preferMathML }; -} \ No newline at end of file +} diff --git a/src/test/components/TrustedHtml.test.tsx b/src/test/components/TrustedHtml.test.tsx index ec9bac3768..ee74ae7e4d 100644 --- a/src/test/components/TrustedHtml.test.tsx +++ b/src/test/components/TrustedHtml.test.tsx @@ -31,7 +31,7 @@ describe('TrustedHtml LaTeX locator', () => { it('can find basic delimiters', () => { delimiters.forEach(([open, displayMode, close]) => { const testcase = html[0] + wrapIn(open, math[0], close) + html[1]; - const result = katexify(testcase, null, undefined, false, {}); + const result = katexify(testcase, undefined, false, false, {}); expect(result).toEqual(html[0] + LATEX + html[1]); // @ts-ignore @@ -48,7 +48,7 @@ describe('TrustedHtml LaTeX locator', () => { it("unbalanced delimiters don't break everything but instead are just skipped", () => { delimiters.forEach(([open, , ]) => { const testcase = html[0] + wrapIn(open, math[0], "") + html[1]; - const result = katexify(testcase, null, undefined, false, {}); + const result = katexify(testcase, undefined, false, false, {}); expect(result).toEqual(html[0] + open + math[0] + html[1]); expect(katex.renderToString).not.toHaveBeenCalled(); @@ -59,7 +59,7 @@ describe('TrustedHtml LaTeX locator', () => { nestedDollars.forEach((dollarMath) => { delimiters.forEach(([open, displayMode, close]) => { const testcase = html[0] + wrapIn(open, dollarMath, close) + html[1]; - const result = katexify(testcase, null, undefined, false, {}); + const result = katexify(testcase, undefined, false, false, {}); expect(result).toEqual(html[0] + LATEX + html[1]); // @ts-ignore @@ -77,7 +77,7 @@ describe('TrustedHtml LaTeX locator', () => { it('can render environments', () => { const env = "\\begin{aligned}" + math[0] + "\\end{aligned}"; const testcase = html[0] + env + html[1]; - const result = katexify(testcase, null, undefined, false, {}); + const result = katexify(testcase, undefined, false, false, {}); expect(result).toEqual(html[0] + LATEX + html[1]); // @ts-ignore @@ -93,7 +93,7 @@ describe('TrustedHtml LaTeX locator', () => { it('missing refs show an inline error', () => { const ref = "\\ref{foo[234o89tdgfiuno34£\"$%^Y}"; const testcase = html[0] + ref + html[1]; - const result = katexify(testcase, null, undefined, false, {}); + const result = katexify(testcase, undefined, false, false, {}); expect(result).toEqual(html[0] + "unknown reference " + ref + html[1]); expect(katex.renderToString).not.toHaveBeenCalled(); @@ -102,7 +102,7 @@ describe('TrustedHtml LaTeX locator', () => { it('found refs show their figure number', () => { const ref = "\\ref{foo}"; const testcase = html[0] + ref + html[1]; - const result = katexify(testcase, null, undefined, false, {foo: 42}); + const result = katexify(testcase, undefined, false, false, {foo: 42}); const expectedFigureRef = "Figure" + " " + "42"; const expectedFigureRefWithFormatting = `${expectedFigureRef}`; @@ -114,7 +114,7 @@ describe('TrustedHtml LaTeX locator', () => { const escapedDollar = "\\$"; const unescapedDollar = "$"; const testcase = html[0] + escapedDollar + html[1]; - const result = katexify(testcase, null, undefined, false, {}); + const result = katexify(testcase, undefined, false, false, {}); expect(result).toEqual(html[0] + unescapedDollar + html[1]); expect(katex.renderToString).not.toHaveBeenCalled(); diff --git a/src/test/pages/__image_snapshots__/ada/My Account should have no visual regressions on Beta page #0.png b/src/test/pages/__image_snapshots__/ada/My Account should have no visual regressions on Beta page #0.png index 69006ffb8c..9f489b7b97 100644 Binary files a/src/test/pages/__image_snapshots__/ada/My Account should have no visual regressions on Beta page #0.png and b/src/test/pages/__image_snapshots__/ada/My Account should have no visual regressions on Beta page #0.png differ diff --git a/src/test/pages/__image_snapshots__/phy/My Account should have no visual regressions on Beta page #0.png b/src/test/pages/__image_snapshots__/phy/My Account should have no visual regressions on Beta page #0.png index 08ef3217b5..b43f67a545 100644 Binary files a/src/test/pages/__image_snapshots__/phy/My Account should have no visual regressions on Beta page #0.png and b/src/test/pages/__image_snapshots__/phy/My Account should have no visual regressions on Beta page #0.png differ