/** @module mqFunctions */
// les fonctions mathquill qui étaient dans J3Poutils, mises ici car écrites différemment pour mathquill 0.10
// il s’agit des fcts entre sesaparcours_drop_start_mathquill et sesaparcours_drop_stop_mathquill
// elles sont renommées au passage pour harmoniser, préfixe mq et camelCase
import $ from 'jquery'
import Tarbre from 'src/legacy/outils/calculatrice/Tarbre'
import { _clonePropsCleanStyle, j3pAddContent, j3pArrondi, j3pChaine, j3pElement, j3pEnsureHtmlElement, j3pGetCssProp, j3pGetNewId, j3pGetRandomInt, j3pIsHtmlElement, j3pPGCD, j3pRemplace, j3pRestrict, j3pShowError, j3pVirgule } from 'src/legacy/core/functions'
import { notify } from 'sesajs/error'
import loadMathquill from 'src/lib/mathquill/loadMathquill'
import { escHtml, resumeHtml } from 'src/lib/outils/mathlive/internals'
import { convertRestriction } from 'src/lib/utils/regexp'
import { getIndexFermant } from 'sesajs/string'
import MqVirtualKeyboard from 'src/lib/widgets/mqVirtualKeyboard/MqVirtualKeyboard'
import { renderMathInElement } from 'mathlive'
import { addElement, isDomElement } from 'sesajs/dom'
const maxLatexCommands = 100
/**
* Exécute cb en sync si mathquill est chargé, en async après chargement sinon
* @param {function} cb
*/
export function lazyMqProcess (cb) {
if (typeof window.jQuery.fn.mathquill === 'function') {
cb()
return
}
// faut charger
loadMathquill()
.then(cb)
.catch(console.error)
}
/**
* Assure le rendu latex StaticMath dans elt
* Si MathQuill n'a pas fini de charger on attend que ça se termine
* @param elt
*/
function renderStaticMath (elt) {
if (window.MathQuill) {
// MathQuill existe, on peut faire le rendu
const MQ = window.MathQuill.getInterface(2)
MQ.StaticMath(elt).latex()
} else {
// faut attendre qu'il soit chargé
loadMathquill()
.then(() => {
if (!window.MathQuill) throw Error('Le chargement de Mathquill a échoué')
// on peut se rappeler
renderStaticMath(elt)
})
.catch(j3pShowError)
}
}
/**
* Limite la taille des inputMQ
* Attention, MathQuill doit exister avant d'appeler cette fonction
* @param event
*/
function restricTaille (event) {
let el = event.target
while (el.getAttribute('marqueur') !== 'MARK') {
el = el.parentNode
}
const malarg = Number(el.getAttribute('mqlargeur'))
const mahaut = Number(el.getAttribute('mqhauteur'))
const malength = Number(el.getAttribute('mqlength'))
/// /ici j’ai ptet oublie des codes à laisser passer
if ((event.keyCode !== 13) && (event.keyCode !== 9) && (event.keyCode !== 8) && ((malarg && el.offsetWidth > malarg) || (mahaut && el.offsetHeight > mahaut) || (malength && $(j3pElement(el.id)).mathquill('latex').length > malength - 1))) {
event.stopPropagation()
event.preventDefault()
}
}
/**
* ex fct j3pInputDyn3, pour redimensionner l’input à chaque fois qu’il change de contenu
* Changer ça pour imposer une police à taille fixe dans les input pour éviter tous ces calculs compliqués à chaque frappe de clavier ?
* (il suffirait alors d’imposer input.style.width = (input.value.length + 1) + 'ch')
* @param {InputEvent} event
* @private
*/
export function inputAutoSizeListener (event) {
const input = event.currentTarget
resetInputSize(input)
}
export function resetInputSize (input) {
const { fontSize, fontFamily } = getComputedStyle(input)
const texteZone = input.value.replace(/ /g, '.')
const newTailleZone = Math.max($(input).textWidth(texteZone, fontFamily, fontSize), 5)
const maxLength = Number(input.maxLength)
if (maxLength === -1 || (maxLength > 0 && input.value.length <= maxLength)) input.style.width = (newTailleZone + 14) + 'px'
}
/**
* Retourne l’input jQueryfié s’il est valide
* Par valide on entend moins de maxLatexCommands commandes,
* ça protège du plantage de la commande latex avec too much recursion
* https://app.bugsnag.com/sesamath/j3p/errors/61443bf2e910da0007834943
* mais seulement pour ce qui passe par les fcts définies ci-dessous (utilisées par les boutons).
* Si on voulait protéger l’input aussi sur les saisies clavier (rester longtemps avec la touche / enfoncée par ex)
* il faudrait mettre un listener sur chaque input dans j3pAffiche (et chez tous ceux qui créent de l’input mathquill).
* C’est lourd, on verra si ça devient vraiment important.
* @private
* @param {HTMLElement|string} elt L’élément ou son id
* @return {jQuery|void} Le même jQueryfié, s’il n’a pas plus de maxLatexCommands (undefined sinon)
*/
function getInput (elt) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
// on pourrait compter le nb de \ dans le latex (cf commit 880a38c6),
// mais ce serait plus lourd (il faut appeler la commande latex et envelopper tout ça dans un try/catch au cas où elle planterait
// à priori c’est un span.mq-non-leaf par commande (par ex sur une fraction y’en a qu’un qui contient plein d’autres span)
if (elt.querySelectorAll('span.mq-non-leaf').length < maxLatexCommands) return $(elt)
j3pShowError(Error('Écriture trop complexe, il faut simplifier cette formule'))
}
/**
* Retourne la string latex d’un input mathquill (la passer à unLatexify pour la nettoyer)
* @param {HTMLElement} inputMq
* @returns {string}
*/
export function getMqValue (inputMq) {
let latex = $(inputMq).mathquill('latex').trim()
// on vire d’éventuels x^{^3} qui résultent d’un double clic sur x^y (mais ne se voit pas à l’écran)
const reg = /\^ *{\^([^}]*)} */
while (reg.test(latex)) latex = latex.replace(reg, '^$1')
// On remplace les ^ suivi de deux chiffres en rajoutant une multiplication après le premier chiffre
// car cela n’arrive que lorsqu’on tape quelquechose du type 2 exposant 3 suivi de 4 où mathquill
// revoie 2^34 alors que si on avait entré 2 exposant 34 il aurait renvoyé 2^{34}
return latex.replace(/\^(\d)(\d)/g, '^$1\\times$2')
}
/**
* Retourne true si elt est un input Mathquill (mais pas forcément dans le dom)
* @param elt
* @returns {boolean}
*/
export function isInputMathquill (elt) {
return isDomElement(elt) && elt.classList.includes('mq-editable-field')
}
/**
* Ajoute une fonction dans l’input avec la parenthèse ouvrante
* @param {string} cmd
* @param {HTMLElement} elt L’input mathquill
* @param {boolean} focus
*/
function mqAjouteFct (cmd, elt, focus) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
if (elt.tagName.toLowerCase() === 'math-field') {
elt.executeCommand(['insert', cmd])
// renderMathInElement('expressioninputmq1')
if (focus) elt.focus()
} else {
const $input = getInput(elt)
if (!$input) return
if (focus) $input.mathquill('focus')
// SetTimeout nécessaire pour que la parenthèse qui s’ouvre ne se referme pas en fin
// d’expression quand on tape une parenthèse fermante
setTimeout(() => {
$input.mathquill('cmd', cmd)
$input.mathquill('typedText', '(')
})
}
}
/**
* Ajoute la commande mathquill à l’input mathquill
* @param {string} cmd Chaîne qui sera affichée telle quelle dans l’input
* @param {string|HTMLElement} elt L’input mathquill ou son id
* @param {boolean} [focus=true] passer false pour ne pas donner le focus à l’input ajouté
*/
export function mqAjoute (cmd, elt, focus = true) {
// Modifié par Yves pour s’adapter à mathlive
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
if (elt.tagName.toLowerCase() === 'math-field') {
elt.executeCommand(['insert', cmd])
renderMathInElement(elt, { renderAccessibleContent: false })
if (focus) elt.focus()
} else {
const $input = getInput(elt)
if (!$input) return
$input.mathquill('cmd', cmd)
if (focus) $input.mathquill('focus')
}
}
export function mqAjouteLn (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\ln\\left(#0\\right)' : '\\ln'
mqAjouteFct(cmd, elt, focus)
}
export function mqAjouteLog (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\log\\left(#0\\right)' : '\\log'
mqAjouteFct(cmd, elt, focus)
}
export function mqAjouteSin (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\sin\\left(#0\\right)' : '\\sin'
mqAjouteFct(cmd, elt, focus)
}
export function mqAjouteCos (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\cos\\left(#0\\right)' : '\\cos'
mqAjouteFct(cmd, elt, focus)
}
export function mqAjouteTan (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\tan\\left(#0\\right)' : '\\tan'
mqAjouteFct(cmd, elt, focus)
}
export function mqAjouteUn (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
if (elt.tagName.toLowerCase() === 'math-field') {
const cmd = 'u_{n}'
mqAjoute(cmd, elt, focus)
} else {
const $input = getInput(elt)
if (!$input) return
$input.mathquill('write', 'u_n')
if (focus) $input.mathquill('focus')
}
}
export function mqAjouteN (elt, focus = true) {
mqAjoute('n', elt, focus)
}
export function mqAjouteR (elt, focus = true) {
mqAjoute('\\R', elt, focus)
}
export function mqAjouteSetMinus (elt, focus = true) {
mqAjoute('\\setminus', elt, focus)
}
export function mqAjoutePuissance (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '^{#?}' : '^'
mqAjoute(cmd, elt, focus)
}
export function mqAjouteIndice (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '_{#0}' : '_'
mqAjoute(cmd, elt, focus)
}
export function mqAjouteRacine (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
let cmd = '\\sqrt'
cmd += elt.tagName.toLowerCase() === 'math-field' ? '{#0}' : ''
mqAjoute(cmd, elt, focus)
}
export function mqAjouteAbs (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
if (elt.tagName.toLowerCase() === 'math-field') {
const cmd = '\\left| #0 \\right|'
mqAjoute(cmd, elt, focus)
} else {
const $input = getInput(elt)
if (!$input) return
$input.mathquill('typedText', '|')
if (focus) $input.focus()
}
}
export function mqAjouteInteg (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
// Pour mathlive on met des doubles accolades car sinon dans le cas d’un seul caractère il n’est plus
// entouré de {}
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\int_{{#0}}^{{#?}}{#?}\\,\\differentialD' : 'integ'
mqAjoute(cmd, elt, focus)
}
export function mqAjouteBracket (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\left\\{ #0 \\right\\}' : 'bracket'
mqAjoute(cmd, elt, focus)
}
export function mqAjoutePrim (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
// Pour mathlive on met des doubles accolades pour mieux isoler les éléments
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\left[{#0}\\right]_{{#?}}^{{#?}}' : 'prim'
mqAjoute(cmd, elt, focus)
}
export function mqAjouteDifferential (elt, focus = true) {
mqAjoute('\\differentialD', elt, focus)
}
export function mqAjouteVecteur (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
const cmd = elt.tagName.toLowerCase() === 'math-field' ? '\\overrightarrow {#0}' : '\\vecteur'
mqAjoute(cmd, elt, focus)
}
export function mqAjouteProsca (elt, focus = true) {
mqAjoute('\\cdot', elt, focus)
}
export function mqAjouteFraction (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
let cmd = '\\frac'
cmd += elt.tagName.toLowerCase() === 'math-field' ? '{#0}{#?}' : ''
mqAjoute(cmd, elt, focus)
}
export function mqAjoutePi (elt, focus = true) {
mqAjoute('\\pi', elt, focus)
}
export function mqAjouteExp (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
if (elt.tagName.toLowerCase() === 'math-field') {
mqAjoute('e^{#0}', elt, focus)
} else {
const $input = getInput(elt)
if (!$input) return
$input.mathquill('write', 'e').mathquill('cmd', '^')
if (focus) $input.mathquill('focus')
}
}
export function mqAjouteInf (elt, focus = true) {
mqAjoute('\\infty', elt, focus)
}
export function mqAjouteInter (elt, focus = true) {
mqAjoute('\\cap', elt, focus)
}
export function mqAjouteUnion (elt, focus = true) {
mqAjoute('\\cup', elt, focus)
}
export function mqAjouteVide (elt, focus = true) {
mqAjoute('\\emptyset', elt, focus)
}
export function mqAjouteInfEgal (elt, focus = true) {
mqAjoute('\\leq', elt, focus)
}
export function mqAjouteSupEgal (elt, focus = true) {
mqAjoute('\\geq', elt, focus)
}
export function mqAjouteEquivaut (elt, focus = true) {
mqAjoute('\\iff', elt, focus)
}
export function mqAjouteBarre (elt, focus = true) {
const $input = getInput(elt)
if (!$input) return // on a déjà râlé
const texte = $input.mathquill('latex')
if (!texte) {
// le texte est vide donc on met juste la barre
$input.mathquill('cmd', '\\overline')
if (focus) $input.mathquill('focus')
} else {
const overlineRegExp = /^\\overline{\w+}$/g
if (overlineRegExp.test(texte)) {
// C’est que l’événement proposé est déjà un evt contraire, donc on repasse à l’evt
const newTexte = texte.substring(10, texte.length - 1)
$input.mathquill('latex', '').mathquill('write', newTexte)
if (focus) $input.mathquill('focus')
} else {
if (/^\\overline{.+}$/g.test(texte)) {
if (/\\c[au]p}$/g.test(texte)) {
// On est avec un truc de la forme \\overline{... cup} donc dans ce cas, il convient d’avoir \\overline{... cup \\overline{}}
$input.mathquill('latex', '')
const newTexte = texte.substring(10, texte.length - 1)
$input.mathquill('cmd', '\\overline').mathquill('write', newTexte + '\\overline{}')
} else {
// Dans la zone, on a le contraire de quelque chose d’un peu plus complet, mais qui n’a pas d’union ou d’intersection
// On vire alors la barre
const newTexte = texte.substring(10, texte.length - 1)
$input.mathquill('latex', '').mathquill('write', newTexte)
}
} else {
// Par exemple pour écrire A\\cap \\overline{B} ou \\overline{A} \\cap \\overline{B}, on doit pouvoir mettre un evt contraire ce qui n’avait pas été prévu
// if (/^\w+$/g.test(texte)) {
if (/^\w+$/g.test(texte) || /^\w+\{[a-z+\-0-9]+}$/g.test(texte)) {
// Le premier capture un événement simple ou écrit de la forme E_n mais E_{n+1} ne passe pas d’où le 2nd
// on passe au contraire de ce qui est affiché
$input.mathquill('latex', '')
$input.mathquill('cmd', '\\overline').mathquill('write', texte)
} else {
// on laisse ce qui est déjà écrit et on met une barre pour ce qui va suivre
$input.mathquill('latex', '')
$input.mathquill('write', texte).mathquill('cmd', '\\overline')
}
}
if (focus) $input.mathquill('focus')
}
}
}
export function mqAjouteConj (elt, focus = true) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('élément invalide'), elt)
if (elt.tagName.toLowerCase() === 'math-field') {
mqAjoute('\\overline{#0}', elt, focus)
} else {
mqAjoute('\\overline', elt, focus)
}
}
export function mqAjouteSigma (elt, focus = true) {
mqAjoute('\\sigma', elt, focus)
}
export function mqAjouteKparmiN (elt, focus = true) {
mqAjoute('\\binom', elt, focus)
}
/**
* La liste des commandes mathquill connues
* (clé nom de la commande et valeur la fonction, utile lorsque la commande est une variable)
*/
export const mqCommandes = {
racine: mqAjouteRacine,
vecteur: mqAjouteVecteur,
prosca: mqAjouteProsca,
fraction: mqAjouteFraction,
pi: mqAjoutePi,
exp: mqAjouteExp,
puissance: mqAjoutePuissance,
inf: mqAjouteInf,
inter: mqAjouteInter,
union: mqAjouteUnion,
vide: mqAjouteVide,
Un: mqAjouteUn,
n: mqAjouteN,
R: mqAjouteR,
setminus: mqAjouteSetMinus,
infegal: mqAjouteInfEgal,
supegal: mqAjouteSupEgal,
equivaut: mqAjouteEquivaut,
indice: mqAjouteIndice,
barre: mqAjouteBarre,
conj: mqAjouteConj,
sigma: mqAjouteSigma,
k_parmi_n: mqAjouteKparmiN,
ln: mqAjouteLn,
log: mqAjouteLog,
sin: mqAjouteSin,
cos: mqAjouteCos,
tan: mqAjouteTan,
abs: mqAjouteAbs,
integ: mqAjouteInteg,
bracket: mqAjouteBracket,
prim: mqAjoutePrim,
differential: mqAjouteDifferential
}
// Les séparateurs de zones à substituer (heureusement qu’on a que 4 types :-D)
const charsSpe = {
$: 'mq',
'@': 'input',
'&': 'mqedit',
'#': 'liste'
}
/**
* renvoie le premier caractere special de la chaîne ou ''
* @param {string} ch
* @returns {string}
*/
function getFirstCharSpe (ch) {
let min = ch.length
let retour = ''
let pos
for (const char in charsSpe) {
pos = ch.indexOf(char)
if (pos !== -1 && pos < min) {
min = pos
retour = char
}
}
return retour
}
const escLt = ch => ch.replace(/</g, '<')
/**
* Le retour d’un j3pAffiche, avec le span parent et les listes d’éléments mis dedans (en général une seule liste est non vide)
* @typedef ResultatAffichage
* @property {HTMLSpanElement} parent
* @property {HTMLElement[]} inputList liste d’input créés par les @N@
* @property {HTMLElement[]} inputmqList input mathquill créés par les &N&
* @property {HTMLElement[]} inputmqefList les input mathquill créés avec du \editable{} dans le latex fourni
* @property {HTMLElement[]} mqList liste de span mathquill créés par les $text$
* @property {HTMLElement[]} selectList liste de select créées par #N# (parcourir sa propriété childNodes pour avoir les options)
*/
/**
* Affiche contenu dans conteneur (créera un span du contenu), avec bcp de traitements !!!
* contenu est en effet traitée pour
* - substituer les £x ou £{x}
* - gérer du $code LaTeX$
* - gérer du contenu mathquill substituable
* - gérer des input mathquill
* - gérer de la substitution dynamique (les valeurs à substituer peuvent contenir des opérations comme calcule[…], signe[…], pgcd[…]
* Voir le détail sur {@tutorial paramContent}
* Si vous voulez simplement afficher une chaîne sans avoir besoin de traitement particulier, utiliser j3pAddElt(conteneur, options)
* @param {HTMLElement|string} conteneur
* @param {string|null} [id] si fourni sera mis sur le span qu’on va créer dans conteneur
* @param {string} contenu Le texte à afficher (les caractères spéciaux $@&# seront interprétés à la mode j3p :
* $\\frac{3}{2}$ : affiché comme du LaTeX
* @N@ : input mathquill n°N, à documenter (options.inputN a bcp de propriétés possibles)
* &N& : zone d’édition mathquill n°N
* #???# : pour insérer un <select> @todo à documenter
* Si contenu contient ou < ou > c’est géré
* mais ça ne tolèrera pas d’autres htmlEntities de cette forme)
* @param {Object} [options={}]
* @param {Object} [options.style] Objet style qui sera passé au span, pour ceux qui préfèrent style.fontSize (en string avec unité px ou autre) plutôt que styletexte.taillepolice (number sans unité)
* @param {Object} [options.styletexte] des infos pour le style à mettre sur le span qu’on va créer autour du contenu
* @param {string} [options.styletexte.couleur]
* @param {number} [options.styletexte.taillepolice]
* @param {string|number} [options.x] pour le remplacement d’un £x ou £{x} qui serait dans contenu
* @param {object} [options.inputN] pour les @N@ @todo à documenter
* @param {object} [options.inputmqN] pour les &N& (édition mathquill) @todo à documenter
* @param {object} [options.listeN] pour les #N# (le <select>) @todo à documenter
* @param {string[]} [options.listeN] les <option> mises dans le <select> précédent
* @param {string} [options.tag=span] Le tag à créer pour y mettre le contenu
* @param {Object} [position] Si fourni, doit avoir les propriétés top & left en number (sinon ce sera ignoré)
* @param {number} [position.top]
* @param {number} [position.left]
* @return {ResultatAffichage} Les éléments créés dans le DOM, parent est le span englobant, les autres propriétés sont toutes des array d’éléments (ces listes sont donc souvent vides), mqList pour les span créés par $text$, inputList pour les input créés par @N@, selectList pour les listes créées par #N# (parcourir sa propriété childNodes pour avoir les options), inputmqList pour les input mathquill créés avec &N& et inputmqefList pour les champs éditables créés par mathquill si y’a du \editable{} dans le latex fourni
*/
export function j3pAffiche (conteneur, id, contenu, options, position) {
if (!options || typeof options !== 'object') options = {}
conteneur = j3pEnsureHtmlElement(conteneur)
if (typeof contenu !== 'string') {
if (typeof contenu !== 'number') {
console.error(Error(`escHtml ne prend que des string ! (${typeof contenu} fourni : ${contenu})`))
}
contenu = String(contenu)
}
// Réglage du style du span, on check et clone au passage
const props = _clonePropsCleanStyle({ style: options.style }, { addEmpty: true })
// ajout de class si passée en option (fait par Jean-Claude Lhote le 26/05/2023 pour faciliter la localisation de l’élément lors des tests.)
if (options.className) props.className = options.className
// si y’a un id on le met
if (id && typeof id === 'string') {
// on s’assure qu’il n’y a pas déjà un élément avec cet id (et si jamais y’en avait un ça va râler en console et lui ajouter un suffixe numérique)
props.id = j3pGetNewId(id, true)
} else {
// sinon on en génère un car il sert ensuite de préfixe un peu partout
id = j3pGetNewId('affiche')
}
// console.log(`j3pAffiche avec id ${id} et contenu :`, contenu)
if (typeof options.styletexte === 'object') {
// ne contient normalement que les propriétés couleur et taillepolice, mais parfois on nous passe un objet plus complet (par ex j3p.styles.moyen.enonce)
props.style = Object.assign({}, options.styletexte, options.style)
if (typeof options.styletexte.couleur === 'string') {
props.style.color = options.styletexte.couleur
delete props.style.couleur
}
if (typeof options.styletexte.taillepolice === 'number') {
props.style.fontSize = options.styletexte.taillepolice + 'px'
delete props.style.taillepolice
}
}
// 5ème argument de positionnement ?
if (typeof position === 'object' && typeof position.top === 'number' && typeof position.left === 'number') {
props.style.position = 'absolute'
props.style.top = position.top + 'px'
props.style.left = position.left + 'px'
}
// @todo revoir l’usine à gaz qui suit, et ajouter les éléments dans span avec du j3pAddElt
// plutôt que de concaténer une grosse string qui finit en innerHTML
const mqrestric = []// pour le restrict mq
const mqlargeur = []
const mqhauteur = []
const mqlength = []
contenu = escHtml(contenu)
// const hasCharSpe = RegExp('[' + Object.keys(charsSpe).join('') + ']').test(contenu)
// Il faut mettre cet id dans le DOM pour qu’il existe et qu’au prochain appel de
// J3PAffiche sans id il ne choisisse pas le même, sinon on risque des collisions sur les ids générés à partir de celui-ci
// (par ex avec affiche1 mathquill va créé un #affiche10, mais si on ne met pas de #affiche1 dans le dom
// et qu’au prochain appel y’a aussi du mathquill ça fera un 2e #affiche10)
// On colle donc cet id sur le span, même si on s’en servira jamais, juste pour occuper le terrain
props.id = id
const tag = options.tag ?? 'span'
const mainElt = addElement(conteneur, tag, props)
// le html qui sera mis dans le span précédent
let html = ''
// les ids dont il faudra récupérer les éléments pour les retourner, quand ils existeront
const ids = { mqList: [], inputList: [], selectList: [], inputmqList: [] }
// La chaine est découpée par paquets de 2 : expression constante (éventuellement vide) suivie d’une expression spéciale
// l’expression constante est un des caractères de charsSpe
const table = []
// ce tableau contient toutes les sous-chaînes
// table[0] = constante (éventuellement vide) , table[1] expression spéciale
// table[2k] = constante (éventuellement vide) , table[2k+1] expression spéciale
const typetable = []
// typetable[k+1] contient le type de l’expression spéciale
const numerotable = []
// contient les numéros des ID
// par exemple si on a &2_\\frac{3}{2}& (inputmathquill) alors l’id sera id+"inputmq"+numerotable[k+1]
// Recherche du premier caractère spécial contenu dans le contenu
let exp1, exp2, position1, position2
// on remplace les £x, £{truc} et £truc par leurs valeurs dans les options
contenu = escHtml(j3pChaine(contenu, options))
let charSpe = getFirstCharSpe(contenu)
while (charSpe) {
position1 = contenu.indexOf(charSpe)
position2 = contenu.indexOf(charSpe, position1 + 1)
exp1 = contenu.substring(0, position1)
exp2 = contenu.substring(position1 + 1, position2)
if (position2 === -1) {
console.warn(Error(`Il manque un symbole ${charSpe} dans la chaîne "${contenu}"`))
position2 = Math.min(position1 + 1, contenu.length - 1)
}
contenu = contenu.substring(position2 + 1)
table.push(escLt(exp1))
typetable.push('constante')
table.push(escLt(exp2))
typetable.push(charsSpe[charSpe])
charSpe = getFirstCharSpe(contenu)
}
// reste le dernier morceau, qu’on analyse ici
let schaine, unarbre, unresultat, tab
// traitement de calcule[xx]
while (contenu.indexOf('calcule[') !== -1) {
position1 = contenu.indexOf('calcule[')
position2 = contenu.indexOf(']', position1)
schaine = contenu.substring(position1 + 8, position2)
unarbre = new Tarbre(schaine, [])
unresultat = unarbre.evalue([])
contenu = j3pRemplace(contenu, position1, position2, j3pVirgule(j3pArrondi(unresultat, 10)))
}
// traitement de signe[xx]
while (contenu.indexOf('signe[') !== -1) {
position1 = contenu.indexOf('signe[')
position2 = contenu.indexOf(']', position1)
schaine = contenu.substring(position1 + 6, position2)
if (Number(schaine) > 0) {
unresultat = '+' + Number(schaine)
} else {
unresultat = schaine
}
contenu = j3pRemplace(contenu, position1, position2, unresultat)
}
// traitement de pgcd[]
while (contenu.indexOf('pgcd[') !== -1) {
position1 = contenu.indexOf('pgcd[')
position2 = contenu.indexOf(']', position1)
schaine = contenu.substring(position1 + 5, position2)
tab = schaine.split(',')
unresultat = String(j3pPGCD(tab[0], tab[1]))
contenu = j3pRemplace(contenu, position1, position2, unresultat)
}
// on termine toujours par une expression constante (éventuellement vide)
table.push(escLt(contenu))
// pas de typetable.push ? ATTENTION, typetable est plus court de 1
// table et typetable sont complets
/** liste des ids des inputs dynamiques */
const dynIputsIds = []
const lastIndice = table.length - 1
let k, lettreParam, valeurParam, chunks, tableElt, tableEltSuiv, typeEltSuiv, nombre, intervalle
// on parcours table de 2 en 2
for (k = 0; k < lastIndice; k = k + 2) {
// on construit la chaine par paquet de 2 : expression constante suivie d’une expression spéciale
tableElt = table[k]
tableEltSuiv = table[k + 1]
typeEltSuiv = typetable[k + 1]
// avant de passer à l’expression spéciale, il faut remplacer les paramètres éventuels (précédés de £)
while (tableElt.indexOf('£') !== -1) {
position1 = tableElt.indexOf('£')
// on remplace £x par sa valeur dans les options
// options[lettreParam] contient une constante ou une expression du type genere[10;19]
lettreParam = tableElt.charAt(position1 + 1)
valeurParam = options[lettreParam]
chunks = /^genere\[([+-]?[0-9.]+);([+-]?[0-9.]+)]$/.exec(String(valeurParam))
nombre = chunks ? j3pGetRandomInt(chunks[1], chunks[2]) : valeurParam
tableElt = tableElt.substring(0, position1) + String(nombre) + tableElt.substring(2 + position1)
}
html += tableElt // l’expression constante
// avant de passer à l’expression spéciale, il faut remplacer les paramètres éventuels (précédés de £)
while (tableEltSuiv.indexOf('£') !== -1) {
position1 = tableEltSuiv.indexOf('£')
lettreParam = tableEltSuiv.charAt(position1 + 1)
valeurParam = options[lettreParam]
// on remplace £x par sa valeur dans l’options
if (String(valeurParam).indexOf('genere') !== -1) {
intervalle = valeurParam.substring(6)
nombre = j3pGetRandomInt.apply(null, intervalle)
options[lettreParam] = nombre // on redéfinit la valeur du paramètre pour le passage suivant
} else {
nombre = valeurParam
}
tableEltSuiv = tableEltSuiv.substring(0, position1) + nombre + tableEltSuiv.substring(2 + position1)
}
while (tableEltSuiv.indexOf('calcule[') !== -1) {
position1 = tableEltSuiv.indexOf('calcule[')
position2 = tableEltSuiv.indexOf(']', position1)
schaine = tableEltSuiv.substring(position1 + 8, position2)
unarbre = new Tarbre(schaine, [])
unresultat = unarbre.evalue([])
tableEltSuiv = j3pRemplace(tableEltSuiv, position1, position2, j3pVirgule(j3pArrondi(unresultat, 10)))
}
while (tableEltSuiv.indexOf('signe[') !== -1) {
position1 = tableEltSuiv.indexOf('signe[')
position2 = tableEltSuiv.indexOf(']', position1)
schaine = tableEltSuiv.substring(position1 + 6, position2)
if (Number(schaine) > 0) {
unresultat = '+' + Number(schaine)
} else {
unresultat = schaine
}
tableEltSuiv = j3pRemplace(tableEltSuiv, position1, position2, unresultat)
}
while (tableEltSuiv.indexOf('pgcd[') !== -1) {
position1 = tableEltSuiv.indexOf('pgcd[')
position2 = tableEltSuiv.indexOf(']', position1)
schaine = tableEltSuiv.substring(position1 + 5, position2)
tab = schaine.split(',')
unresultat = String(j3pPGCD(tab[0], tab[1]))
tableEltSuiv = j3pRemplace(tableEltSuiv, position1, position2, unresultat)
}
let numero, letexte, couleur, size, border, maxlength, maxchars, police, width
// CAS mq
if (typeEltSuiv === 'mq') {
// il faut le _ entre id et k pour distinguer affiche_10 et affiche_1_0 (cf sesabibli/5ecea1f27201b35bfe323468)
const spanId = j3pGetNewId(`${id}_${k}`, true) // on s’assure de l’unicité
// le replace sert à ajouter une espace après les < suivis d’une lettre ou d’un chiffre, pour éviter une interprétation html
html += '<span id="' + spanId + '" class="mq-math-mode">' + tableEltSuiv.replace(/<(\w)/g, '< $1') + '</span>'
ids.mqList.push(spanId)
// CAS mqedit
} else if (typeEltSuiv === 'mqedit') {
numero = Number(tableEltSuiv)
const mqProp = 'inputmq' + numero
const mqOpts = options[mqProp] || {}
// contenu du type n_expressionfalculatative
// var expression = table[k+1].substring(2);
const expression = mqOpts.texte || ''
if (typeof mqOpts.maxwidth === 'undefined') {
mqlargeur[k + 1] = 0
} else {
mqlargeur[k + 1] = mqOpts.maxwidth
mqrestric[k + 1] = true
}
if (typeof mqOpts.maxheight === 'undefined') {
mqhauteur[k + 1] = 0
} else {
mqhauteur[k + 1] = mqOpts.maxheight
mqrestric[k + 1] = true
}
// var numero = Number(table[k+1].substring(0, table[k+1].indexOf("_")));
if (typeof mqOpts.maxlength === 'undefined') {
mqlength[k + 1] = 0
} else {
mqlength[k + 1] = mqOpts.maxlength
mqrestric[k + 1] = true
}
numerotable[k + 1] = numero
const spanId = j3pGetNewId(id + mqProp, true)
ids.inputmqList.push(spanId)
html += '<span id="' + spanId + '" class="mq-editable">' + expression + '</span>'
// CAS input
} else if (typeEltSuiv === 'input') {
numero = Number(tableEltSuiv)
const inputValue = options['input' + numero]
let fontSize
if (typeof inputValue === 'undefined') {
// valeurs par défaut
letexte = ''
fontSize = '18px'
couleur = options?.styletexte?.couleur || '#000'
size = 4
border = 'none'
police = j3pGetCssProp(conteneur, 'fontFamily')
} else {
// y’a des options pour cet input, on init chaque paramètre
// maxchars
if (typeof inputValue.maxchars === 'number' || (typeof inputValue.maxchars === 'string' && /^\d+$/.test(inputValue.maxchars))) {
maxchars = inputValue.maxchars
} else {
maxchars = ''
}
// maxlength
if (typeof inputValue.maxlength === 'number' || (typeof inputValue.maxlength === 'string' && /^\d+$/.test(inputValue.maxlength))) {
maxlength = inputValue.maxlength
} else {
maxlength = maxchars // éventuellement vide
}
// letexte
letexte = inputValue.texte || ''
// couleur
couleur = inputValue.couleur || options?.styletexte?.couleur
// taillepolice
if (typeof inputValue.taillepolice === 'number' || (typeof inputValue.taillepolice === 'string' && /^[0-9]+$/.test(inputValue.taillepolice))) {
fontSize = inputValue.taillepolice + 'px'
} else if (props.style.fontSize) {
// on le prend s’il est en px
chunks = /^([0-9]+px)$/.exec(props.style.fontSize)
if (chunks) fontSize = chunks[1]
}
// sinon, faut remonter les parents pour en trouver un qui fixe fontSize
// (pourquoi ça n’en hérite pas d’office, je sais pas mais c'était le cas après aa58aac0)
if (!fontSize) {
fontSize = j3pGetCssProp(conteneur, 'fontSize')
if (!fontSize) fontSize = '18px'
}
// size
size = inputValue.taille || 20
// border
border = inputValue.border || 'none'
// la largeur par défaut pour les inputs dynamiques et classiques
const largDynDefaut = 20
const largInputDefaut = 100
width = 'width:' + (inputValue.dynamique ? largDynDefaut : largInputDefaut) + 'px'
if (typeof inputValue.width === 'string') {
chunks = /^([0-9]+)(px)?$/.exec(inputValue.width)
const valWidth = chunks ? Number(chunks[1]) : 0
width = 'width:' + Math.max(valWidth, largDynDefaut) + 'px'
}
// police (pourquoi faut du j3pGetCssProp alors qu’on devrait en hériter ?
// Si on le fait pas on se retrouve avec la police par défaut du navigateur, cf aa58aac0)
police = inputValue.police || j3pGetCssProp(conteneur, 'fontFamily')
}
const spanId = j3pGetNewId(id + 'inputSpan' + numero, true)
const inputId = j3pGetNewId(id + 'input' + numero, true)
let style = `border:${border};size:${size};${width};`
if (couleur) style += `color:${couleur};`
if (police) style += `font-family:${police.replace(/"/g, '\'')};` // peut contenir des " ("CM Super" par ex)
if (fontSize) style += `font-size:${fontSize};`
html += `<span id="${spanId}"><input type="text" autocomplete="off" maxchars="${maxchars}" size="${size}" id="${inputId}" maxlength="${maxlength}" value="${letexte}" style="${style}"></span> `
ids.inputList.push(inputId)
if (inputValue.dynamique) {
dynIputsIds.push(id + 'input' + numero)
}
// CAS liste
} else if (typeEltSuiv === 'liste') {
numero = Number(tableEltSuiv)
const opt = options['liste' + numero]
// idem, on impose l’héritage si c’est pas précisé
const fontSize = (opt?.taillepolice && (opt.taillepolice + 'px')) || j3pGetCssProp(conteneur, 'fontSize') || '18px'
let style = `font-size:${fontSize};`
const color = opt?.couleur || j3pGetCssProp(conteneur, 'color')
if (color) style += `color:${color};`
const spanId = j3pGetNewId(id + 'spanListe' + numero, true)
const selectId = j3pGetNewId(id + 'liste' + numero, true)
html += `<span id="${spanId}"><form style="display:inline"><select id="${selectId}" size="1" style="${style}">`
if (opt?.texte) {
for (const txt of opt.texte) {
html += '<option>' + txt + '</option>'
}
} else {
console.error(Error('Aucune option texte à mettre dans le select'))
html += '<option>Désolé, aucun choix n’a été paramétré</option>'
}
html += '</select></form></span>'
ids.selectList.push(selectId)
}
} // fin boucle sur les elts de table
// on ajoute la dernière constante
html += table[lastIndice]
mainElt.innerHTML = resumeHtml(html)
// on ecrit les equations avec mathquill(), s’il y en a
let elt, $elt
for (k = 0; k < lastIndice; k = k + 2) {
if (typetable[k + 1] === 'mq') {
const selector = `#${id}_${k}`
const $elt = $(selector)
if ($elt[0]) {
renderStaticMath($elt[0])
} else {
console.error(Error(`${selector} ne remonte pas d’élément, impossible d’appeler latex() dessus`))
}
} else if (typetable[k + 1] === 'mqedit') {
elt = j3pElement(id + 'inputmq' + numerotable[k + 1])
if (elt) {
lazyMqProcess(() => {
$elt = $(elt)
$elt.mathquill('editable')
if (mqrestric[k + 1]) {
elt.setAttribute('mqlargeur', mqlargeur[k + 1])
elt.setAttribute('mqlength', mqlength[k + 1])
elt.setAttribute('mqhauteur', mqhauteur[k + 1])
elt.setAttribute('marqueur', 'MARK')
elt.addEventListener('keypress', restricTaille, false)
}
})
}
}
}
// Tous les input sont autosize
for (k = 0; k < dynIputsIds.length; k++) {
j3pElement(dynIputsIds[k]).addEventListener('input', inputAutoSizeListener, false)
}
// on a les ids des éléments qu’on a créé, maintenant qu’ils sont dans le dom on veut ces éléments
const retour = { parent: mainElt }
// on remplace chaque id par l’élément
Object.entries(ids).forEach(([prop, value]) => {
retour[prop] = value.map(j3pElement)
})
// Ajout Yves pour le traitement des \editable{}, que mathquill a transformé en mq-editable-field
// mais qui n’ont pas été construit ici comme les inputmq (les champs &n°&)
// On cherche tous les champs Mathquill editable qui n’ont pas d’id
retour.inputmqefList = []
const nodeList = conteneur.querySelectorAll('.mq-editable-field')
let indice = 0 // faut pas prendre l’indice de la nodeList, on veut incrémenter seulement sur ceux qui n’ont pas d’id
Array.from(nodeList).forEach((mqField) => {
if (!mqField.id) {
indice++ // ça démarrera donc à 1
mqField.id = j3pGetNewId(id + 'inputmqef' + indice, true)
retour.inputmqefList.push(mqField)
}
})
// console.log('j3pAffiche retourne', retour)
return retour
} // j3pAffiche
/**
* Crée un texte mathématique, les parties entre $ seront affichées par mathquill et leurs éventuels £x substitués
* Variante de syntaxe : j3pMathsAjouteDans(conteneur, id, content, parametres, pos)
* @param {string|HTMLElement} conteneur L’élément (ou son id) dans lequel ajouter le texte
* @param {Object} options
* @param {string} [options.id] Si fourni mis sur l’élément créé et utilisé comme préfixe sur les id des zones mathquill (qui n’auront pas d’id sinon)
* @param {string} options.content Le texte à afficher par ex "$z_1=£x+i£y$"
* @param {Object} [options.parametres] Les remplacements pour les £xx (entre $) dans content, de la forme { nomVariable: valeur }
* @param {Object} [options.pos] pour positionnement absolu du texte (avec top & left) ou taille de police
* @param {number|string} [options.pos.top]
* @param {number|string} [options.pos.left]
* @param {number|string} [options.pos.taillepolice] sera mis en style.fontSize, c’est un vieux comportement, il vaut mieux passer par options.style.fontSize
* @param {string} [options.tag=span] Le tag à utiliser pour envelopper la formule
* @param {Object} [options.style] Le style qui sera sera mis sur le tag enveloppe (éventuellement surchargé par pos pour le positionnement)
* @return {HTMLElement|void} L’élément créé (de type options.tag, span par défaut), ou undefined si on a pas pu l’ajouter
*/
export function j3pMathsAjouteDans (conteneur, options) {
if (typeof conteneur === 'string') conteneur = j3pElement(conteneur)
if (!j3pIsHtmlElement(conteneur)) return console.error(Error('Pas de conteneur pour mettre cette formule'))
let id, content, parametres, pos, tag
if (typeof options === 'string') {
// syntaxe à 5 arguments
id = options
content = arguments[2]
parametres = arguments[3] || {}
pos = arguments[4]
tag = 'span'
} else {
id = options.id
content = options.content
parametres = options.parametres || {}
pos = options.pos
tag = options.tag || 'span'
}
let props = options.style ? _clonePropsCleanStyle({ style: options.style }) : {}
if (id) props.id = id
// positionnement ?
if (pos) {
if ((typeof pos.top !== 'undefined') && (typeof pos.left !== 'undefined')) {
if (!props.style) props.style = {}
props.style.position = 'absolute'
props.style.top = pos.top
props.style.left = pos.left
}
}
const cible = addElement(conteneur, tag, props)
const chunks = content.split('$')
if (!chunks.length % 2) {
// nombre pair de morceaux, donc il manque un $ de fin
console.warn(Error('Le contenu passée à j3pMathsAjouteDans a un nombre impair de $'))
// => on remet un $ entre les deux derniers morceaux
const last = chunks.pop()
chunks[chunks.length - 1] += '$' + last
}
for (const [i, chunk] of chunks.entries()) {
if (i % 2 === 0) {
// indice pair, hors LaTex => tel quel
j3pAddContent(cible, chunk)
} else {
// indice impair, traitement des £ puis affichage mathquill
// On utilise mathquill car mathgraph charge déjà son MathJax pour faire du svg,
// Ici le html de mathquill est amplement suffisant
props = {
className: 'mathquill-embedded-latex'
}
if (id) props.id = id + (i - 1) // i-1 pour faire comme c'était avant
props.content = chunk.includes('£')
? j3pChaine(chunk, parametres)
: chunk
const span = addElement(cible, 'span', props)
renderStaticMath(span)
}
}
return cible
} // j3pMathsAjouteDans
/**
* Désactive un input (en général à la correction, pour rendre l’input non modifiable en conservant son contenu à l’écran)
* @param {HTMLElement|string} elt
*/
export function j3pDesactive (elt) {
if (typeof elt === 'string') elt = j3pElement(elt)
if (!elt) return console.error(Error('Impossible de désactiver un élément qui n’existe pas'))
// console.trace('j3pDesactive', elt)
const $elt = $(elt)
// cas textbox
if ($elt.hasClass('mathquill-textbox')) {
// on passe parfois ici ? Franchement, on n’y crois pas, mais au cas où///
notify(Error('Cas mathquill-textbox dans J3PDesactive'))
// avant il y avait ici du code pour récupérer le latex et le passer directement à J3PAffiche
// on laisse continuer et ce cas devrait être récupéré par la gestion mathquill plus bas
}
// Ajout Yves : S’il a un clavier virtuel associe on désactive ce clavier virtuel
// (on le cache s’il était visible et on désactive la petite flèche pour le faire apparaître)
if (elt?.virtualKeyboard?.isActive && typeof elt.virtualKeyboard.setActive === 'function') {
elt.virtualKeyboard.setActive(false)
}
// Pour les listes déroulantes pouvant accueillir du mathquill, il faut que je lui applique un revert
if (elt.classList.contains('mq-math-mode')) $elt.mathquill('revert')
// cas input non mathquill
if (!elt.id || !elt.id.includes('inputmq')) {
try {
elt.disabled = true
} catch (error) {
console.error(error, 'C’est probablement dû à l’appel de j3pDesactive après avoir appelé j3pBarre ou une autre correction qui freeze l’input')
}
return
}
// y’a inputmq dans l’id
// On le rend figé
$elt.mathquill('revert')
}
export function traiteMathQuillSansMultImplicites (ch) {
return ch
.replace(/}{/g, '}/{') // Pour traiter les \frac
.replace(/\\times/g, '*') // POur traiter les signes de multiplication
.replace(/\\left\(/g, '(') // Pour traiter les parenthèses ouvrantes
.replace(/\\right\)/g, ')') // Pour traiter les parenthèses fermantes
.replace(/\\left\|/g, 'abs(') // Traitement des valeurs absolues
.replace(/\\right\|/g, ')')
.replace(/\\frac/g, '') // Les fractions ont déja été remplacées par des divisions
.replace(/\\sqrt/g, 'sqrt') // Traitement des racines carrées
.replace(/\\ln/g, 'ln') // Traitement des ln
.replace(/\\le/g, '<=') // Traitement des signes <=
.replace(/\\ge/g, '>=') // Traitement des signes >=
.replace(/{/g, '(') // Les accolades deviennet des parenthèses
.replace(/}/g, ')')
.replace(/\)(\d)/g, ')*$1') // Remplace les parenthèses ")" suivies d’un chiffre en ajoutant un *
.replace(/\^(\d)(\d)/g, '^$1*$2') // On peut avoir par exemple 2^35^4 pour 2^3*5^4
.replace(/,/g, '.') // Remplacement des virgules par des points décimaux
.replace(/PI/g, 'pi')
.replace(/\\pi */g, 'pi')
.replace(/ /g, '') // ON retire aussi les espaces inutiles
.replace(/([0-9])\*([a-z])/g, '$1$2')// Pour remplacer les expression de la forme 2*x en 2x
}
/**
* Remplace les commandes LaTeX (venant de $().mathquill('latex') ou d’un input mathlive) par leur équivalent texte (\times par *, etc.)
* Attention, il y a quelques partis pris :
* - toute fct latex a deux arguments est supposée \frac
* - il n’y a pas d’exposant à deux chiffres (seul le premier est conservé en exposant, les suivants sont pris comme multiplicateurs)
* @param {string} latex
* @returns {string} La chaîne prête pour être passée à mtgAppLecteur.addImplicitMult() par exemple
*/
export function unLatexify (latex) {
// @tod mettre plutôt ça pour les frac (mais ça marchera pas avec une autre fct latex imbriquée):
// .replace(/\\frac\{([^}])}\{([^}])}/g, '($1)/($2)'). NON car justement les \frac peuvent être imbriqués
return latex.replace(/}{/g, ')/(') // \frac (ça suppose que toute fct latex a deux arguments est \frac, ce qui est le cas dans nos sections)
.replace(/\\frac/g, '') // Les fractions ont déja été remplacées par des divisions
.replace(/\\bracket{/g, '\\(') // FIXM le \\ doit être de trop non ? (faudra aussi corriger le test ensuite) NON ça sert uniquement pour les inéquations
.replace(/\\left\\{/g, '\\(') // Pour le traitement mathlive
.replace(/\\right\\}/g, ')') // Pour le traitement mathlive
.replace(/\\times/g, '*') // signes de multiplication
.replace(/\\left\(/g, '(') // parenthèses ouvrantes
.replace(/\\right\)/g, ')') // parenthèses fermantes
.replace(/\\left\|/g, 'abs(') // valeurs absolues
.replace(/\\overline/g, 'conj') // conjugués
.replace(/\\right\|/g, ')')
.replace(/\\sqrt/g, 'sqrt') // Traitement des racines carrées
.replace(/\\ln/g, 'ln') // Traitement des ln
// Traitement des log décimaux, cos, sin, tan et arg pour les complexes
.replace(/\\(log|cos|sin|tan|arg)/g, '$1')
.replace(/\\leq/g, '<=') // Traitement des signes <= de mathlive
.replace(/\\le(?!ft)/g, '<=') // Traitement des signes <= sans traiter les \left
.replace(/\\geq/g, '>=') // Traitement des signes >= de mathlive
.replace(/\\ge/g, '>=') // Traitement des signes >=
.replace(/{/g, '(') // Les accolades deviennent des parenthèses
.replace(/}/g, ')')
.replace(/\)(\d)/g, ')*$1') // Remplace les parenthèses ")" suivies d’un chiffre en ajoutant un *
// Cette ligne transforme les puissances à deux chiffres, 2^42 devient 2^4*2
// La ligne suivante n’est plus nécessaire avec le nouveau mathquill, car si on entre par exemple 2^12 3^5 (sans mettre de signe de multiplication)
// il renvoie désormais 2^{12}\times3^5
// .replace(/\^(\d)(\d)/g, '^$1*$2') // On peut avoir par exemple 2^35^4 pour 2^3*5^4
.replace(/,/g, '.') // Remplacement des virgules par des points décimaux
.replace(/P[Ii]/g, 'pi')
.replace(/\\pi/g, 'pi')
.replace(/\\cup/g, '|')
.replace(/\\varnothing/g, '∅')
.replace(/\\infty/g, '∞')
.replace(/\\mathbb\(R\)/g, '_R')
.replace(/ /g, '')
.replace(/\^\{}/g, '') // On supprime les puissances vides (oubli de l’utilisateur)
}
/**
* Ajoute une restriction et un clavier virtuel à un input mathquill. Peut s’utiliser en remplacement direct de j3pRestriction (même s’il offre des options supplémentaires)
* @param {HTMLElement|string} inputMq L’input mathquill (le span.mq-editable-field)
* @param {string|RegExp} restriction La restriction appliquée au keypress (pour ne pas en mettre laisser null).
* Si c’est une string, elle sera contrôlée / nettoyée (cf convertRestriction), privilégier le format RegExp.
* @param {Object} [options]
* @param {RegExp} [options.virtualKeyboardRestriction] Une regexp qui serait différente de celle appliqué au keypress (si non fournie ce sera restriction)
* @param {string[]} [options.commandes] Une liste de commandes mathquill (racine, fraction, etc.)
* @param {HTMLElement} [options.boundingContainer] Un parent dans lequel il faut essayer de rester (que le clavier ne déborde pas), si non fourni on prendra le premier parent .divZone
*/
export function mqRestriction (inputMq, restriction, options = {}) {
if (typeof inputMq === 'string') inputMq = j3pElement(inputMq)
if (!inputMq) return // j3pElement a déjà râlé en console
if (!inputMq.classList.contains('mq-editable-field')) {
console.warn('mqRestriction appelé sur input invalide', inputMq.outerHTML)
throw Error('input mathquill invalide')
}
if (!restriction) restriction = /./ // on autorise tout
if (typeof restriction === 'string') restriction = convertRestriction(restriction)
// on peut nous passer une restriction différente pour le clavier virtuel (par ex on autorise virgule et point
// pour la saisie clavier physique, car on la remplace, mais pas pour le clavier virtuel)
const kbRestriction = options.virtualKeyboardRestriction || restriction
// on ajoute le clavier virtuel
MqVirtualKeyboard.create(inputMq, kbRestriction, options)
// et on attache aussi la restriction à l’input directement (pour les événements du clavier physique)
inputMq.addEventListener('keypress', function (event) {
j3pRestrict(inputMq, event, restriction)
})
}
/**
* Nettoie une chaîne latex pour mathquill, remplace les \n par <br>, vire les \displaystyle \left[ \right],
* S’il y a du \text, ils seront isolés, un par ligne (et ce qui précède le premier ou suit le dernier est ignoré)
* et \lim_{… \to …} transformés en \limite{}{}
* @param {string} strLatex
* @param {Object} [options]
* @param {boolean} [options.keepText=false] passer true pour conserver les \text dans la chaîne retournée
* @returns {string}
*/
export function cleanLatexForMq (strLatex, { keepText = false } = {}) {
let ch = strLatex
// ça semblerait plus logique de remplacer les \n par des <br>, mais il y avait une bonne raison de les virer complètement (indiquer ici laquelle)
.replace(/\n/g, '')
.replace(/\\displaystyle/g, ' ')
.replace(/\\,/g, ' ')
.replace(/\\left\[/g, '[')
.replace(/\\right]/g, ']')
// On remplace les séparateurs décimaux . par des virgules
.replace(/(\d)\.(\d)/g, '$1,$2')
.replace(/\\mathbb\{R}/g, '\\R')
// transformation des \text éventuels, on ne récupère que leur contenu, et on les sépare par des <br>
if (!keepText && ch.includes('\\text{')) {
let i = ch.indexOf('\\text{')
// faut chercher l’accolade fermante de ce \text
let res = ''
while (i !== -1) {
const idaf = getIndexFermant(ch, i + 5)
if (idaf === -1) {
console.error(Error(`Accolade fermante manquante dans : ${ch}`))
// on laisse tout
res += ch.substring(i + 6)
break
}
// trouvé le fermant, on met ça sur une ligne
res += ch.substring(i + 6, idaf) + '<br>'
i = ch.indexOf('\\text{', idaf + 1)
}
// On vire un éventuel <br> de fin
ch = res.replace(/<br>$/, '')
}
// transformation des \lim éventuels
let ind = ch.indexOf('\\lim_{')
while (ind !== -1) {
const indaccouv = ind + 5
const indaccfer = getIndexFermant(ch, indaccouv)
// si on trouve pas les accodades fermantes ou le \to on retourne la chaîne non modifiée
if (indaccfer === -1) {
console.error(Error(`Accolade fermante manquante dans : ${ch}`))
return ch
}
const indsub = ch.indexOf('\\to', indaccouv + 1)
if (indsub === -1) {
console.error(Error(`\\to manquant dans un \\lim{} : ${ch}`))
return ch
}
const ch1 = ch.substring(indaccouv + 1, indsub)
const ch2 = ch.substring(indsub + 3, indaccfer)
ch = ch.substring(0, ind) + '\\limite{' + ch1 + '}{' + ch2 + '}' + ch.substring(indaccfer + 1)
ind = ch.indexOf('\\lim_{')
}
return ch
}