lib/editor/tiny/plugins/equation/amd/src/equation.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Equation helper for Tiny Equation plugin.
  17. *
  18. * @module tiny_equation/equation
  19. * @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. import Selectors from 'tiny_equation/selectors';
  23. let sourceEquation = null;
  24. /**
  25. * Get the source equation.
  26. * @returns {Object}
  27. */
  28. export const getSourceEquation = () => sourceEquation;
  29. /**
  30. * Get selected equation.
  31. * @param {TinyMCE} editor
  32. * @returns {boolean}
  33. */
  34. export const getSelectedEquation = (editor) => {
  35. const currentSelection = editor.selection.getSel();
  36. if (!currentSelection) {
  37. // Do the early return if there is no text selected.
  38. return false;
  39. }
  40. const textSelection = editor.selection.getNode().textContent;
  41. const currentCaretPos = currentSelection.focusOffset;
  42. let returnValue = false;
  43. Selectors.equationPatterns.forEach((pattern) => {
  44. // For each pattern in turn, find all whole matches (including the delimiters).
  45. const regexPattern = new RegExp(pattern.source, "g");
  46. [...textSelection.matchAll(regexPattern)].forEach((matches) => {
  47. const match = matches[0];
  48. // Check each occurrence of this match.
  49. let startIndex = 0;
  50. const startOuter = textSelection.indexOf(match, startIndex);
  51. const endOuter = startOuter + match.length;
  52. // This match is in our current position - fetch the innerMatch data.
  53. const innerMatch = match.match(pattern);
  54. if (innerMatch && innerMatch.length) {
  55. // We need the start and end of the inner match for later.
  56. const startInner = textSelection.indexOf(innerMatch[1], startOuter);
  57. const endInner = startInner + innerMatch[1].length;
  58. // We need to check the caret position before returning the match.
  59. if (currentCaretPos >= startOuter && currentCaretPos <= endOuter) {
  60. // We'll be returning the inner match for use in the editor itself.
  61. returnValue = innerMatch[1];
  62. // Save all data for later.
  63. sourceEquation = {
  64. // Inner match data.
  65. startInnerPosition: startInner,
  66. endInnerPosition: endInner,
  67. innerMatch: innerMatch
  68. };
  69. return;
  70. }
  71. }
  72. // Update the startIndex to match the end of the current match so that we can continue hunting
  73. // for further matches.
  74. startIndex = endOuter;
  75. });
  76. });
  77. // We trim the equation when we load it and then add spaces when we save it.
  78. if (returnValue !== false) {
  79. returnValue = returnValue.trim();
  80. } else {
  81. // Clear the saved source equation.
  82. sourceEquation = null;
  83. }
  84. return returnValue;
  85. };
  86. /**
  87. * Get current equation data.
  88. * @param {TinyMCE} editor
  89. * @returns {{}}
  90. */
  91. export const getCurrentEquationData = (editor) => {
  92. let properties = {};
  93. const equation = getSelectedEquation(editor);
  94. if (equation) {
  95. properties.equation = equation;
  96. }
  97. return properties;
  98. };
  99. /**
  100. * Handle insertion of a new equation, or update of an existing one.
  101. * @param {Element} currentForm
  102. * @param {TinyMCE} editor
  103. */
  104. export const setEquation = (currentForm, editor) => {
  105. const input = currentForm.querySelector(Selectors.elements.equationTextArea);
  106. const sourceEquation = getSourceEquation();
  107. let value = input.value;
  108. if (value !== '') {
  109. if (sourceEquation) {
  110. const selectedNode = editor.selection.getNode();
  111. const text = selectedNode.textContent;
  112. value = ' ' + value + ' ';
  113. selectedNode.textContent = text.slice(0, sourceEquation.startInnerPosition)
  114. + value
  115. + text.slice(sourceEquation.endInnerPosition);
  116. } else {
  117. value = Selectors.delimiters.start + ' ' + value + ' ' + Selectors.delimiters.end;
  118. editor.insertContent(value);
  119. }
  120. }
  121. };