MenuHandler.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2019-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 adds a context-menu to MathJax output
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {mathjax} from '../../mathjax.js';
  23. import {STATE, newState} from '../../core/MathItem.js';
  24. import {MathDocumentConstructor} from '../../core/MathDocument.js';
  25. import {Handler} from '../../core/Handler.js';
  26. import {ComplexityMathDocument, ComplexityMathItem} from '../../a11y/complexity.js';
  27. import {ExplorerMathDocument, ExplorerMathItem} from '../../a11y/explorer.js';
  28. import {AssistiveMmlMathDocument, AssistiveMmlMathItem} from '../../a11y/assistive-mml.js';
  29. import {expandable} from '../../util/Options.js';
  30. import {Menu} from './Menu.js';
  31. /*==========================================================================*/
  32. /**
  33. * Generic constructor for Mixins
  34. */
  35. export type Constructor<T> = new(...args: any[]) => T;
  36. /**
  37. * Constructor for base MathItem for MenuMathItem
  38. */
  39. export type A11yMathItemConstructor = {
  40. new(...args: any[]): ComplexityMathItem<HTMLElement, Text, Document> &
  41. ExplorerMathItem & AssistiveMmlMathItem<HTMLElement, Text, Document>;
  42. };
  43. /**
  44. * Constructor for base document for MenuMathDocument
  45. */
  46. export type A11yDocumentConstructor =
  47. MathDocumentConstructor<ComplexityMathDocument<HTMLElement, Text, Document> &
  48. ExplorerMathDocument & AssistiveMmlMathDocument<HTMLElement, Text, Document>>;
  49. /*==========================================================================*/
  50. /**
  51. * Add STATE value for menus being added (after TYPESET and before INSERTED)
  52. */
  53. newState('CONTEXT_MENU', 170);
  54. /**
  55. * The new function for MathItem that adds the context menu
  56. */
  57. export interface MenuMathItem extends ComplexityMathItem<HTMLElement, Text, Document> {
  58. /**
  59. * @param {MenuMathDocument} document The document where the menu is being added
  60. * @param {boolean} force True if menu should be added even if enableMenu is false
  61. */
  62. addMenu(document: MenuMathDocument, force?: boolean): void;
  63. /**
  64. * @param {MenuMathDocument} document The document to check for if anything is being loaded
  65. */
  66. checkLoading(document: MenuMathDocument): void;
  67. }
  68. /**
  69. * The mixin for adding context menus to MathItems
  70. *
  71. * @param {B} BaseMathItem The MathItem class to be extended
  72. * @return {MathMathItem} The extended MathItem class
  73. *
  74. * @template B The MathItem class to extend
  75. */
  76. export function MenuMathItemMixin<B extends A11yMathItemConstructor>(
  77. BaseMathItem: B
  78. ): Constructor<MenuMathItem> & B {
  79. return class extends BaseMathItem {
  80. /**
  81. * @param {MenuMathDocument} document The document where the menu is being added
  82. * @param {boolean} force True if menu should be added even if enableMenu is false
  83. */
  84. public addMenu(document: MenuMathDocument, force: boolean = false) {
  85. if (this.state() >= STATE.CONTEXT_MENU) return;
  86. if (!this.isEscaped && (document.options.enableMenu || force)) {
  87. document.menu.addMenu(this);
  88. }
  89. this.state(STATE.CONTEXT_MENU);
  90. }
  91. /**
  92. * @param {MenuMathDocument} document The document to check for if anything is being loaded
  93. */
  94. public checkLoading(document: MenuMathDocument) {
  95. document.checkLoading();
  96. }
  97. };
  98. }
  99. /*==========================================================================*/
  100. /**
  101. * The properties needed in the MathDocument for context menus
  102. */
  103. export interface MenuMathDocument extends ComplexityMathDocument<HTMLElement, Text, Document> {
  104. /**
  105. * The menu associated with this document
  106. */
  107. menu: Menu;
  108. /**
  109. * Add context menus to the MathItems in the MathDocument
  110. *
  111. * @return {MenuMathDocument} The MathDocument (so calls can be chained)
  112. */
  113. addMenu(): MenuMathDocument;
  114. /**
  115. * Checks if there are files being loaded by the menu, and restarts the typesetting if so
  116. *
  117. * @return {MenuMathDocument} The MathDocument (so calls can be chained)
  118. */
  119. checkLoading(): MenuMathDocument;
  120. }
  121. /**
  122. * The mixin for adding context menus to MathDocuments
  123. *
  124. * @param {B} BaseDocument The MathDocument class to be extended
  125. * @return {MenuMathDocument} The extended MathDocument class
  126. *
  127. * @template B The MathDocument class to extend
  128. */
  129. export function MenuMathDocumentMixin<B extends A11yDocumentConstructor>(
  130. BaseDocument: B
  131. ): Constructor<MenuMathDocument> & B {
  132. return class extends BaseDocument {
  133. /**
  134. * @override
  135. */
  136. public static OPTIONS = {
  137. //
  138. // These options are from the a11y extensions, which may not be loaded
  139. // initially, and so would cause "undefined option" error messages
  140. // if a user tries to configure them. So we include them here.
  141. // They are overridden by the options from the extensions when
  142. // those are loaded (via ...BaseDocument.OPTIONS).
  143. //
  144. enableEnrichment: true,
  145. enableComplexity: true,
  146. enableExplorer: true,
  147. enrichSpeech: 'none',
  148. enrichError: (_doc: MenuMathDocument, _math: MenuMathItem, err: Error) =>
  149. console.warn('Enrichment Error:', err),
  150. ...BaseDocument.OPTIONS,
  151. MenuClass: Menu,
  152. menuOptions: Menu.OPTIONS,
  153. enableMenu: true,
  154. sre: (BaseDocument.OPTIONS.sre || expandable({})),
  155. a11y: (BaseDocument.OPTIONS.a11y || expandable({})),
  156. renderActions: expandable({
  157. ...BaseDocument.OPTIONS.renderActions,
  158. addMenu: [STATE.CONTEXT_MENU],
  159. checkLoading: [STATE.UNPROCESSED + 1]
  160. })
  161. };
  162. /**
  163. * The menu associated with this document
  164. */
  165. public menu: Menu;
  166. /**
  167. * Extend the MathItem class used for this MathDocument
  168. *
  169. * @override
  170. * @constructor
  171. */
  172. constructor(...args: any[]) {
  173. super(...args);
  174. this.menu = new this.options.MenuClass(this, this.options.menuOptions);
  175. const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
  176. if (!ProcessBits.has('context-menu')) {
  177. ProcessBits.allocate('context-menu');
  178. }
  179. this.options.MathItem = MenuMathItemMixin<A11yMathItemConstructor>(this.options.MathItem);
  180. }
  181. /**
  182. * Add context menus to the MathItems in the MathDocument
  183. *
  184. * @return {MenuMathDocument} The MathDocument (so calls can be chained)
  185. */
  186. public addMenu(): MenuMathDocument {
  187. if (!this.processed.isSet('context-menu')) {
  188. for (const math of this.math) {
  189. (math as MenuMathItem).addMenu(this);
  190. }
  191. this.processed.set('context-menu');
  192. }
  193. return this;
  194. }
  195. /**
  196. * Checks if there are files being loaded by the menu, and restarts the typesetting if so
  197. *
  198. * @return {MenuMathDocument} The MathDocument (so calls can be chained)
  199. */
  200. public checkLoading(): MenuMathDocument {
  201. if (this.menu.isLoading) {
  202. mathjax.retryAfter(this.menu.loadingPromise.catch((err) => console.log(err)));
  203. }
  204. const settings = this.menu.settings;
  205. if (settings.collapsible) {
  206. this.options.enableComplexity = true;
  207. this.menu.checkComponent('a11y/complexity');
  208. }
  209. if (settings.explorer) {
  210. this.options.enableEnrichment = true;
  211. this.options.enableExplorer = true;
  212. this.menu.checkComponent('a11y/explorer');
  213. }
  214. return this;
  215. }
  216. /**
  217. * @override
  218. */
  219. public state(state: number, restore: boolean = false) {
  220. super.state(state, restore);
  221. if (state < STATE.CONTEXT_MENU) {
  222. this.processed.clear('context-menu');
  223. }
  224. return this;
  225. }
  226. /**
  227. * @override
  228. */
  229. public updateDocument() {
  230. super.updateDocument();
  231. (this.menu.menu.store as any).sort();
  232. return this;
  233. }
  234. };
  235. }
  236. /*==========================================================================*/
  237. /**
  238. * Add context-menu support to a Handler instance
  239. *
  240. * @param {Handler} handler The Handler instance to enhance
  241. * @return {Handler} The handler that was modified (for purposes of chaining extensions)
  242. */
  243. export function MenuHandler(handler: Handler<HTMLElement, Text, Document>): Handler<HTMLElement, Text, Document> {
  244. handler.documentClass = MenuMathDocumentMixin<A11yDocumentConstructor>(handler.documentClass as any);
  245. return handler;
  246. }