lib/mathquill/loadMathquill.js

/** @module loadMathquill */
/* global MathQuill */

let loadedPromise = null

// pour un rendu auto au chargement cf les versions de ce fichier avant 2020-04-10 (fct renderAll)

/**
 * Charge jQuery et les css mathquill en parallèle, met jQuery en global puis charge mathquill
 * @return {Promise}
 */
export default async function loadMathquill () {
  // si on est appelé plusieurs fois on ne relance pas le chargement
  if (loadedPromise) return loadedPromise
  // on lance ça en parallèle, mais juste après le js
  let parallelCssLoadingPromise
  setTimeout(() => {
    parallelCssLoadingPromise = Promise.all([
      import('./surcharge.scss'),
      import('../../vendors/mathquill/0.10.1/mathquill.css')
    ])
  }, 0)
  const { default: jQuery } = await import('jquery')
  // mathquill veut un jQuery en global
  window.jQuery = jQuery
  await import('../../vendors/mathquill/0.10.1/mathquill.js')
  // ici on pourrait ajouter une config, cf http://docs.mathquill.com/en/latest/Config/
  // on recrée l’ancien $.mathquill() d’après le code de legacy/externals/mathquill/mathquill_maj.js
  // https://github.com/mathquill/mathquill/wiki/v0.9.x-%E2%86%92-v0.10.0-Migration-Guide
  window.jQuery.fn.mathquill = function mathquill (cmd, latex) {
    const $elt = this // élément jQueryfié sur lequel on est appelé
    // console.log('** mathquill avec les arguments', arguments, 'sur', $elt)
    const MQ = MathQuill.getInterface(2)

    // appel de $(…).mathquill()
    if (arguments.length === 0) return MQ.MathField($elt[0])
    const hasMultipleArguments = arguments.length > 1

    if (arguments.length > 1 && typeof latex !== 'string') {
      if (typeof latex === 'number') {
        console.warn(Error(`Appel invalide de mathquill('${cmd}', latex) où latex est un number (il a été converti en string mais devrait l’être avant l’appel)`))
        latex = String(latex)
      } else {
        return console.error(Error(`Appel invalide de mathquill('${cmd}', latex) où latex n’est pas une string (${typeof latex})`))
      }
    }

    // y’a des arguments, et si latex est fourni c’est une string, et on fait ces modifs,
    // cf breaking changes de https://github.com/mathquill/mathquill/wiki/v0.9.x-%E2%86%92-v0.10.0-Migration-Guide
    if (latex) latex = latex.replace(/\\a((?:sin|cos|tan|sec|csc|cosec|cot|cotan)h?)\b/g, '\\arc$1')

    // reste à gérer la migration de ces commandes d’après
    // https://github.com/mathquill/mathquill/wiki/v0.9.x-%E2%86%92-v0.10.0-Migration-Guide
    const mathField = MQ.MathField($elt[0])
    if (!mathField) {
      console.error(Error('Aucun champ math trouvé dans'), $elt)
      return $elt
    }

    switch (cmd) {
      case 'cmd':
        // http://docs.mathquill.com/en/latest/Api_Methods/#cmdlatex_string
        // if (latex) $elt.cmd(latex)
        if (latex) mathField.cmd(latex)
        else console.error(Error('appel de matquill("cmd") sans 2e argument'))
        return $elt

      case 'editable':
        return mathField

      case 'latex': {
        // http://docs.mathquill.com/en/latest/Api_Methods/#latex
        // pas sûr que lui passer undefined revienne à ne pas lui passer d’argument (on sait pas s’il regarde arguments.length)
        // dans le doute on distingue
        if (hasMultipleArguments) {
          // deux arguments, on affecte
          mathField.latex(latex)
          // et on retourne l’élément pour pouvoir chaîner
          return $elt
        }
        // un seul argument, on veut récupérer le contenu et on le retourne
        const isEditable = $elt.hasClass('mq-editable-field')
        const res = mathField.latex() // cette commande ajoute la classe mq-editable-field
        // donc faut la virer si elle n’y était pas avant
        if (!isEditable) $elt.removeClass('mq-editable-field')
        return res
      }

      case 'focus':
        mathField.focus()
        return $elt

      case 'blur':
        mathField.blur()
        return $elt

      case 'redraw':
        // http://docs.mathquill.com/en/latest/Api_Methods/#reflow
        mathField.reflow()
        return $elt

      case 'revert':
        // http://docs.mathquill.com/en/latest/Api_Methods/#revert
        // Problème avec certaines sections de Rémi. Je mets un try catch en attendant
        // mathField.revert()
        $elt.removeClass('mq-editable-field')
        return $elt

      case 'write':
        if (latex) mathField.write(latex)
        else console.error(Error('appel de matquill("write") sans 2e argument'))
        return $elt

      case 'typedText' :
        if (latex) mathField.typedText(latex)
        else console.error(Error('appel de matquill("typedText") sans 2e argument'))
        return $elt

      case 'keystroke' :
        if (latex) mathField.keystroke(latex)
        else console.error(Error('appel de matquill("keystroke") sans 2e argument'))
        return $elt

      default:
        console.warn(`Commande mathquill ${cmd} non implémentée`)
        return $elt
    }
  }
  // On redéfinit la $.focus pour que, si elle est appelée sur un champ editable
  // elle utilise notre code (et sinon l’original)
  const oldFocus = window.jQuery.fn.focus
  window.jQuery.fn.focus = function focusMod () {
    const $elt = this
    if ($elt.hasClass('mq-editable-field')) {
      const MQ = MathQuill.getInterface(2)
      const mathField = MQ.MathField($elt[0])
      mathField.focus()
      return $elt
    }
    // c’est pas un champ éditable, on applique l’original
    return oldFocus.apply(this, arguments)
  }
  // on résoud l’ensemble quand les css sont complètement chargées
  await parallelCssLoadingPromise
  // on stocke une promesse résolue pour les éventuels appels suivants
  loadedPromise = Promise.resolve()
} // loadMathquill