UNPKG

mathlive

Version:

Render and edit beautifully typeset math

1,007 lines (918 loc) 72 kB
/** * @module editor/virtualKeyboard * @private */ import MathAtom from '../core/mathAtom.js'; import Span from '../core/span.js'; import Lexer from '../core/lexer.js'; import ParserModule from '../core/parser.js'; import Color from '../core/color.js'; import '../addons/outputLatex.js'; import { l10n } from './l10n.js'; const KEYBOARDS = { 'numeric': { tooltip: 'keyboard.tooltip.numeric', layer: 'math', label: '123', layers: ['math'] }, 'roman': { tooltip: 'keyboard.tooltip.roman', layer: 'lower-roman', label: 'ABC', layers: ['lower-roman', 'upper-roman', 'symbols'] }, 'greek': { tooltip: 'keyboard.tooltip.greek', layer: 'lower-greek', label: '&alpha;&beta;&gamma;', classes: 'tex-math', layers: ['lower-greek', 'upper-greek'] }, 'functions': { tooltip: 'keyboard.tooltip.functions', layer: 'functions', label: '<i>f</i>&thinsp;()', classes: 'tex', layers: ['functions'] }, 'command': { tooltip: 'keyboard.tooltip.command', // For the command keyboard, perform a command rather than // doing a simple layer switch, as we want to enter command mode // when the keyboard is activated command: 'enterCommandMode', label: `<svg><use xlink:href='#svg-command' /></svg>`, layers: ['lower-command', 'upper-command', 'symbols-command'] }, 'style': { tooltip: 'keyboard.tooltip.style', layer: 'style', label: '<b>b</b><i>i</i>𝔹', } } const SHIFTED_KEYS = { '\\varphi ': {label: '&Phi;', insert: '\\Phi '}, '\\varsigma ': {label: '&Sigma;', insert: '\\Sigma '}, '\\epsilon ': {label: '&#x0190;', insert: '{\\char"0190}'}, '\\rho ': {label: '&#x3A1', insert: '{\\char"3A1}'}, '\\tau ': {label: '&#x3A4;', insert: '{\\char"3A4}'}, '\\upsilon ': {label: '&Upsilon;', insert: '\\Upsilon '}, '\\theta ': {label: '&Theta;', insert: '\\Theta '}, '\\iota ': {label: '&Iota;', insert: '{\\char"399}'}, '\\omicron ': {label: '&#x039F;', insert: '{\\char"39F}'}, '\\pi ': {label: '&Pi;', insert: '\\Pi '}, '\\alpha ': {label: '&Alpha;', insert: '{\\char"391}'}, '\\sigma ': {label: '&Sigma;', insert: '\\Sigma '}, '\\delta ': {label: '&Delta;', insert: '\\Delta '}, '\\phi ': {label: '&#x03a6;', insert: '\\Phi '}, '\\gamma ': {label: '&Gamma;', insert: '\\Gamma '}, '\\eta ': {label: '&Eta;', insert: '{\\char"397}'}, '\\xi ': {label: '&Xi;', insert: '\\Xi '}, '\\kappa ': {label: '&Kappa;', insert: '{\\char"39A}'}, '\\lambda ': {label: '&Lambda;', insert: '\\Lambda '}, '\\zeta ': {label: '&Zeta;', insert: '{\\char"396}'}, '\\chi ': {label: '&Chi;', insert: '{\\char"3A7}'}, '\\psi ': {label: '&Psi;', insert: '\\Psi '}, '\\omega ': {label: '&Omega;', insert: '\\Omega '}, '\\beta ': {label: '&Beta;', insert: '{\\char"392}'}, '\\nu ': {label: '&Nu;', insert: '{\\char"39D}'}, '\\mu ': {label: '&Mu;', insert: '{\\char"39C}'} } // const FUNCTIONS = [ // 'Basic', // ['\\sin', '\\cos', '\\tan', '\\min', '\\max', '\\gcd', '\\lcm', '\\repeat', 'encapsulate', 'recognize'], // 'Operators', // ['\\sum', '\\prod', '\\bigcup_x'] // ] const ALT_KEYS_BASE = { '0': ['\\emptyset', '\\varnothing', '\\infty', {latex: '#?_0', insert: '#@_0'}, '\\circ', '\\bigcirc', '\\bullet'], '2': ['\\frac{1}{2}', {latex: '#?^2', insert: '#@^2' }], '3': ['\\frac{1}{3}', {latex: '#?^3', insert: '#@^3' }], '.': [ ',', ';', '\\colon', {latex:':', aside:'ratio'}, {latex:'\\cdotp', aside:'center dot', classes:'box'}, {latex:'\\cdots', aside:'center ellipsis', classes:'box'}, {latex:'\\ldotp', aside:'low dot', classes:'box'} , {latex:'\\ldots', aside:'low ellipsis', classes:'box'} , {latex:'\\vdots', aside:'', classes:'box'} , {latex:'\\ddots', aside:'', classes:'box'} , '\\odot', '\\oslash', '\\circledcirc', ], '*': [ '\\cdot', '\\ast', '\\star', '\\bigstar', '\\ltimes', '\\rtimes', '\\rightthreetimes','\\leftthreetimes', '\\intercal', '\\prod', {latex:'\\prod_{n\\mathop=0}^{\\infty}', classes:'small'}, ], '+': [ '\\pm', '\\mp', '\\sum', {latex:'\\sum_{n\\mathop=0}^{\\infty}', classes:'small'}, '\\dotplus', '\\oplus' ], '-': [ '\\pm', '\\mp', '\\ominus', '\\vert #0 \\vert' ], '/': ['\\divideontimes', '/', '\\div'], '(':[ '\\left( #0\\right)', '\\left[ #0\\right]', '\\left\\{ #0\\right\\}', '\\left\\langle #0\\right\\rangle', '\\lfloor', '\\llcorner', '(', '\\lbrack', '\\lvert', '\\lVert', '\\lgroup', '\\langle', '\\lceil', '\\ulcorner', '\\lmoustache', '\\lbrace', ], ')':[ '\\rfloor', '\\lrcorner', ')', '\\rbrack', '\\rvert', '\\rVert', '\\rgroup', '\\rangle', '\\rceil', '\\urcorner', '\\rmoustache', '\\rbrace', ], '=': [ '\\cong', '\\asymp', '\\equiv', '\\differencedelta', '\\varpropto', '\\thickapprox', '\\approxeq', '\\thicksim', '\\backsim', '\\eqsim', '\\simeq', '\\Bumpeq', '\\bumpeq', '\\doteq', '\\Doteq', '\\fallingdotseq', '\\risingdotseq', '\\coloneq', '\\eqcirc', '\\circeq', '\\triangleq', '\\between', ], '!=': [ '\\neq', '\\ncong', '', '\\nsim', ], '<': [ '\\leq', '\\leqq', '\\lneqq', '\\ll', '\\nless', '\\nleq', '\\precsim', '\\lesssim', '\\lessgtr', '\\prec', '\\preccurlyeq', '\\lessdot', '\\nprec', ], '>': [ '\\geq', '\\geqq', '\\gneqq', '\\gg', '\\ngtr', '\\ngeq', '\\succsim', '\\gtrsim', '\\gtrless', '\\succ', '\\succcurlyeq', '\\gtrdot', '\\nsucc' ], 'set': [ '\\in', '\\owns', '\\subset', '\\nsubset', '\\supset', '\\nsupset' ], '!set': [ '\\notin', '\\backepsilon' ], 'subset': [], 'supset': [], 'infinity': ['\\aleph_0', '\\aleph_1', '\\omega', '\\mathfrak{m}'], 'numeric-pi': ['\\prod', '\\theta', '\\rho', '\\sin', '\\cos', '\\tan' ], 'ee': [ '\\times 10^{#?}', '\\ln', '\\ln_{10}', '\\log' ], '^': ['_{#?}'], // Integrals 'int': [ {latex:'\\int_{#?}^{#?}', classes:'small'}, {latex:'\\int', classes:'small'}, {latex:'\\smallint', classes:'small'}, {latex:'\\iint', classes:'small'}, {latex:'\\iiint', classes:'small'}, {latex:'\\oint', classes:'small'}, {latex: '\\dfrac{\\rd}{\\rd x}', classes:'small'}, {latex:'\\frac{\\partial}{\\partial x}', classes:'small'}, '\\capitalDifferentialD', '\\rd', '\\partial', ], 'nabla': ['\\nabla\\times', '\\nabla\\cdot', '\\nabla^{2}'], '!': ['!!', '\\Gamma', '\\Pi'], 'accents': ['\\bar{#@}', '\\vec{#@}', '\\hat{#@}', '\\check{#@}', '\\dot{#@}', '\\ddot{#@}', '\\mathring{#@}', '\\breve{#@}', '\\acute{#@}', '\\tilde{#@}', '\\grave{#@}'], // 'absnorm': [{latex:'\\lVert #@ \\rVert', aside:'norm'}, // {latex:'\\lvert #@ \\rvert', aside:'determinant'}, // {latex:'\\begin{cardinality} #@ \\end{cardinality}', aside:'cardinality'}, // {latex:'\\lvert #@ \\rvert', aside:'length'}, // {latex:'\\lvert #@ \\rvert', aside:'order'}, // ], 'A': [{latex:'\\aleph', aside:'aleph'}, {latex:'\\forall', aside:'for all'}, ], 'a': [{latex:'\\aleph', aside:'aleph'}, {latex:'\\forall', aside:'for all'}, ], 'b': [{latex:'\\beth', aside:'beth'}], 'B': [{latex:'\\beth', aside:'beth'}], 'c': [{latex:'\\C', aside:'set of complex numbers'}], 'd': [{latex:'\\daleth', aside:'daleth'}], 'D': [{latex:'\\daleth', aside:'daleth'}], 'e': [{latex:'\\exponentialE', aside:'exponential e'}, {latex:'\\exists', aside:'there is'}, {latex:'\\nexists', aside:'there isn’t'}, ], 'g': [{latex:'\\gimel', aside:'gimel'}], 'G': [{latex:'\\gimel', aside:'gimel'}], 'h': [{latex:'\\hbar', aside:'h bar'}, {latex: '\\hslash', aside:'h slash'} ], 'i': [{latex:'\\imaginaryI', aside:'imaginary i'}], 'j': [{latex:'\\imaginaryJ', aside:'imaginary j'}], 'l': [{latex:'\\ell', aside:'ell'}], 'n': [{latex:'\\N', aside:'set of natural numbers'}], 'p': [{latex:'\\P', aside:'set of primes'}], 'q': [{latex:'\\Q', aside:'set of rational numbers'}], 'r': [{latex:'\\R', aside:'set of real numbers'}], 'z': [{latex:'\\Z', aside:'set of integers'}], 'x-var': ['y', 'z', 't', 'r', {latex:'f(#?)', classes:'small'}, {latex:'g(#?)', classes:'small'}, 'x^2', 'x^n', 'x_n', 'x_{n+1}', 'x_i', 'x_{i+1}'], 'n-var': ['i', 'j', 'p', 'k', 'a', 'u'], 'ii': ['\\Re', '\\Im', '\\imaginaryJ', '\\Vert #0 \\Vert'], 'logic': [ {latex:'\\exists', aside:'there is'}, {latex:'\\nexists', aside:'there isn’t'}, {latex:'\\ni', aside:'such that'}, {latex:'\\Colon', aside:'such that'}, {latex:'\\implies', aside:'implies'}, {latex:'\\impliedby', aside:'implied by'}, {latex:'\\iff', aside:'if and only if'}, {latex:'\\land', aside:'and'}, {latex:'\\lor', aside:'or'}, {latex:'\\oplus', aside:'xor'}, {latex:'\\lnot', aside:'not'}, {latex:'\\downarrow', aside:'nor'}, {latex:'\\uparrow', aside:'nand'}, {latex:'\\curlywedge', aside:'nor'}, {latex:'\\bar\\curlywedge', aside:'nand'}, // {latex:'\\barwedge', aside:'bar wedge'}, // {latex:'\\curlyvee', aside:'curly vee'}, // {latex:'\\veebar', aside:'vee bar'}, {latex:'\\therefore', aside:'therefore'}, {latex:'\\because', aside:'because'}, {latex:'^\\biconditional', aside:'biconditional'}, '\\leftrightarrow', '\\Leftrightarrow', '\\to', '\\models', '\\vdash', '\\gets', '\\dashv', '\\roundimplies'], 'set-operators': ['\\cap', '\\cup', '\\setminus', '\\smallsetminus', '\\complement'], 'set-relations': [ '\\in', '\\notin', '\\ni', '\\owns', '\\subset', '\\supset', '\\subseteq', '\\supseteq', '\\subsetneq', '\\supsetneq', '\\varsubsetneq', '\\subsetneqq', '\\nsubset', '\\nsupset', '\\nsubseteq', '\\nsupseteq' ], 'space': [ {latex: '\\char"203A\\!\\char"2039', insert: '\\!', aside:'negative thin space<br>⁻³⧸₁₈ em'}, {latex: '\\unicode{"203A}\\,\\unicode{"2039}', insert: '\\,', aside:'thin space<br>³⧸₁₈ em'}, {latex: '\\unicode{"203A}\\:\\unicode{"2039}', insert: '\\:', aside:'medium space<br>⁴⧸₁₈ em'}, {latex: '\\unicode{"203A}\\;\\unicode{"2039}', insert: '\\;', aside:'thick space<br>⁵⧸₁₈ em'}, {latex: '\\unicode{"203A}\\ \\unicode{"2039}', insert: '\\ ', aside:'⅓ em'}, {latex: '\\unicode{"203A}\\enspace\\unicode{"2039}', insert: '\\enspace', aside:'½ em'}, {latex: '\\unicode{"203A}\\quad\\unicode{"2039}', insert: '\\quad', aside:'1 em'}, {latex: '\\unicode{"203A}\\qquad\\unicode{"2039}', insert: '\\qquad', aside:'2 em'} ], // @todo could also delete to end 'delete': [{label: '<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>', command: '"deleteAll"' }], // @todo Tab: could turn on speech, visible keyboard... '->|': [] }; let ALT_KEYS = {}; const LAYERS = { 'math': ` <div class='rows'> <ul> <li class='keycap tex' data-alt-keys='x-var'><i>x</i></li> <li class='keycap tex' data-alt-keys='n-var'><i>n</i></li> <li class='separator w5'></li> <row name='numpad-1'/> <li class='separator w5'></li> <li class='keycap tex' data-key='ee' data-alt-keys='ee'>e</li> <li class='keycap tex' data-key='ii' data-alt-keys='ii'>i</li> <li class='keycap tex' data-latex='\\pi' data-alt-keys='numeric-pi'></li> </ul> <ul> <li class='keycap tex' data-key='<' data-alt-keys='<'>&lt;</li> <li class='keycap tex' data-key='>' data-alt-keys='>'>&gt;</li> <li class='separator w5'></li> <row name='numpad-2'/> <li class='separator w5'></li> <li class='keycap tex' data-alt-keys='x2' data-insert='#@^{2}'><span><i>x</i>&thinsp;²</span></li> <li class='keycap tex' data-alt-keys='^' data-insert='#@^{#?}'><span><i>x</i><sup>&thinsp;<small>&#x2b1a;</small></sup></span></li> <li class='keycap tex' data-alt-keys='sqrt' data-insert='\\sqrt{#0}' data-latex='\\sqrt{#0}'></li> </ul> <ul> <li class='keycap tex' data-alt-keys='(' >(</li> <li class='keycap tex' data-alt-keys=')' >)</li> <li class='separator w5'></li> <row name='numpad-3'/> <li class='separator w5'></li> <li class='keycap tex small' data-alt-keys='int' data-latex='\\int_0^\\infty'><span></span></li> <li class='keycap tex' data-latex='\\forall' data-alt-keys='logic' ></li> <li class='action font-glyph bottom right' data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]'>&#x232b;</li></ul> </ul> <ul> <li class='keycap' data-alt-keys='foreground-color' data-command='["applyStyle",{"color":"#cc2428"}]'><span style='border-radius: 50%;width:22px;height:22px; border: 3px solid #cc2428; box-sizing: border-box'></span></li> <li class='keycap' data-alt-keys='background-color' data-command='["applyStyle",{"backgroundColor":"#fff590"}]'><span style='border-radius: 50%;width:22px;height:22px; background:#fff590; box-sizing: border-box'></span></li> <li class='separator w5'></li> <row name='numpad-4'/> <li class='separator w5'></li> <arrows/> </ul> </div> `, 'lower-roman': ` <div class='rows'> <ul> <row name='numpad-1' class='if-wide'/> <row name='lower-1' shift-layer='upper-roman'/> </ul> <ul> <row name='numpad-2' class='if-wide'/> <row name='lower-2' shift-layer='upper-roman''/> </ul> <ul> <row name='numpad-3' class='if-wide'/> <row name='lower-3' shift-layer='upper-roman''/> </ul> <ul> <row name='numpad-4' class='if-wide'/> <li class='layer-switch font-glyph modifier bottom left' data-layer='symbols'>&infin;≠</li> <li class='keycap' data-alt-keys=','>,</li> <li class='keycap w50' data-key=' ' data-alt-keys='space'>&nbsp;</li> <arrows/> </ul> </div>`, 'upper-roman': ` <div class='rows'> <ul> <row name='numpad-1' class='if-wide'/> <row name='upper-1' shift-layer='lower-roman'/> </ul> <ul> <row name='numpad-2' class='if-wide'/> <row name='upper-2' shift-layer='lower-roman'/> </ul> <ul> <row name='numpad-3' class='if-wide'/> <row name='upper-3' shift-layer='lower-roman'/> </ul> <ul> <row name='numpad-4' class='if-wide'/> <li class='layer-switch font-glyph modifier bottom left' data-layer='symbols'>&infin;≠</li> <li class='keycap' data-alt-keys='.'>;</li> <li class='keycap w50' data-key=' '>&nbsp;</li> <arrows/> </ul> </div>`, 'symbols': ` <div class='rows'> <ul> <row name='numpad-1' class='if-wide'/> <li class='keycap tex' data-alt-keys='(' data-insert='\\lbrace '>{</li> <li class='keycap tex' data-alt-keys=')' data-insert='\\rbrace '>}</li> <li class='separator w5'></li> <li class='keycap tex' data-alt-keys='set' data-insert='\\in '>&#x2208;</li> <li class='keycap tex' data-alt-keys='!set' data-insert='\\notin '>&#x2209;</li> <li class='keycap tex' data-insert='\\Re '>&#x211c;<aside>Real</aside></li> <li class='keycap tex' data-insert='\\Im '>&#x2111;<aside>Imaginary</aside></li> <li class='keycap w15' data-insert='\\ulcorner#0\\urcorner '><span><sup>&#x250c;</sup><span><span style='color:#ddd'>o</span><sup>&#x2510;</sup></span><aside>ceil</aside></li> <li class='keycap tex' data-alt-keys='nabla' data-insert='\\nabla '>&#x2207;<aside>nabla</aside></li> <li class='keycap tex' data-alt-keys='infinity' data-insert='\\infty '>&#x221e;</li> </ul> <ul> <row name='numpad-2' class='if-wide'/> <li class='keycap tex' data-alt-keys='(' data-insert='\\lbrack '>[</li> <li class='keycap tex' data-alt-keys=')' data-insert='\\rbrack '>]</li> <li class='separator w5'></li> <li class='keycap tex' data-alt-keys='subset' data-insert='\\subset '>&#x2282;</li> <li class='keycap tex' data-alt-keys='supset' data-insert='\\supset '>&#x2283;</li> <li class='keycap tex' data-key='!' data-alt-keys='!'>!<aside>factorial</aside></li> <li class='keycap' data-insert='^{\\prime} '><span><sup><span><span style='color:#ddd'>o</span>&#x2032</sup></span><aside>prime</aside></li> <li class='keycap w15' data-insert='\\llcorner#0\\lrcorner '><span><sub>&#x2514;</sub><span style='color:#ddd'>o</span><sub>&#x2518;</sub></span><aside>floor</aside></li> <li class='keycap tex' data-insert='\\partial '>&#x2202;<aside>partial<br>derivative</aside></li> <li class='keycap tex' data-insert='\\emptyset '>&#x2205;<aside>empty set</aside></li> </ul> <ul> <row name='numpad-3' class='if-wide'/> <li class='keycap tex' data-alt-keys='(' data-insert='\\langle '>&#x27e8;</li> <li class='keycap tex' data-alt-keys=')' data-insert='\\rangle '>&#x27e9;</li> <li class='separator w5'></li> <li class='keycap tex' data-insert='\\subseteq '>&#x2286;</li> <li class='keycap tex' data-insert='\\supseteq '>&#x2287;</li> <li class='keycap tex' data-alt-keys='accents' data-insert='\\vec{#@}' data-latex='\\vec{#?}' data-aside='vector'></li> <li class='keycap tex' data-alt-keys='accents' data-insert='\\bar{#@}' data-latex='\\bar{#?}' data-aside='bar'></li> <li class='keycap tex' data-alt-keys='absnorm' data-insert='\\lvert #@ \\rvert ' data-latex='\\lvert #? \\rvert' data-aside='abs'></li> <li class='keycap tex' data-insert='\\ast '>&#x2217;<aside>asterisk</aside></li> <li class='action font-glyph bottom right w15' data-shifted='<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>' data-shifted-command='"deleteAll"' data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]' >&#x232b;</li> </ul> <ul> <row name='numpad-4' class='if-wide'/> <li class='layer-switch font-glyph modifier bottom left' data-layer='lower-roman'>abc</li> <li class='keycap tex' data-insert='\\cdot '>&#x22c5;<aside>centered dot</aside></li> <li class='keycap tex' data-insert='\\colon '>:<aside>colon</aside></li> <li class='keycap tex' data-insert='\\circ '>&#x2218;<aside>circle</aside></li> <li class='keycap tex' data-insert='\\approx '>&#x2248;<aside>approx.</aside></li> <li class='keycap tex' data-insert='\\ne '>&#x2260;</li> <li class='keycap tex' data-insert='\\pm '>&#x00b1;</li> <arrows/> </ul> </div>`, 'lower-greek': ` <div class='rows'> <ul><li class='keycap tex' data-insert='\\varphi '><i>&#x03c6;</i><aside>phi var.</aside></li> <li class='keycap tex' data-insert='\\varsigma '><i>&#x03c2;</i><aside>sigma var.</aside></li> <li class='keycap tex' data-insert='\\epsilon '><i>&#x03f5;</i></li> <li class='keycap tex' data-insert='\\rho '><i>&rho;</i></li> <li class='keycap tex' data-insert='\\tau '><i>&tau;</i></li> <li class='keycap tex' data-insert='\\upsilon '><i>&upsilon;</i></li> <li class='keycap tex' data-insert='\\theta '><i>&theta;</i></li> <li class='keycap tex' data-insert='\\iota '><i>&iota;</i></li> <li class='keycap tex' data-insert='\\omicron '>&omicron;</i></li> <li class='keycap tex' data-insert='\\pi '><i>&pi;</i></li> </ul> <ul><li class='keycap tex' data-insert='\\alpha ' data-shifted='&Alpha;' data-shifted-command='["insert","{\\\\char\\"391}"]'><i>&alpha;</i></li> <li class='keycap tex' data-insert='\\sigma '><i>&sigma;</i></li> <li class='keycap tex' data-insert='\\delta '><i>&delta;</i></li> <li class='keycap tex' data-insert='\\phi '><i>&#x03d5;</i></i></li> <li class='keycap tex' data-insert='\\gamma '><i>&gamma;</i></li> <li class='keycap tex' data-insert='\\eta '><i>&eta;</i></li> <li class='keycap tex' data-insert='\\xi '><i>&xi;</i></li> <li class='keycap tex' data-insert='\\kappa '><i>&kappa;</i></li> <li class='keycap tex' data-insert='\\lambda '><i>&lambda;</i></li> </ul> <ul><li class='shift modifier font-glyph bottom left w15 layer-switch' data-layer='upper-greek'>&#x21e7;</li> <li class='keycap tex' data-insert='\\zeta '><i>&zeta;</i></li> <li class='keycap tex' data-insert='\\chi '><i>&chi;</i></li> <li class='keycap tex' data-insert='\\psi '><i>&psi;</i></li> <li class='keycap tex' data-insert='\\omega '><i>&omega;</i></li> <li class='keycap tex' data-insert='\\beta '><i>&beta;</i></li> <li class='keycap tex' data-insert='\\nu '><i>&nu;</i></li> <li class='keycap tex' data-insert='\\mu '><i>&mu;</i></li> <li class='action font-glyph bottom right w15' data-shifted='<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>' data-shifted-command='"deleteAll"' data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]' >&#x232b;</li> </ul> <ul> <li class='keycap ' data-key=' '>&nbsp;</li> <li class='keycap'>,</li> <li class='keycap tex' data-insert='\\varepsilon '><i>&#x03b5;</i><aside>epsilon var.</aside></li> <li class='keycap tex' data-insert='\\vartheta '><i>&#x03d1;</i><aside>theta var.</aside></li> <li class='keycap tex' data-insert='\\varkappa '><i>&#x3f0;</i><aside>kappa var.</aside></li> <li class='keycap tex' data-insert='\\varpi '><i>&#x03d6;<aside>pi var.</aside></i></li> <li class='keycap tex' data-insert='\\varrho '><i>&#x03f1;</i><aside>rho var.</aside></li> <arrows/> </ul> </div>`, 'upper-greek': ` <div class='rows'> <ul><li class='keycap tex' data-insert='\\Phi '>&Phi;<aside>phi</aside></li> <li class='keycap tex' data-insert='\\Sigma '>&Sigma;<aside>sigma</aside></li> <li class='keycap tex' data-insert='{\\char"0190}'>&#x0190;<aside>epsilon</aside></li> <li class='keycap tex' data-insert='{\\char"3A1}'>&#x3A1;<aside>rho</aside></li> <li class='keycap tex' data-insert='{\\char"3A4}'>&#x3A4;<aside>tau</aside></li> <li class='keycap tex' data-insert='\\Upsilon '>&Upsilon;<aside>upsilon</aside></li> <li class='keycap tex' data-insert='\\Theta '>&Theta;<aside>theta</aside></li> <li class='keycap tex' data-insert='{\\char"399}'>&Iota;<aside>iota</aside></li> <li class='keycap tex' data-insert='{\\char"39F}'>&#x039F;<aside>omicron</aside></li> <li class='keycap tex' data-insert='\\Pi '>&Pi;<aside>pi</aside></li></ul> <ul><li class='keycap tex' data-insert='{\\char"391}'>&#x391;<aside>alpha</aside></li> <li class='keycap tex' data-insert='\\Sigma '>&Sigma;<aside>sigma</aside></li> <li class='keycap tex' data-insert='\\Delta '>&Delta;<aside>delta</aside></li> <li class='keycap tex' data-insert='\\Phi '>&#x03a6;<aside>phi</aside></li> <li class='keycap tex' data-insert='\\Gamma '>&Gamma;<aside>gamma</aside></li> <li class='keycap tex' data-insert='{\\char"397}'>&Eta;<aside>eta</aside></li> <li class='keycap tex' data-insert='\\Xi '>&Xi;<aside>xi</aside></li> <li class='keycap tex' data-insert='{\\char"39A}'>&Kappa;<aside>kappa</aside></li> <li class='keycap tex' data-insert='\\Lambda '>&Lambda;<aside>lambda</aside></li></ul> <ul><li class='shift modifier font-glyph bottom left selected w15 layer-switch' data-layer='lower-greek'>&#x21e7;</li> <li class='keycap tex' data-insert='{\\char"396}'>&Zeta;<aside>zeta</aside></li> <li class='keycap tex' data-insert='{\\char"3A7}'>&Chi;<aside>chi</aside></li> <li class='keycap tex' data-insert='\\Psi '>&Psi;<aside>psi</aside></li> <li class='keycap tex' data-insert='\\Omega '>&Omega;<aside>omega</aside></li> <li class='keycap tex' data-insert='{\\char"392}'>&Beta;<aside>beta</aside></li> <li class='keycap tex' data-insert='{\\char"39D}'>&Nu;<aside>nu</aside></li> <li class='keycap tex' data-insert='{\\char"39C}'>&Mu;<aside>mu</aside></li> <li class='action font-glyph bottom right w15' data-command='["performWithFeedback","deletePreviousChar"]'>&#x232b;</li></ul> <ul> <li class='separator w10'>&nbsp;</li> <li class='keycap'>.</li> <li class='keycap w50' data-key=' '>&nbsp;</li> <arrows/> </ul> </div>`, 'lower-command': ` <div class='rows'> <ul><row name='lower-1' class='tt' shift-layer='upper-command'/></ul> <ul><row name='lower-2' class='tt' shift-layer='upper-command'/></ul> <ul><row name='lower-3' class='tt' shift-layer='upper-command'/></ul> <ul> <li class='layer-switch font-glyph modifier bottom left' data-layer='symbols-command'>01#</li> <li class='keycap tt' data-shifted='[' data-shifted-command='["insertAndUnshiftKeyboardLayer", "["]'>{</li> <li class='keycap tt' data-shifted=']' data-shifted-command='["insertAndUnshiftKeyboardLayer", "]"]'>}</li> <li class='keycap tt' data-shifted='(' data-shifted-command='["insertAndUnshiftKeyboardLayer", "("]'>^</li> <li class='keycap tt' data-shifted=')' data-shifted-command='["insertAndUnshiftKeyboardLayer", ")"]'>_</li> <li class='keycap w20' data-key=' '>&nbsp;</li> <arrows/> </ul> </div>`, 'upper-command': ` <div class='rows'> <ul><row name='upper-1' class='tt' shift-layer='lower-command'/></ul> <ul><row name='upper-2' class='tt' shift-layer='lower-command'/></ul> <ul><row name='upper-3' class='tt' shift-layer='lower-command'/></ul> <ul> <li class='layer-switch font-glyph modifier bottom left' data-layer='symbols-command'01#</li> <li class='keycap tt'>[</li> <li class='keycap tt'>]</li> <li class='keycap tt'>(</li> <li class='keycap tt'>)</li> <li class='keycap w20' data-key=' '>&nbsp;</li> <arrows/> </ul> </div>`, 'symbols-command': ` <div class='rows'> <ul><li class='keycap tt'>1</li><li class='keycap tt'>2</li><li class='keycap tt'>3</li><li class='keycap tt'>4</li><li class='keycap tt'>5</li><li class='keycap tt'>6</li><li class='keycap tt'>7</li><li class='keycap tt'>8</li><li class='keycap tt'>9</li><li class='keycap tt'>0</li></ul> <ul><li class='keycap tt'>!</li><li class='keycap tt'>@</li><li class='keycap tt'>#</li><li class='keycap tt'>$</li><li class='keycap tt'>%</li><li class='keycap tt'>^</li><li class='keycap tt'>&</li><li class='keycap tt'>*</li><li class='keycap tt'>+</li><li class='keycap tt'>=</li></ul> <ul> <li class='keycap tt'>\\</li> <li class='keycap tt'>|</li> <li class='keycap tt'>/</li> <li class='keycap tt'>\`</li> <li class='keycap tt'>;</li> <li class='keycap tt'>:</li> <li class='keycap tt'>?</li> <li class='keycap tt'>'</li> <li class='keycap tt'>"</li> <li class='action font-glyph bottom right' data-shifted='<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>' data-shifted-command='"deleteAll"' data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]' >&#x232b;</li> </ul> <ul> <li class='layer-switch font-glyph modifier bottom left' data-layer='lower-command'>abc</li> <li class='keycap tt'>&lt;</li> <li class='keycap tt'>&gt;</li> <li class='keycap tt'>~</li> <li class='keycap tt'>,</li> <li class='keycap tt'>.</li> <li class='keycap' data-key=' '>&nbsp;</li> <arrows/> </ul> </div>`, 'functions': ` <div class='rows'> <ul><li class='separator'></li> <li class='fnbutton' data-insert='\\sin'></li> <li class='fnbutton' data-insert='\\sin^{-1}'></li> <li class='fnbutton' data-insert='\\ln'></li> <li class='fnbutton' data-insert='\\exponentialE^{#?}'></li> <li class='bigfnbutton' data-insert='\\operatorname{lcm}(#?)' data-latex='\\operatorname{lcm}()'></li> <li class='bigfnbutton' data-insert='\\operatorname{ceil}(#?)' data-latex='\\operatorname{ceil}()'></li> <li class='bigfnbutton' data-insert='\\lim_{n\\to\\infty}'></li> <li class='bigfnbutton' data-insert='\\int'></li> <li class='bigfnbutton' data-insert='\\operatorname{abs}(#?)' data-latex='\\operatorname{abs}()'></li> </ul> <ul><li class='separator'></li> <li class='fnbutton' data-insert='\\cos'></li> <li class='fnbutton' data-insert='\\cos^{-1}'></li> <li class='fnbutton' data-insert='\\ln_{10}'></li> <li class='fnbutton' data-insert='10^{#?}'></li> <li class='bigfnbutton' data-insert='\\operatorname{gcd}(#?)' data-latex='\\operatorname{gcd}()'></li> <li class='bigfnbutton' data-insert='\\operatorname{floor}(#?)' data-latex='\\operatorname{floor}()'></li> <li class='bigfnbutton' data-insert='\\sum_{n\\mathop=0}^{\\infty}'></li> <li class='bigfnbutton' data-insert='\\int_{0}^{\\infty}'></li> <li class='bigfnbutton' data-insert='\\operatorname{sign}(#?)' data-latex='\\operatorname{sign}()'></li> </ul> <ul><li class='separator'></li> <li class='fnbutton' data-insert='\\tan'></li> <li class='fnbutton' data-insert='\\tan^{-1}'></li> <li class='fnbutton' data-insert='\\log_{#?}'></li> <li class='fnbutton' data-insert='\\sqrt[#?]{#0}'></li> <li class='bigfnbutton' data-insert='#0 \\mod' data-latex='\\mod'></li> <li class='bigfnbutton' data-insert='\\operatorname{round}(#?) ' data-latex='\\operatorname{round}()'></li> <li class='bigfnbutton' data-insert='\\prod_{n\\mathop=0}^{\\infty}' data-latex='{\\tiny \\prod_{n=0}^{\\infty}}'></li> <li class='bigfnbutton' data-insert='\\frac{\\differentialD #0}{\\differentialD x}'></li> <li class='action font-glyph bottom right' data-command='["performWithFeedback","deletePreviousChar"]'>&#x232b;</li></ul> <ul><li class='separator'></li> <li class='fnbutton'>(</li> <li class='fnbutton'>)</li> <li class='fnbutton' data-insert='^{#?} ' data-latex='x^{#?} '></li> <li class='fnbutton' data-insert='_{#?} ' data-latex='x_{#?} '></li> <li class='keycap w20 ' data-key=' '>&nbsp;</li> <arrows/> </ul> </div>`, 'style': ` <div class='rows'> <ul> <li class='keycap' data-alt-keys='foreground-color' data-command='["applyStyle",{"color":"#cc2428"}]'><span style='border-radius: 50%;width:22px;height:22px; border: 3px solid #cc2428'></span></li> <li class='keycap' data-alt-keys='background-color' data-command='["applyStyle",{"backgroundColor":"#fff590"}]'><span style='border-radius: 50%;width:22px;height:22px; background:#fff590'></span></li> <li class='separator w5'></li> <li class='keycap' data-command='["applyStyle",{"size":"size3"}]' data-latex='\\scriptsize\\text{small}'></li> <li class='keycap' data-command='["applyStyle",{"size":"size5"}]' data-latex='\\scriptsize\\text{normal}'></li> <li class='keycap' data-command='["applyStyle",{"size":"size9"}]' data-latex='\\huge\\text{big}'></li> <li class='separator w5'></li> <li class='keycap' data-latex='\\langle' data-command='["insert", "\\\\langle", {"smartFence":true}]'></li> </ul> <ul> <li class='keycap' data-command='["applyStyle",{"series":"l"}]' data-latex='\\fontseries{l}\\text{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"series":"m"}]' data-latex='\\fontseries{m}\\text{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"series":"b"}]' data-latex='\\fontseries{b}\\text{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"series":"bx"}]' data-latex='\\fontseries{bx}\\text{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"series":"sb"}]' data-latex='\\fontseries{sb}\\text{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"series":"c"}]' data-latex='\\fontseries{c}\\text{Ab}'></li> </ul> <ul> <li class='keycap' data-command='["applyStyle",{"shape":"up"}]' data-latex='\\textup{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"shape":"it"}]' data-latex='\\textit{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"shape":"sl"}]' data-latex='\\textsl{Ab}'></li> <li class='keycap' data-command='["applyStyle",{"shape":"sc"}]' data-latex='\\textsc{Ab}'></li> <li class='separator w5'></li> <li class='keycap' data-insert='\\emph{#?} ' data-latex='\\text{\\emph{emph}}'></li> </ul> <ul> <li class='keycap' data-command='["applyStyle",{"fontFamily":"cmr"}]' data-latex='\\textrm{Az}'></li> <li class='keycap' data-command='["applyStyle",{"fontFamily":"cmtt"}]' data-latex='\\texttt{Az}'></li> <li class='keycap' data-command='["applyStyle",{"fontFamily":"cmss"}]' data-latex='\\textsf{Az}'></li> <li class='keycap' data-command='["applyStyle",{"fontFamily":"bb"}]' data-latex='\\mathbb{AZ}'></li> <li class='keycap' data-command='["applyStyle",{"fontFamily":"scr"}]' data-latex='\\mathscr{AZ}'></li> <li class='keycap' data-command='["applyStyle",{"fontFamily":"cal"}]' data-latex='\\mathcal{A1}'></li> <li class='keycap' data-command='["applyStyle",{"fontFamily":"frak"}]' data-latex='\\mathfrak{Az}'></li> </ul> </div>`, } function latexToMarkup(latex, arg, mf) { // Since we don't have preceding atoms, we'll interpret #@ as a placeholder latex = latex.replace(/(^|[^\\])#@/g, '$1#?'); const parse = ParserModule.parseTokens(Lexer.tokenize(latex), 'math', arg, mf.config.macros); const spans = MathAtom.decompose({ mathstyle: 'displaystyle', macros: mf.config.macros }, parse); const base = Span.makeSpan(spans, 'ML__base'); const topStrut = Span.makeSpan('', 'ML__strut'); topStrut.setStyle('height', base.height, 'em'); const bottomStrut = Span.makeSpan('', 'ML__strut--bottom'); bottomStrut.setStyle('height', base.height + base.depth, 'em'); bottomStrut.setStyle('vertical-align', -base.depth, 'em'); const wrapper = Span.makeSpan([topStrut, bottomStrut, base], 'ML__mathlive'); return wrapper.toMarkup(); } /** * Return a markup string for the keyboard toolbar for the specified layer. * @private */ function makeKeyboardToolbar(mf, keyboardIDs, currentKeyboard) { // The left hand side of the toolbar has a list of all the available keyboards let result = "<div class='left'>"; const keyboardList = keyboardIDs.replace(/\s+/g, ' ').split(' '); if (keyboardList.length > 1) { const keyboards = Object.assign({}, KEYBOARDS, mf.config.customVirtualKeyboards || {}); for (const keyboard of keyboardList) { if (!keyboards[keyboard]) { console.error('Unknown virtual keyboard "' + keyboard + '"'); break; } result += '<div class=\''; if (keyboard === currentKeyboard) { result += 'selected '; } else { if (keyboards[keyboard].command) { result += 'action '; } else { result += 'layer-switch '; } } result += (keyboards[keyboard].classes || '') + "'"; if (keyboards[keyboard].tooltip) { result += "data-tooltip='" + l10n(keyboards[keyboard].tooltip) + "' "; result += "data-placement='top' data-delay='1s'"; } if (keyboard !== currentKeyboard) { if (keyboards[keyboard].command) { result += "data-command='\"" + keyboards[keyboard].command + "\"'"; } if (keyboards[keyboard].layer) { result += "data-layer='" + keyboards[keyboard].layer + "'"; } } result += '>' + keyboards[keyboard].label + '</div>'; } } result += '</div>'; // The right hand side of the toolbar, with the copy/undo/redo commands result += ` <div class='right'> <div class='action' data-command='"copyToClipboard"' data-tooltip='${l10n('tooltip.copy to clipboard')}' data-placement='top' data-delay='1s'> <svg><use xlink:href='#svg-copy' /></svg> </div> <div class='action disabled' data-command='"undo"' data-tooltip='${l10n('tooltip.undo')}' data-placement='top' data-delay='1s'> <svg><use xlink:href='#svg-undo' /></svg> </div> <div class='action disabled' data-command='"redo"' data-tooltip='${l10n('tooltip.redo')}' data-placement='top' data-delay='1s'> <svg><use xlink:href='#svg-redo' /></svg> </div> </div> `; return "<div class='keyboard-toolbar' role='toolbar'>" + result + "</div>"; } function makeKeycap(mf, elList, chainedCommand) { for (let i = 0; i < elList.length; ++i) { const el = elList[i]; // Display if (el.getAttribute('data-latex')) { el.innerHTML = latexToMarkup(el.getAttribute('data-latex').replace(/&quot;/g, '"'), {'?':'{\\color{#555}{\\tiny \\char"2B1A}}'}, mf); } else if (el.innerHTML === '' && el.getAttribute('data-insert')) { el.innerHTML = latexToMarkup(el.getAttribute('data-insert').replace(/&quot;/g, '"'), {'?':'{\\color{#555}{\\tiny \\char"2B1A}}'}, mf); } else if (el.getAttribute('data-content')) { el.innerHTML = el.getAttribute('data-content').replace(/&quot;/g, '"'); } if (el.getAttribute('data-aside')) { el.innerHTML += '<aside>' + el.getAttribute('data-aside').replace(/&quot;/g, '"') + '</aside>'; } if (el.getAttribute('data-classes')) { el.classList.add(el.getAttribute('data-classes')); } let key = el.getAttribute('data-insert'); if (key) key = key.replace(/&quot;/g, '"'); if (key && SHIFTED_KEYS[key]) { el.setAttribute('data-shifted', SHIFTED_KEYS[key].label); el.setAttribute('data-shifted-command', JSON.stringify(['insertAndUnshiftKeyboardLayer', SHIFTED_KEYS[key].insert])); } // Commands let handlers; if (el.getAttribute('data-command')) { handlers = JSON.parse(el.getAttribute('data-command')); } else if (el.getAttribute('data-insert')) { handlers = ['insert', el.getAttribute('data-insert'), {focus:true, feedback:true, mode:'math', format:'auto', resetStyle:true}]; } else if (el.getAttribute('data-latex')) { handlers = ['insert', el.getAttribute('data-latex'), {focus:true, feedback:true, mode:'math', format:'auto', resetStyle:true}]; } else { handlers = ['typedText', el.getAttribute('data-key') || el.textContent, {focus:true, feedback:true, simulateKeystroke:true}]; } if (chainedCommand) { handlers = [chainedCommand, handlers]; } if (el.getAttribute('data-alt-keys')) { const altKeys = ALT_KEYS[el.getAttribute('data-alt-keys')]; if (altKeys) { handlers = { default: handlers, pressAndHoldStart: ['showAlternateKeys', el.getAttribute('data-alt-keys'), altKeys], pressAndHoldEnd: 'hideAlternateKeys' } } else { console.warn('Unknown alt key set: "' + el.getAttribute('data-alt-keys')); } } mf._attachButtonHandlers(el, handlers); } } /** * Expand the shortcut tags (e.g. <row>) inside a layer. * @param {object} mf * @param {string} layer * @private */ function expandLayerMarkup(mf, layer) { const ROWS = { // First row should be 10 key wide // Second row should be 10 key wide // Third row should be 8.5 key wide // One row should have ^ (shift key) which is 1.5 key wide // One row should have ~ (delete key) which is .5 or 1.5 key wide 'qwerty': { 'lower-1': 'qwertyuiop', 'lower-2': ' asdfghjkl ', 'lower-3': '^zxcvbnm~', 'upper-1': 'QWERTYUIOP', 'upper-2': ' ASDFGHJKL ', 'upper-3': '^ZXCVBNM~', 'numpad-1': '789/', 'numpad-2': '456*', 'numpad-3': '123-', 'numpad-4': '0.=+', }, 'azerty': { 'lower-1': 'azertyuiop', 'lower-2': 'qsdfghjklm', 'lower-3': '^ wxcvbn ~', 'upper-1': 'AZERTYUIOP', 'upper-2': 'QSDFGHJKLM', 'upper-3': '^ WXCVBN ~' }, 'qwertz': { 'lower-1': 'qwertzuiop', 'lower-2': ' asdfghjkl ', 'lower-3': '^yxcvbnm~', 'upper-1': 'QWERTZUIOP', 'upper-2': ' ASDFGHJKL', 'upper-3': '^YXCVBNM~' }, 'dvorak': { 'lower-1': '^ pyfgcrl ', 'lower-2': 'aoeuidhtns', 'lower-3': 'qjkxbmwvz~', 'upper-1': '^ PYFGCRL ', 'upper-2': 'AOEUIDHTNS', 'upper-3': 'QJKXBMWVZ~' }, 'colemak': { 'lower-1': ' qwfpgjluy ', 'lower-2': 'arstdhneio', 'lower-3': '^zxcvbkm~', 'upper-1': ' QWFPGNLUY ', 'upper-2': 'ARSTDHNEIO', 'upper-3': '^ZXCVBKM~' }, } const layout = ROWS[mf.config.virtualKeyboardLayout] ? ROWS[mf.config.virtualKeyboardLayout] : ROWS['qwerty']; let result = layer; let row; result = result.replace(/<arrows\/>/g, ` <li class='action' data-command='["performWithFeedback","moveToPreviousChar"]' data-shifted='<svg><use xlink:href="#svg-angle-double-left" /></svg>' data-shifted-command='["performWithFeedback","extendToPreviousChar"]'> <svg><use xlink:href='#svg-arrow-left' /></svg> </li> <li class='action' data-command='["performWithFeedback","moveToNextChar"]' data-shifted='<svg><use xlink:href="#svg-angle-double-right" /></svg>' data-shifted-command='["performWithFeedback","extendToNextChar"]'> <svg><use xlink:href='#svg-arrow-right' /></svg> </li> <li class='action' data-command='["performWithFeedback","moveToNextPlaceholder"]'> <svg><use xlink:href='#svg-tab' /></svg></li>`); let m = result.match(/(<row\s+)(.*)((?:<\/row|\/)>)/); while (m) { row = ''; const attributesArray = m[2].match(/[a-zA-Z][a-zA-Z0-9-]*=(['"])(.*?)\1/g); const attributes = {}; for (const attribute of attributesArray) { const m2 = attribute.match(/([a-zA-Z][a-zA-Z0-9-]*)=(['"])(.*?)\2/); attributes[m2[1]] = m2[3]; } let keys = layout[attributes['name']]; if (!keys) keys = ROWS['qwerty'][attributes['name']]; if (!keys) { console.warn('Unknown roman keyboard row: ' + attributes['name']); } else { for (const c of keys) { let cls = attributes['class'] || ''; if (cls) cls = ' ' + cls; if (c === '~') { row += `<li class='action font-glyph bottom right `; row += keys.length - ((keys.match(/ /g) || []).length / 2) === 10 ? 'w10' : 'w15'; row += `' data-shifted='<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>' data-shifted-command='"deleteAll"' data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]' >&#x232b;</li>`; } else if (c === ' ') { // Separator row += "<li class='separator w5'></li>"; } else if (c === '^') { // Shift key row += `<li class='shift modifier font-glyph bottom left w15 layer-switch' data-layer='` + attributes['shift-layer'] + `'>&#x21e7;</li>`; } else if (c === '/') { row += "<li class='keycap" + cls + "' data-alt-keys='/' data-insert='\\frac{#0}{#?}'>&divide;</li>"; } else if (c === '*') { row += "<li class='keycap" + cls + "' data-alt-keys='*' data-insert='\\times '>&times;</li>"; } else if (c === '-') { row += "<li class='keycap" + cls + "' data-alt-keys='*' data-key='-' data-alt-keys='-'>&#x2212;</li>"; } else if (/tt/.test(cls)) { row += "<li class='keycap" + cls + "' data-alt-keys='" + c + "'" + ` data-command='["typedText","` + c + `",{"commandMode":true, "focus":true, "feedback":true}]'` + ">" + c + "</li>" } else { row += "<li class='keycap" + cls + "' data-alt-keys='" + c + "'>" + c + "</li>" } } } result = result.replace(new RegExp(m[1] + m[2] + m[3]), row); m = result.match(/(<row\s+)(.*)((?:<\/row|\/)>)/); } return result; } /** * Construct a virtual keyboard element based on the config options in the * mathfield and an optional theme. * @param {obj