Source: preparePage.js

"use strict";

/** JSDoc documentation
 * [JSDoc]{@link https://jsdoc.app/index.html} Documentation
 * npm run doc
 */

/**
 * read <p class="formula_applet" ...>TEX with {{result}}</p>,
 * assemble fApp objects, store in FAList. MathQuillify(id)
 * Install mathQuillEditHandler, clickHandler, hammer (doubletap)
 * if isEditor: prepareEditorApplet from editor.js
 */
import $ from "jquery";
import MQ from "./lib/mathquillWrapper.js";
import Hammer from "@egjs/hammerjs";
import mathQuillEditHandler from "./editHandler.js";
import {
  domLoad,
  isH5P
} from "./dom.js";

import config from "./config.json";
import decode from "./decode.js";

import initVirtualKeyboard, {
  showVirtualKeyboard,
  virtualKeyboardEventHandler
} from "./virtualKeyboard.js";

//TODO hide global vars
var FAList = {};
export let editor_fApp = {};

var default_fApp = {
  id: '',
  hasSolution: undefined,
  solution: '',
  hasResultField: true,
  formulaApplet: '',
  mqEditableField: '',
  mathField: '',
  hammer: '',
  definitionsetList: [],
  precision: config.defaultPrecision,
  unitAuto: false,
}

export default async function preparePage() {
  await domLoad;

  // body click deselects all applets
  $('body').on('click', function () {
    $(".formula_applet").removeClass('selected');
    $("button.keyb_button").removeClass('selected');
  });

  // make tab key work
  $('body').on('keyup', function (ev) {
    var key = ev.originalEvent.key;
    if (key == 'Tab') {
      var fa = $(ev.target).parents('.formula_applet');
      // var id = $(fa).attr('id');
      fa.trigger('click');
    }
  });
  // initTranslation(); //not for H5P. Call manually for HTML
  initVirtualKeyboard();
  mathQuillifyAll();
}

export async function mathQuillifyAll() {
  try {
    // if class="formula_applet mq-math-mode ...", then already mathquillified, 
    $(".formula_applet:not(.mq-math-mode)").each(function () {
      // retrieve ids of .formula_applet
      // if editor, id ends with -edit
      mathQuillify(this.id);
    });
  } catch (error) {
    console.error('ERROR: ' + error);
  }
}

