import { isArray } from '../common/types' ; import { makeStruts , parseLatex , Atom , coalesce , Box , Context , adjustInterAtomSpacing , DEFAULT_FONT_SIZE , getMacros , } from '../core/core' ; import { getKeybindingsForCommand } from './keybindings' ; import { attachButtonHandlers } from '../editor-mathfield/buttons' ; import { getCaretPoint , getSharedElement , releaseSharedElement , } from '../editor-mathfield/utils' ; import type { MathfieldPrivate } from '../editor-mathfield/mathfield-private' ; import { typeset } from '../core/typeset' ; import { getDefaultRegisters } from '../core/registers' ; import { throwIfNotInBrowser } from '../common/capabilities' ; import { hashCode } from '../common/hash-code' ; import { Stylesheet , inject as injectStylesheet } from '../common/stylesheet' ; import POPOVER_STYLESHEET from '../../css/popover.less' ; import CORE_STYLESHEET from '../../css/core.less' ; let POPOVER_STYLESHEET_HASH = undefined ; let gPopoverStylesheet = null ; let gCoreStylesheet = null ; export const NOTES = { '\\text' : 'roman text' , '\\textrm' : 'roman text' , '\\textnormal' : 'roman text' , '\\textit' : 'italic text' , '\\textbf' : 'bold text' , '\\texttt' : 'monospaced text' , '\\textsf' : 'sans-serif text' , '\\mathrm' : [ 'roman' , '(upright)' ] , '\\mathbf' : 'bold' , '\\bf' : 'bold' , '\\bold' : 'bold' , '\\mathit' : 'italic' , '\\mathbb' : 'blackboard' , '\\mathscr' : 'script' , '\\mathtt' : [ 'typewriter' , '(monospaced)' ] , '\\mathsf' : 'sans-serif' , '\\mathcal' : 'caligraphic' , '\\frak' : [ 'fraktur' , '(gothic)' ] , '\\mathfrak' : [ 'fraktur' , '(gothic)' ] , '\\textcolor' : 'text color' , '\\color' : 'color' , '\\forall' : 'for all' , '\\exists' : 'there exists' , '\\nexists' : 'there does not exist' , '\\frac' : 'fraction' , '\\dfrac' : 'display fraction' , '\\cfrac' : 'continuous fraction' , '\\tfrac' : 'text fraction' , '\\binom' : 'binomial coefficient' , '\\dbinom' : 'display binomial coefficient' , '\\tbinom' : 'text binomial coefficient' , '\\pdiff' : 'partial differential' , '\\vec' : 'vector' , '\\check' : 'caron' , '\\acute' : 'acute' , '\\breve' : 'breve' , '\\tilde' : 'tilde' , '\\dot' : 'dot' , '\\hat' : [ 'hat' , 'circumflex' ] , '\\ddot' : 'double dot' , '\\bar' : 'bar' , '\\prime' : 'prime' , '\\doubleprime' : 'double prime' , '\\varnothing' : 'empty set' , '\\emptyset' : 'empty set' , '\\subseteq' : 'subset of or <br>equal to' , '\\supseteq' : 'superset of or <br>equal to' , '\\supset' : 'superset of' , '\\subset' : 'subset of' , '\\partial' : 'partial derivative' , '\\bigcup' : 'union' , '\\bigcap' : 'intersection' , '\\approx' : 'approximately equal to' , '\\notin' : 'not an element of' , '\\in' : [ 'element of' , 'included in' ] , '\\infty' : 'infinity' , '\\land' : 'logical and' , '\\sqrt' : 'square root' , '\\prod' : 'product' , '\\sum' : 'summation' , '\\amalg' : [ 'amalgamation' , 'coproduct' , 'free product' , 'disjoint union' ] , '\\cup' : 'union with' , '\\cap' : 'intersection with' , '\\int' : 'integral' , '\\iint' : 'surface integral' , '\\oint' : 'curve integral' , '\\iiint' : 'volume integral' , '\\iff' : 'if and only if' , '\\ln' : 'natural logarithm' , '\\boldsymbol' : 'bold' , '\\setminus' : 'set subtraction' , '\\stackrel' : 'relation with symbol above' , '\\stackbin' : 'operator with symbol above' , '\\underset' : 'symbol with annotation below' , '\\overset' : 'symbol with annotation above' , '\\hslash' : [ 'h-bar' , 'Planck constant' ] , '\\gtrsim' : 'greater than or <br>similar to' , '\\propto' : 'proportional to' , '\\equiv' : 'equivalent to' , '\\!' : [ 'negative thin space' , '(-3 mu)' ] , '\\ ' : [ 'space' , '(6 mu)' ] , '\\,' : [ 'thin space' , '(3 mu)' ] , '\\:' : [ 'medium space' , '(4 mu)' ] , '\\;' : [ 'thick space' , '(5 mu)' ] , '\\quad' : [ '1 em space' , '(18 mu)' ] , '\\qquad' : [ '2 em space' , '(36 mu)' ] , '\\enskip' : [ '&#189; em space' , '(9 mu)' ] , '\\mp' : 'minus or plus' , '\\pm' : 'plus or minus' , '\\Im' : 'Imaginary part of' , '\\Re' : 'Real part of' , '\\gothicCapitalR' : 'Real part of' , '\\gothicCapitalI' : 'Imaginary part part of' , '\\differentialD' : 'differential d' , '\\aleph' : [ 'aleph' , 'infinite cardinal' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Cardinal_number">Wikipedia <big>&#x203A;</big></a>' , ] , '\\beth' : [ 'beth' , 'beth number' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Beth_number">Wikipedia <big>&#x203A;</big></a>' , ] , '\\gimel' : [ 'gimel' , 'gimel function' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Gimel_function">Wikipedia <big>&#x203A;</big></a>' , ] , '\\O' : 'empty set' , '\\N' : 'set of <br>natural numbers' , '\\Z' : 'set of <br>integers' , '\\Q' : 'set of <br>rational numbers' , '\\C' : 'set of <br>complex numbers' , '\\R' : 'set of <br>real numbers' , '\\P' : 'set of <br>prime numbers' , '\\lesseqqgtr' : 'less than, equal to or<br> greater than' , '\\gnapprox' : 'greater than and <br>not approximately' , '\\lnapprox' : 'lesser than and <br>not approximately' , '\\j' : 'dotless j' , '\\i' : 'dotless i' , '\\cdot' : 'centered dot' , '\\lmoustache' : 'left moustache' , '\\rmoustache' : 'right moustache' , '\\nabla' : [ 'nabla' , 'del' , 'differential vector operator' ] , '\\square' : [ 'square' , 'd’Alembert operator' , '<a target="_blank" href="https://en.wikipedia.org/wiki/D%27Alembert_operator">Wikipedia <big>&#x203A;</big></a>' , ] , '\\blacksquare' : [ 'black square' , 'end of proof' , 'tombstone' , 'Halmos symbol' , ] , '\\Box' : 'end of proof' , '\\colon' : [ 'such that' , 'ratio' ] , '\\coloneq' : [ 'is defined by' , 'is assigned' ] , '\\Colon' : [ 'is defined by' , 'as' ] , '\\_' : [ 'underbar' , 'underscore' ] , '\\ll' : 'much less than' , '\\gg' : 'much greater than' , '\\doteq' : 'approximately equal to' , '\\Doteq' : 'approximately equal to' , '\\doteqdot' : 'approximately equal to' , '\\cong' : [ 'isomorphism of' , '(for algebras, modules...)' ] , '\\det' : [ 'determinant of' , '(of a matrix)' ] , '\\dotplus' : 'Cartesian product algebra' , '\\otimes' : [ 'tensor product' , '(of algebras)' , 'Kronecker product' , '(of matrices)' , ] , '\\oplus' : [ 'direct sum' , '(of modules)' ] , '\\lb' : 'base-2 logarithm' , '\\lg' : 'base-10 logarithm' , '\\wp' : [ 'Weierstrass P' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Weierstrass%27s_elliptic_functions">Wikipedia <big>&#x203A;</big></a>' , ] , '\\wr' : [ 'wreath product' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Wreath_product">Wikipedia <big>&#x203A;</big></a>' , ] , '\\top' : [ 'tautology' , 'Proposition P is universally true' ] , '\\bot' : [ 'contradiction' , 'Proposition P is contradictory' ] , '\\mid' : [ 'probability' , 'of event A given B' ] , '\\mho' : [ 'Siemens' , 'electrical conductance in SI unit' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Siemens_(unit)">Wikipedia <big>&#x203A;</big></a>' , ] , '\\Longrightarrow' : 'implies' , '\\Longleftrightarrow' : 'if, and only if,' , '\\prec' : 'precedes' , '\\preceq' : 'precedes or is equal to' , '\\succ' : 'succeedes' , '\\succeq' : 'succeedes or is equal to' , '\\perp' : [ 'is perpendicular to' , 'is independent of' ] , '\\models' : [ 'entails' , 'double-turnstyle, models' , 'is a semantic consequence of' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Double_turnstile">Wikipedia <big>&#x203A;</big></a>' , ] , '\\vdash' : [ 'satisfies' , 'turnstyle, assertion sign' , 'syntactic inference' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Turnstile_(symbol)">Wikipedia <big>&#x203A;</big></a>' , ] , '\\implies' : [ 'implies' , 'logical consequence' ] , '\\impliedby' : [ 'implied by' , 'logical consequence' ] , '\\surd' : [ 'surd' , 'root of' , 'checkmark' ] , '\\ltimes' : [ 'semi direct product' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Semidirect_product">Wikipedia <big>&#x203A;</big></a>' , ] , '\\rtimes' : [ 'semi direct product' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Semidirect_product">Wikipedia <big>&#x203A;</big></a>' , ] , '\\leftthreetimes' : [ 'semi direct product' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Semidirect_product">Wikipedia <big>&#x203A;</big></a>' , ] , '\\rightthreetimes' : [ 'semi direct product' , '<a target="_blank" href="https://en.wikipedia.org/wiki/Semidirect_product">Wikipedia <big>&#x203A;</big></a>' , ] , '\\divideontimes' : [ 'divide on times' ] , '\\curlywedge' : 'nor' , '\\curlyvee' : 'nand' , '\\simeq' : 'is group isomorphic with' , '\\vartriangleleft' : [ 'is a normal subgroup of' , 'is an ideal ring of' ] , '\\circ' : [ 'circle' , 'ring' , 'function composition' ] , '\\rlap' : [ 'overlap right' , '\\rlap{x}o' ] , '\\llap' : [ 'overlap left' , 'o\\llap{/}' ] , '\\colorbox' : [ 'color box' , '\\colorbox{#fbc0bd}{...}' ] , '\\ast' : [ 'asterisk' , 'reflexive closure (as a superscript)' ] , '\\bullet' : 'bullet' , '\\lim' : 'limit' , } ; function getNote ( symbol ) { let result = NOTES [ symbol ] ?? '' ; if ( isArray ( result ) ) { result = result . join ( '<br>' ) ; } return result ; } function latexToMarkup ( latex ) { const root = new Atom ( 'root' , { mode : 'math' } ) ; root . body = typeset ( parseLatex ( latex , { parseMode : 'math' , macros : getMacros ( ) , registers : getDefaultRegisters ( ) , } ) ) ; const box = coalesce ( adjustInterAtomSpacing ( new Box ( root . render ( new Context ( { macros : getMacros ( ) , registers : getDefaultRegisters ( ) , smartFence : false , } , { fontSize : DEFAULT_FONT_SIZE , } , 'displaystyle' ) ) , { classes : 'ML__base' } ) ) ) ; return makeStruts ( box , { classes : 'ML__mathlive' } ) . toMarkup ( ) ; } export function showPopoverWithLatex ( mf , latex , displayArrows ) { if ( ! latex || latex . length === 0 ) { hidePopover ( mf ) ; return ; } const command = latex ; const commandMarkup = latexToMarkup ( latex ) ; const commandNote = getNote ( command ) ; const keybinding = getKeybindingsForCommand ( mf . keybindings , command ) . join ( '<br>' ) ; let template = displayArrows ? '<div class="ML__popover__prev-shortcut" role="button" aria-label="Previous suggestion"><span><span>&#x25B2;</span></span></div>' : '' ; template += '<span class="ML__popover__content" role="button">' ; template += '<div class="ML__popover__command">' + commandMarkup + '</div>' ; if ( commandNote ) { template += '<div class="ML__popover__note">' + commandNote + '</div>' ; } if ( keybinding ) { template += '<div class="ML__popover__shortcut">' + keybinding + '</div>' ; } template += '</span>' ; template += displayArrows ? '<div class="ML__popover__next-shortcut" role="button" aria-label="Next suggestion"><span><span>&#x25BC;</span></span></div>' : '' ; mf . popover = createPopover ( mf , template ) ; let element = mf . popover . querySelectorAll < HTMLElement > ( '.ML__popover__content' ) ; if ( element && element . length > 0 ) { attachButtonHandlers ( ( command ) => mf . executeCommand ( command ) , element [ 0 ] , { default : [ 'complete' , 'accept-suggestion' ] , } ) ; } element = mf . popover . querySelectorAll ( '.ML__popover__prev-shortcut' ) ; if ( element && element . length > 0 ) { attachButtonHandlers ( ( command ) => mf . executeCommand ( command ) , element [ 0 ] , 'previousSuggestion' ) ; } element = mf . popover . querySelectorAll ( '.ML__popover__next-shortcut' ) ; if ( element && element . length > 0 ) { attachButtonHandlers ( ( command ) => mf . executeCommand ( command ) , element [ 0 ] , 'nextSuggestion' ) ; } setTimeout ( ( ) => { const caretPoint = getCaretPoint ( mf . field ! ) ; if ( caretPoint ) setPopoverPosition ( mf , caretPoint ) ; if ( mf . popover ) { mf . popover . classList . add ( 'is-visible' ) ; mf . popoverVisible = true ; } } , 32 ) ; } export function updatePopoverPosition ( mf , options ? ) { if ( ! mf . element || mf . element . mathfield !== mf ) return ; if ( ! mf . popover || ! mf . popoverVisible ) return ; if ( options ?. deferred ) { setTimeout ( ( ) => updatePopoverPosition ( mf ) , 100 ) ; return ; } if ( mf . model . at ( mf . model . position ) ?. type !== 'latex' ) { hidePopover ( mf ) ; } else { const caretPoint = getCaretPoint ( mf . field ! ) ; if ( caretPoint ) setPopoverPosition ( mf , caretPoint ) ; } } function setPopoverPosition ( mf , position ) { throwIfNotInBrowser ( ) ; if ( ! mf . popover || ! mf . popoverVisible ) return ; const screenHeight = window . innerHeight || document . documentElement . clientHeight || document . body . clientHeight ; const screenWidth = window . innerWidth || document . documentElement . clientWidth || document . body . clientWidth ; const scrollbarWidth = window . innerWidth - document . documentElement . clientWidth ; const scrollbarHeight = window . innerHeight - document . documentElement . clientHeight ; const virtualkeyboardHeight = mf . virtualKeyboard ! . height ; if ( position . x + mf . popover . offsetWidth / 2 > screenWidth - scrollbarWidth ) { mf . popover . style . left = `${ screenWidth - mf . popover . offsetWidth - scrollbarWidth }px` ; } else if ( position . x - mf . popover . offsetWidth / 2 < 0 ) { mf . popover . style . left = '0' ; } else { mf . popover . style . left = `${ position . x - mf . popover . offsetWidth / 2 }px` ; } if ( position . y + mf . popover . offsetHeight + 5 > screenHeight - scrollbarHeight - virtualkeyboardHeight ) { mf . popover . classList . add ( 'ML__popover--reverse-direction' ) ; mf . popover . style . top = `${ position . y - position . height - mf . popover . offsetHeight - 5 }px` ; } else { mf . popover . classList . remove ( 'ML__popover--reverse-direction' ) ; mf . popover . style . top = `${ position . y + 5 }px` ; } } export function hidePopover ( mf ) { mf . suggestionIndex = 0 ; mf . popoverVisible = false ; if ( mf . popover ) { mf . popover . classList . remove ( 'is-visible' ) ; mf . popover . innerHTML = '' ; } } export function createPopover ( mf , html ) { if ( mf . popover ) { mf . popover . innerHTML = mf . options . createHTML ( html ) ; return mf . popover ; } mf . popover = getSharedElement ( 'mathlive-popover-panel' ) ; if ( POPOVER_STYLESHEET_HASH === undefined ) { POPOVER_STYLESHEET_HASH = hashCode ( POPOVER_STYLESHEET ) . toString ( 36 ) ; } gPopoverStylesheet = injectStylesheet ( null , POPOVER_STYLESHEET , POPOVER_STYLESHEET_HASH ) ; gCoreStylesheet = injectStylesheet ( null , CORE_STYLESHEET , hashCode ( CORE_STYLESHEET ) . toString ( 36 ) ) ; mf . popover . innerHTML = mf . options . createHTML ( html ) ; return mf . popover ; } export function disposePopover ( mf ) { releaseSharedElement ( mf . popover ) ; if ( gPopoverStylesheet ) gPopoverStylesheet . release ( ) ; if ( gCoreStylesheet ) gCoreStylesheet . release ( ) ; delete mf . popover ; }