explorer.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2018-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Mixin that implements the Explorer
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {Handler} from '../core/Handler.js';
  23. import {MmlNode} from '../core/MmlTree/MmlNode.js';
  24. import {MathML} from '../input/mathml.js';
  25. import {STATE, newState} from '../core/MathItem.js';
  26. import {EnrichedMathItem, EnrichedMathDocument, EnrichHandler} from './semantic-enrich.js';
  27. import {MathDocumentConstructor} from '../core/MathDocument.js';
  28. import {OptionList, expandable} from '../util/Options.js';
  29. import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
  30. import {MJContextMenu} from '../ui/menu/MJContextMenu.js';
  31. import {Explorer} from './explorer/Explorer.js';
  32. import * as ke from './explorer/KeyExplorer.js';
  33. import * as me from './explorer/MouseExplorer.js';
  34. import {TreeColorer, FlameColorer} from './explorer/TreeExplorer.js';
  35. import {LiveRegion, ToolTip, HoverRegion} from './explorer/Region.js';
  36. import {Submenu} from 'mj-context-menu/js/item_submenu.js';
  37. import Sre from './sre.js';
  38. /**
  39. * Generic constructor for Mixins
  40. */
  41. export type Constructor<T> = new(...args: any[]) => T;
  42. /**
  43. * Shorthands for types with HTMLElement, Text, and Document instead of generics
  44. */
  45. export type HANDLER = Handler<HTMLElement, Text, Document>;
  46. export type HTMLDOCUMENT = EnrichedMathDocument<HTMLElement, Text, Document>;
  47. export type HTMLMATHITEM = EnrichedMathItem<HTMLElement, Text, Document>;
  48. export type MATHML = MathML<HTMLElement, Text, Document>;
  49. /*==========================================================================*/
  50. /**
  51. * Add STATE value for having the Explorer added (after TYPESET and before INSERTED or CONTEXT_MENU)
  52. */
  53. newState('EXPLORER', 160);
  54. /**
  55. * The properties added to MathItem for the Explorer
  56. */
  57. export interface ExplorerMathItem extends HTMLMATHITEM {
  58. /**
  59. * @param {HTMLDocument} document The document where the Explorer is being added
  60. * @param {boolean} force True to force the explorer even if enableExplorer is false
  61. */
  62. explorable(document: HTMLDOCUMENT, force?: boolean): void;
  63. /**
  64. * @param {HTMLDocument} document The document where the Explorer is being added
  65. */
  66. attachExplorers(document: HTMLDOCUMENT): void;
  67. }
  68. /**
  69. * The mixin for adding the Explorer to MathItems
  70. *
  71. * @param {B} BaseMathItem The MathItem class to be extended
  72. * @param {Function} toMathML The function to serialize the internal MathML
  73. * @returns {ExplorerMathItem} The Explorer MathItem class
  74. *
  75. * @template B The MathItem class to extend
  76. */
  77. export function ExplorerMathItemMixin<B extends Constructor<HTMLMATHITEM>>(
  78. BaseMathItem: B,
  79. toMathML: (node: MmlNode) => string
  80. ): Constructor<ExplorerMathItem> & B {
  81. return class extends BaseMathItem {
  82. /**
  83. * The Explorer objects for this math item
  84. */
  85. protected explorers: {[key: string]: Explorer} = {};
  86. /**
  87. * The currently attached explorers
  88. */
  89. protected attached: string[] = [];
  90. /**
  91. * True when a rerendered element should restart these explorers
  92. */
  93. protected restart: string[] = [];
  94. /**
  95. * True when a rerendered element should regain the focus
  96. */
  97. protected refocus: boolean = false;
  98. /**
  99. * Save explorer id during rerendering.
  100. */
  101. protected savedId: string = null;
  102. /**
  103. * Add the explorer to the output for this math item
  104. *
  105. * @param {HTMLDocument} document The MathDocument for the MathItem
  106. * @param {boolean} force True to force the explorer even if enableExplorer is false
  107. */
  108. public explorable(document: ExplorerMathDocument, force: boolean = false) {
  109. if (this.state() >= STATE.EXPLORER) return;
  110. if (!this.isEscaped && (document.options.enableExplorer || force)) {
  111. const node = this.typesetRoot;
  112. const mml = toMathML(this.root);
  113. if (this.savedId) {
  114. this.typesetRoot.setAttribute('sre-explorer-id', this.savedId);
  115. this.savedId = null;
  116. }
  117. // Init explorers:
  118. this.explorers = initExplorers(document, node, mml);
  119. this.attachExplorers(document);
  120. }
  121. this.state(STATE.EXPLORER);
  122. }
  123. /**
  124. * Attaches the explorers that are currently meant to be active given
  125. * the document options. Detaches all others.
  126. * @param {ExplorerMathDocument} document The current document.
  127. */
  128. public attachExplorers(document: ExplorerMathDocument) {
  129. this.attached = [];
  130. let keyExplorers = [];
  131. for (let key of Object.keys(this.explorers)) {
  132. let explorer = this.explorers[key];
  133. if (explorer instanceof ke.AbstractKeyExplorer) {
  134. explorer.AddEvents();
  135. explorer.stoppable = false;
  136. keyExplorers.unshift(explorer);
  137. }
  138. if (document.options.a11y[key]) {
  139. explorer.Attach();
  140. this.attached.push(key);
  141. } else {
  142. explorer.Detach();
  143. }
  144. }
  145. // Ensure that the last currently attached key explorer stops propagating
  146. // key events.
  147. for (let explorer of keyExplorers) {
  148. if (explorer.attached) {
  149. explorer.stoppable = true;
  150. break;
  151. }
  152. }
  153. }
  154. /**
  155. * @override
  156. */
  157. public rerender(document: ExplorerMathDocument, start: number = STATE.RERENDER) {
  158. this.savedId = this.typesetRoot.getAttribute('sre-explorer-id');
  159. this.refocus = (window.document.activeElement === this.typesetRoot);
  160. for (let key of this.attached) {
  161. let explorer = this.explorers[key];
  162. if (explorer.active) {
  163. this.restart.push(key);
  164. explorer.Stop();
  165. }
  166. }
  167. super.rerender(document, start);
  168. }
  169. /**
  170. * @override
  171. */
  172. public updateDocument(document: ExplorerMathDocument) {
  173. super.updateDocument(document);
  174. this.refocus && this.typesetRoot.focus();
  175. this.restart.forEach(x => this.explorers[x].Start());
  176. this.restart = [];
  177. this.refocus = false;
  178. }
  179. };
  180. }
  181. /**
  182. * The functions added to MathDocument for the Explorer
  183. */
  184. export interface ExplorerMathDocument extends HTMLDOCUMENT {
  185. /**
  186. * The objects needed for the explorer
  187. */
  188. explorerRegions: ExplorerRegions;
  189. /**
  190. * Add the Explorer to the MathItems in the MathDocument
  191. *
  192. * @returns {MathDocument} The MathDocument (so calls can be chained)
  193. */
  194. explorable(): HTMLDOCUMENT;
  195. }
  196. /**
  197. * The mixin for adding the Explorer to MathDocuments
  198. *
  199. * @param {B} BaseDocument The MathDocument class to be extended
  200. * @returns {ExplorerMathDocument} The extended MathDocument class
  201. */
  202. export function ExplorerMathDocumentMixin<B extends MathDocumentConstructor<HTMLDOCUMENT>>(
  203. BaseDocument: B
  204. ): MathDocumentConstructor<ExplorerMathDocument> & B {
  205. return class extends BaseDocument {
  206. /**
  207. * @override
  208. */
  209. public static OPTIONS: OptionList = {
  210. ...BaseDocument.OPTIONS,
  211. enableExplorer: true,
  212. renderActions: expandable({
  213. ...BaseDocument.OPTIONS.renderActions,
  214. explorable: [STATE.EXPLORER]
  215. }),
  216. sre: expandable({
  217. ...BaseDocument.OPTIONS.sre,
  218. speech: 'shallow', // overrides option in EnrichedMathDocument
  219. }),
  220. a11y: {
  221. align: 'top', // placement of magnified expression
  222. backgroundColor: 'Blue', // color for background of selected sub-expression
  223. backgroundOpacity: 20, // opacity for background of selected sub-expression
  224. braille: false, // switch on Braille output
  225. flame: false, // color collapsible sub-expressions
  226. foregroundColor: 'Black', // color to use for text of selected sub-expression
  227. foregroundOpacity: 100, // opacity for text of selected sub-expression
  228. highlight: 'None', // type of highlighting for collapsible sub-expressions
  229. hover: false, // show collapsible sub-expression on mouse hovering
  230. infoPrefix: false, // show speech prefixes on mouse hovering
  231. infoRole: false, // show semantic role on mouse hovering
  232. infoType: false, // show semantic type on mouse hovering
  233. keyMagnifier: false, // switch on magnification via key exploration
  234. magnification: 'None', // type of magnification
  235. magnify: '400%', // percentage of magnification of zoomed expressions
  236. mouseMagnifier: false, // switch on magnification via mouse hovering
  237. speech: true, // switch on speech output
  238. subtitles: true, // show speech as a subtitle
  239. treeColoring: false, // tree color expression
  240. viewBraille: false // display Braille output as subtitles
  241. }
  242. };
  243. /**
  244. * The objects needed for the explorer
  245. */
  246. public explorerRegions: ExplorerRegions;
  247. /**
  248. * Extend the MathItem class used for this MathDocument
  249. * and create the visitor and explorer objects needed for the explorer
  250. *
  251. * @override
  252. * @constructor
  253. */
  254. constructor(...args: any[]) {
  255. super(...args);
  256. const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
  257. if (!ProcessBits.has('explorer')) {
  258. ProcessBits.allocate('explorer');
  259. }
  260. const visitor = new SerializedMmlVisitor(this.mmlFactory);
  261. const toMathML = ((node: MmlNode) => visitor.visitTree(node));
  262. this.options.MathItem = ExplorerMathItemMixin(this.options.MathItem, toMathML);
  263. // TODO: set backward compatibility options here.
  264. this.explorerRegions = initExplorerRegions(this);
  265. }
  266. /**
  267. * Add the Explorer to the MathItems in this MathDocument
  268. *
  269. * @return {ExplorerMathDocument} The MathDocument (so calls can be chained)
  270. */
  271. public explorable(): ExplorerMathDocument {
  272. if (!this.processed.isSet('explorer')) {
  273. if (this.options.enableExplorer) {
  274. for (const math of this.math) {
  275. (math as ExplorerMathItem).explorable(this);
  276. }
  277. }
  278. this.processed.set('explorer');
  279. }
  280. return this;
  281. }
  282. /**
  283. * @override
  284. */
  285. public state(state: number, restore: boolean = false) {
  286. super.state(state, restore);
  287. if (state < STATE.EXPLORER) {
  288. this.processed.clear('explorer');
  289. }
  290. return this;
  291. }
  292. };
  293. }
  294. /*==========================================================================*/
  295. /**
  296. * Add Explorer functions to a Handler instance
  297. *
  298. * @param {Handler} handler The Handler instance to enhance
  299. * @param {MathML} MmlJax A MathML input jax to be used for the semantic enrichment
  300. * @returns {Handler} The handler that was modified (for purposes of chainging extensions)
  301. */
  302. export function ExplorerHandler(handler: HANDLER, MmlJax: MATHML = null): HANDLER {
  303. if (!handler.documentClass.prototype.enrich && MmlJax) {
  304. handler = EnrichHandler(handler, MmlJax);
  305. }
  306. handler.documentClass = ExplorerMathDocumentMixin(handler.documentClass as any);
  307. return handler;
  308. }
  309. /*==========================================================================*/
  310. /**
  311. * The regions objects needed for the explorers.
  312. */
  313. export type ExplorerRegions = {
  314. speechRegion?: LiveRegion,
  315. brailleRegion?: LiveRegion,
  316. magnifier?: HoverRegion,
  317. tooltip1?: ToolTip,
  318. tooltip2?: ToolTip,
  319. tooltip3?: ToolTip
  320. };
  321. /**
  322. * Initializes the regions needed for a document.
  323. * @param {ExplorerMathDocument} document The current document.
  324. */
  325. function initExplorerRegions(document: ExplorerMathDocument) {
  326. return {
  327. speechRegion: new LiveRegion(document),
  328. brailleRegion: new LiveRegion(document),
  329. magnifier: new HoverRegion(document),
  330. tooltip1: new ToolTip(document),
  331. tooltip2: new ToolTip(document),
  332. tooltip3: new ToolTip(document)
  333. };
  334. }
  335. /**
  336. * Type of explorer initialization methods.
  337. * @type {(ExplorerMathDocument, HTMLElement, any[]): Explorer}
  338. */
  339. type ExplorerInit = (doc: ExplorerMathDocument,
  340. node: HTMLElement, ...rest: any[]) => Explorer;
  341. /**
  342. * Generation methods for all MathJax explorers available via option settings.
  343. */
  344. let allExplorers: {[options: string]: ExplorerInit} = {
  345. speech: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => {
  346. let explorer = ke.SpeechExplorer.create(
  347. doc, doc.explorerRegions.speechRegion, node, ...rest) as ke.SpeechExplorer;
  348. explorer.speechGenerator.setOptions({
  349. locale: doc.options.sre.locale, domain: doc.options.sre.domain,
  350. style: doc.options.sre.style, modality: 'speech'});
  351. // This weeds out the case of providing a non-existent locale option.
  352. let locale = explorer.speechGenerator.getOptions().locale;
  353. if (locale !== Sre.engineSetup().locale) {
  354. doc.options.sre.locale = Sre.engineSetup().locale;
  355. explorer.speechGenerator.setOptions({locale: doc.options.sre.locale});
  356. }
  357. explorer.showRegion = 'subtitles';
  358. return explorer;
  359. },
  360. braille: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => {
  361. let explorer = ke.SpeechExplorer.create(
  362. doc, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer;
  363. explorer.speechGenerator.setOptions({locale: 'nemeth', domain: 'default',
  364. style: 'default', modality: 'braille'});
  365. explorer.showRegion = 'viewBraille';
  366. return explorer;
  367. },
  368. keyMagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) =>
  369. ke.Magnifier.create(doc, doc.explorerRegions.magnifier, node, ...rest),
  370. mouseMagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
  371. me.ContentHoverer.create(doc, doc.explorerRegions.magnifier, node,
  372. (x: HTMLElement) => x.hasAttribute('data-semantic-type'),
  373. (x: HTMLElement) => x),
  374. hover: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
  375. me.FlameHoverer.create(doc, null, node),
  376. infoType: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
  377. me.ValueHoverer.create(doc, doc.explorerRegions.tooltip1, node,
  378. (x: HTMLElement) => x.hasAttribute('data-semantic-type'),
  379. (x: HTMLElement) => x.getAttribute('data-semantic-type')),
  380. infoRole: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
  381. me.ValueHoverer.create(doc, doc.explorerRegions.tooltip2, node,
  382. (x: HTMLElement) => x.hasAttribute('data-semantic-role'),
  383. (x: HTMLElement) => x.getAttribute('data-semantic-role')),
  384. infoPrefix: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
  385. me.ValueHoverer.create(doc, doc.explorerRegions.tooltip3, node,
  386. (x: HTMLElement) => x.hasAttribute('data-semantic-prefix'),
  387. (x: HTMLElement) => x.getAttribute('data-semantic-prefix')),
  388. flame: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
  389. FlameColorer.create(doc, null, node),
  390. treeColoring: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) =>
  391. TreeColorer.create(doc, null, node, ...rest)
  392. };
  393. /**
  394. * Initialises explorers for a document.
  395. * @param {ExplorerMathDocument} document The target document.
  396. * @param {HTMLElement} node The node explorers will be attached to.
  397. * @param {string} mml The corresponding Mathml node as a string.
  398. * @return {Explorer[]} A list of initialised explorers.
  399. */
  400. function initExplorers(document: ExplorerMathDocument, node: HTMLElement, mml: string): {[key: string]: Explorer} {
  401. let explorers: {[key: string]: Explorer} = {};
  402. for (let key of Object.keys(allExplorers)) {
  403. explorers[key] = allExplorers[key](document, node, mml);
  404. }
  405. return explorers;
  406. }
  407. /* Context Menu Interactions */
  408. /**
  409. * Sets a list of a11y options for a given document.
  410. * @param {HTMLDOCUMENT} document The current document.
  411. * @param {{[key: string]: any}} options Association list for a11y option value pairs.
  412. */
  413. export function setA11yOptions(document: HTMLDOCUMENT, options: {[key: string]: any}) {
  414. let sreOptions = Sre.engineSetup() as {[name: string]: string};
  415. for (let key in options) {
  416. if (document.options.a11y[key] !== undefined) {
  417. setA11yOption(document, key, options[key]);
  418. if (key === 'locale') {
  419. document.options.sre[key] = options[key];
  420. }
  421. continue;
  422. }
  423. if (sreOptions[key] !== undefined) {
  424. document.options.sre[key] = options[key];
  425. }
  426. }
  427. // Reinit explorers
  428. for (let item of document.math) {
  429. (item as ExplorerMathItem).attachExplorers(document as ExplorerMathDocument);
  430. }
  431. }
  432. /**
  433. * Sets a single a11y option for a menu name.
  434. * @param {HTMLDOCUMENT} document The current document.
  435. * @param {string} option The option name in the menu.
  436. * @param {string|boolean} value The new value.
  437. */
  438. export function setA11yOption(document: HTMLDOCUMENT, option: string, value: string | boolean) {
  439. switch (option) {
  440. case 'magnification':
  441. switch (value) {
  442. case 'None':
  443. document.options.a11y.magnification = value;
  444. document.options.a11y.keyMagnifier = false;
  445. document.options.a11y.mouseMagnifier = false;
  446. break;
  447. case 'Keyboard':
  448. document.options.a11y.magnification = value;
  449. document.options.a11y.keyMagnifier = true;
  450. document.options.a11y.mouseMagnifier = false;
  451. break;
  452. case 'Mouse':
  453. document.options.a11y.magnification = value;
  454. document.options.a11y.keyMagnifier = false;
  455. document.options.a11y.mouseMagnifier = true;
  456. break;
  457. }
  458. break;
  459. case 'highlight':
  460. switch (value) {
  461. case 'None':
  462. document.options.a11y.highlight = value;
  463. document.options.a11y.hover = false;
  464. document.options.a11y.flame = false;
  465. break;
  466. case 'Hover':
  467. document.options.a11y.highlight = value;
  468. document.options.a11y.hover = true;
  469. document.options.a11y.flame = false;
  470. break;
  471. case 'Flame':
  472. document.options.a11y.highlight = value;
  473. document.options.a11y.hover = false;
  474. document.options.a11y.flame = true;
  475. break;
  476. }
  477. break;
  478. default:
  479. document.options.a11y[option] = value;
  480. }
  481. }
  482. /**
  483. * Values for the ClearSpeak preference variables.
  484. */
  485. let csPrefsSetting: {[pref: string]: string} = {};
  486. /**
  487. * Generator of all variables for the Clearspeak Preference settings.
  488. * @param {MJContextMenu} menu The current context menu.
  489. * @param {string[]} prefs The preferences.
  490. */
  491. let csPrefsVariables = function(menu: MJContextMenu, prefs: string[]) {
  492. let srVariable = menu.pool.lookup('speechRules');
  493. for (let pref of prefs) {
  494. if (csPrefsSetting[pref]) continue;
  495. menu.factory.get('variable')(menu.factory, {
  496. name: 'csprf_' + pref,
  497. setter: (value: string) => {
  498. csPrefsSetting[pref] = value;
  499. srVariable.setValue(
  500. 'clearspeak-' +
  501. Sre.clearspeakPreferences.addPreference(
  502. Sre.clearspeakStyle(), pref, value)
  503. );
  504. },
  505. getter: () => { return csPrefsSetting[pref] || 'Auto'; }
  506. }, menu.pool);
  507. }
  508. };
  509. /**
  510. * Generate the selection box for the Clearspeak Preferences.
  511. * @param {MJContextMenu} menu The current context menu.
  512. * @param {string} locale The current locale.
  513. */
  514. let csSelectionBox = function(menu: MJContextMenu, locale: string) {
  515. let prefs = Sre.clearspeakPreferences.getLocalePreferences();
  516. let props = prefs[locale];
  517. if (!props) {
  518. let csEntry = menu.findID('Accessibility', 'Speech', 'Clearspeak');
  519. if (csEntry) {
  520. csEntry.disable();
  521. }
  522. return null;
  523. }
  524. csPrefsVariables(menu, Object.keys(props));
  525. let items = [];
  526. for (const prop of Object.getOwnPropertyNames(props)) {
  527. items.push({
  528. 'title': prop,
  529. 'values': props[prop].map(x => x.replace(RegExp('^' + prop + '_'), '')),
  530. 'variable': 'csprf_' + prop
  531. });
  532. }
  533. let sb = menu.factory.get('selectionBox')(menu.factory, {
  534. 'title': 'Clearspeak Preferences',
  535. 'signature': '',
  536. 'order': 'alphabetic',
  537. 'grid': 'square',
  538. 'selections': items
  539. }, menu);
  540. return {'type': 'command',
  541. 'id': 'ClearspeakPreferences',
  542. 'content': 'Select Preferences',
  543. 'action': () => sb.post(0, 0)};
  544. };
  545. /**
  546. * Creates dynamic clearspeak menu.
  547. * @param {MJContextMenu} menu The context menu.
  548. * @param {Submenu} sub The submenu to attach.
  549. */
  550. let csMenu = function(menu: MJContextMenu, sub: Submenu) {
  551. let locale = menu.pool.lookup('locale').getValue() as string;
  552. const box = csSelectionBox(menu, locale);
  553. let items: Object[] = [];
  554. try {
  555. items = Sre.clearspeakPreferences.smartPreferences(
  556. menu.mathItem, locale);
  557. } catch (e) {
  558. console.log(e);
  559. }
  560. if (box) {
  561. items.splice(2, 0, box);
  562. }
  563. return menu.factory.get('subMenu')(menu.factory, {
  564. items: items,
  565. id: 'Clearspeak'
  566. }, sub);
  567. };
  568. MJContextMenu.DynamicSubmenus.set('Clearspeak', csMenu);
  569. /**
  570. * Creates dynamic locale menu.
  571. * @param {MJContextMenu} menu The context menu.
  572. * @param {Submenu} sub The submenu to attach.
  573. */
  574. let language = function(menu: MJContextMenu, sub: Submenu) {
  575. let radios: {type: string, id: string,
  576. content: string, variable: string}[] = [];
  577. for (let lang of Sre.locales.keys()) {
  578. if (lang === 'nemeth') continue;
  579. radios.push({type: 'radio', id: lang,
  580. content: Sre.locales.get(lang) || lang, variable: 'locale'});
  581. }
  582. radios.sort((x, y) => x.content.localeCompare(y.content, 'en'));
  583. return menu.factory.get('subMenu')(menu.factory, {
  584. items: radios, id: 'Language'}, sub);
  585. };
  586. MJContextMenu.DynamicSubmenus.set('A11yLanguage', language);