export async function mathQuillify(id) {
  await domLoad;
  // create new FApp object and store it in FAList
  // var fApp = new FApp();
  var fApp = Object.create(default_fApp);
  // H5P: applets should have different ids in view mode and in edit mode
  var isEditor = (id.slice(-5) === '-edit');
  if (isEditor) {
    // prepare for getHTML()
    // id is not changed, still ends with -edit
    fApp.id = id.slice(0, -5);
  } else {
    fApp.id = id
  }

  var $el = $('#' + id + '.formula_applet:not(.mq-math-mode)');
  if (typeof $el === 'undefined') {
    throw id + ' not found';
  }
  // activate mouse clicks
  $el.on('click', clickHandler);

  var domElem = $el[0];
  if (typeof domElem !== 'undefined') {
    var temp = domElem.innerHTML;
    temp = temp.replace(/{{result}}/g, '\\MathQuillMathField{}');
    temp = temp.replace(/\\Ohm/g, '\\Omega');
    temp = temp.replace(/\\mathrm/g, '');
    temp = temp.replace(/\\unit{/g, config.unit_replacement);
    temp = temp.replace(/\\times/g, config.multiplicationDot);
    // temp = temp.replace(/\\cdot/g, config.multiplicationCross);
    //TODO simplify code 
    if (isEditor && isH5P()) {
      console.log('H5P & Editor');
      var mf = document.getElementById('math-field');
      temp = mf.textContent;
      console.log('editor & H5P: replace {{result}} with \\class{inputfield}{} - does this ever happen?');
      temp = temp.replace(/{{result}}/g, '\\class{inputfield}{}');
      mf.textContent = temp;
    } else {
      domElem.innerHTML = temp; // does not work with H5P-Editor!!!
    }
    fApp.formulaApplet = domElem;

    if (isEditor) {
      fApp.hasResultField = true;
    } else {
      fApp.hasResultField = ($el.html().indexOf('\\MathQuillMathField{}') >= 0);
    }

    // retrieve definitionsets
    var def = $el.attr('def');
    if (typeof def !== 'undefined') {
      fApp.definitionsetList = unifyDefinitions(def);
    }
    // physics mode or math mode
    var unitAttr = $el.attr('unit');
    var unitAuto = (typeof unitAttr !== 'undefined' && unitAttr === 'auto');
    // mode="physics" is a synonym for "unit="auto"
    var modeAttr = $el.attr('mode');
    var modePhysics = (typeof modeAttr !== 'undefined' && modeAttr === 'physics');
    fApp.unitAuto = unitAuto || modePhysics;

    // retrieve precision
    var prec = $el.attr('precision');
    if (typeof prec === 'undefined') {
      // second chance: allow abbreviation 'prec' for attribute 'precision'
      prec = $el.attr('prec');
    }
    fApp.precision = sanitizePrecision(prec);

    if (typeof $el.attr('data-b64') !== 'undefined') {
      fApp.hasSolution = true;
      fApp.solution = decode($el.attr('data-b64'));
    } else {
      fApp.hasSolution = false;
    }

    // store FApp object in FAList and take id as key
    // editor applets: id ends with -edit, fApp.id is shortened
    FAList[id] = fApp;
  } else {
    throw 'ERROR: no domElem';
  }

  var mqEditableField;
  if (isEditor) {
    editor_fApp = fApp;
    } else {
    // *** no editor ***
    try {
      MQ.StaticMath(domElem);
      // MQ.StaticMath seems to generate a mqEditableField
    } catch (err) {
      console.error('Error using MQ.StaticMath: ' + err);
      console.trace();
    }
    try {
      if (fApp.hasResultField) {
        mqEditableField = $el.find('.mq-editable-field')[0];
        fApp.mqEditableField = mqEditableField;
        mf = MQ.MathField(mqEditableField, {});
        mf.config({
          handlers: {
            edit: () => {
              mqEditableField.focus();
              mathQuillEditHandler(fApp, MQ);
            },
            enter: () => {
              mathQuillEditHandler(fApp, MQ);
            },
          }
        });
        fApp.mathField = mf;

        // make touch sensitive
        try {
          fApp.hammer = new Hammer(mqEditableField);
          fApp.hammer.on("doubletap", function () {
            showVirtualKeyboard();
          });
        } catch (error) {
          console.error('Hammer error: ' + error);
        }
      }
    } catch (error) {
      console.error('ERROR ' + error);
    }
  } // end of *** no editor ***

  try {
    // make virtual keyboard show/hide by mouseclick
    ($('<button class="keyb_button">\u2328</button>')).insertAfter($el);
    $('button.keyb_button').on('mousedown', function () {
      showVirtualKeyboard();
      $("button.keyb_button").removeClass('selected');
    });
    // insert span for right/wrong tag
    $('<span class="truefalse">&nbsp;</span>').insertAfter($el);
  } catch (error) {
    console.error(error);
  }
  if ($('#' + id).hasClass('mq-math-mode')) {
    console.log('mathquillifying ' + id + ': SUCCESS');
  }
  return fApp;
}

function sanitizePrecision(prec) {
  if (typeof prec == 'undefined') {
    prec = config.defaultPrecision;
  } else {
    prec = prec.replace(/,/g, '.');
    var endsWithPercent = prec.slice(-1) === '%';
    if (endsWithPercent) {
      prec = prec.substring(0, prec.length - 1);
    }
    prec = prec.valueOf();
    if (endsWithPercent) {
      prec = prec * 0.01;
    }
  }
  return prec;
}

function clickHandler(ev) {
  try {
    var fApp = FAList[ev.currentTarget.id];
    if (typeof fApp !== 'undefined') {
      if (fApp.hasResultField) {
        ev.stopPropagation(); // avoid body click
        // deselect all applets
        $(".formula_applet").removeClass('selected');
        $(".formula_applet").off('virtualKeyboardEvent');
        $(fApp.formulaApplet).addClass('selected');
        $(fApp.formulaApplet).on('virtualKeyboardEvent', function (_evnt, cmd) {
          if (cmd === '#Enter') {
            // mathQuillEditHandler cannot be outsourced to virtualKeyboard (circular dependency)
            fApp = FAList[_evnt.currentTarget.id];
            //TODO see, if special syntax necessary? 
            //TODO mathQuillEditHandler(fApp, MQ, 'enter'); 
            mathQuillEditHandler(fApp, MQ);
          } else {
            virtualKeyboardEventHandler(_evnt, cmd, fApp.mathField);
          }
        });
        $("button.keyb_button").removeClass('selected');
        if (($('#virtualKeyboard').css('display') || 'none') == 'none') {
          // if virtual keyboard is hidden, select keyboard button
          $(fApp.formulaApplet).nextAll("button.keyb_button:first").addClass('selected');
        }
      }
      // else {
      //   // fApp has no ResultField (static formula)
      //   try {
      //     var mfContainer = MQ.StaticMath(fApp.formulaApplet);
      //     var mfLatexForParser = mfContainer.latex();
      //     var myTree = new FaTree();
      //     myTree.leaf.content = mfLatexForParser;
      //   } catch (error) {
      //     console.log('ERROR ' + error);
      //   }
      // }
    }
  } catch (error) {
    console.log('ERROR ' + error);
  }
}

/**
 * decomposes a definition string into a list of definitions
 * 
 * @param {string} def definition sets, composed with & or &&
 * @returns {string[]} array of string expressions with condition to be positive
 * @example def="x > 0 && y < 5" returns ["x", "5-y"], meaning x > 0 and 5-y > 0
 */
function unifyDefinitions(def) {
  def = def.replace(/\s/g, "");
  def = def.replace(/&&/g, "&");
  var dsList = def.split("&");
  for (var i = 0; i < dsList.length; i++) {
    var ds = dsList[i];
    var result = '';
    var temp;
    if (ds.indexOf('>') > -1) {
      temp = ds.split('>');
      if (temp[1] == '0') {
        result = temp[0];
      } else {
        result = temp[0] + '-' + temp[1];
      }
    }
    if (ds.indexOf('<') > -1) {
      temp = ds.split('<');
      if (temp[0] == '0') {
        result = temp[1];
      } else {
        result = temp[1] + '-' + temp[0];
      }
    }
    dsList[i] = result;
  }
  return dsList;
}
//TODO delete refreshLatex, replace with refreshTimesGlyph
// function refreshLatex(lang) {
//   var id;
//   for (id in FAList) {
//     var fApp = FAList[id];
//     var isEditor = (id.slice(-5) == '-edit');
//     if (!isEditor) {
//       var hasSolution = fApp.hasSolution || false;
//       var oldLatex, newLatex;
//       if (hasSolution) {
//         var mf = fApp.mathField;
//         oldLatex = mf.latex();
//       } else {
//         try {
//           var mfContainer = MQ.StaticMath(fApp.formulaApplet);
//           oldLatex = mfContainer.latex();
//         } catch (error) {
//           console.log('ERROR ' + error);
//         }
//       }
//       if (lang == 'de') {
//         newLatex = oldLatex.replace(/\\times/g, '\\cdot');
//         newLatex = newLatex.replace(/[.]/g, ',');
//       }
//       if (lang == 'en') {
//         newLatex = oldLatex.replace(/\\cdot/g, '\\times');
//         newLatex = newLatex.replace(/,/g, '.');
//       }
//       newLatex = sanitizeInputfieldTag(newLatex);
//       if (oldLatex !== newLatex) {
//         console.log('oldLatex=' + oldLatex);
//         console.log('newLatex=' + newLatex);
//         editHandlerActive = false;
//         if (fApp.hasSolution) {
//           mf.latex(newLatex);
//         } else {
//           mfContainer.latex(newLatex);
//         }
//         editHandlerActive = true;
//       }
//     }
//   }
// }