From c2e016e7778189d2b0cd6c7dbd92b26b539de959 Mon Sep 17 00:00:00 2001 From: Hrushikesh Vaidya Date: Wed, 31 Jan 2024 14:59:17 +0530 Subject: [PATCH] Adding MJXGUI form input --- Gruntfile.js | 59 +++-- docs/contributing.md | 20 ++ .../functionality.md} | 4 +- docs/customizing/index.md | 9 + docs/customizing/ui.md | 0 src/mjxgui.css | 28 ++- src/modules/cursor.js | 210 +++++++++--------- src/modules/editor.html | 10 +- src/modules/editor.min.html | 2 +- src/modules/form-input.html | 4 + src/modules/form-input.min.html | 1 + src/modules/ui.js | 84 ++++++- 12 files changed, 282 insertions(+), 149 deletions(-) rename docs/{customizing.md => customizing/functionality.md} (96%) create mode 100644 docs/customizing/index.md create mode 100644 docs/customizing/ui.md create mode 100644 src/modules/form-input.html create mode 100644 src/modules/form-input.min.html diff --git a/Gruntfile.js b/Gruntfile.js index 7590872..7512446 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,32 +1,43 @@ -module.exports = function(grunt) { +module.exports = function (grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), htmlmin: { src: { options: { removeComments: true, - collapseWhitespace: true + collapseWhitespace: true, }, files: { - 'src/modules/editor.min.html': 'src/modules/editor.html' - } - } + 'src/modules/editor.min.html': 'src/modules/editor.html', + 'src/modules/form-input.min.html': + 'src/modules/form-input.html', + }, + }, }, injectHTML: { options: { - html: 'src/modules/editor.min.html', - src: 'src/modules/ui.js' + editorHtml: 'src/modules/editor.min.html', + formInputHtml: 'src/modules/form-input.min.html', + src: 'src/modules/ui.js', }, }, concat: { build: { - src: ['src/modules/expression-backend.js', 'src/modules/cursor.js', 'src/modules/ui.js'], + src: [ + 'src/modules/expression-backend.js', + 'src/modules/cursor.js', + 'src/modules/ui.js', + ], dest: 'src/mjxgui.js', }, buildExample: { - src: ['src/modules/expression-backend.js', 'src/modules/cursor.js', 'src/modules/ui.js'], - dest: 'docs/js/mjxgui.js' - } + src: [ + 'src/modules/expression-backend.js', + 'src/modules/cursor.js', + 'src/modules/ui.js', + ], + dest: 'docs/js/mjxgui.js', + }, }, uglify: { options: { @@ -35,23 +46,33 @@ module.exports = function(grunt) { build: { src: 'src/mjxgui.js', dest: 'src/mjxgui.min.js', - } - } + }, + }, }); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-htmlmin'); - grunt.registerTask('injectHTML', function() { + grunt.registerTask('injectHTML', function () { const options = this.options(); - const template = grunt.file.read(options.html); + const editorTemplate = grunt.file.read(options.editorHtml); + const formInputTemplate = grunt.file.read(options.formInputHtml); const src = grunt.file.read(options.src); - const content = src.replace(/\{\{\seditor_html\s}}/, template); + let content = src.replace(/\{\{\seditor_html\s}}/, editorTemplate); + content = content.replace( + /\{\{\sform_input_html\s}}/, + formInputTemplate, + ); grunt.file.write(options.src, content); - }) + }); - grunt.registerTask('default', ['htmlmin', 'injectHTML', 'concat', 'uglify']); + grunt.registerTask('default', [ + 'htmlmin', + 'injectHTML', + 'concat', + 'uglify', + ]); grunt.registerTask('inject-ui', ['htmlmin', 'injectHTML']); -} \ No newline at end of file +}; diff --git a/docs/contributing.md b/docs/contributing.md index 1a1b084..58a5b5e 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -124,11 +124,31 @@ If you are working on a patch that requires changing the HTML for the MJXGUI edi ``` 4. Run the `inject-ui` task from this project's Gruntfile by running - ```bash + grunt htmlmin grunt inject-ui ``` This will minify the `editor.html` file and inject the minified HTML in the right place in the `ui.js` file. You can then test these changes and complete your patch. +## Working With The Form Input's HTML +If you are working on a patch that requires changing the HTML for the MJXGUI form input, you will need to make changes to the `src/modules/form-input.html` file. Once you have made your changes, you will need to inject this HTML into the MJXGUI source for the new HTML to be used. You can do this by running `Grunt`. Make sure [Grunt is installed](https://gruntjs.com/getting-started) and follow these steps - + + +1. Open the `src/modules/ui.js` file. +2. Find the line where the form input's HTML is injected into the file, which will look something like this - + `const formInputHTML = '< big HTML string >';` +3. Delete the big HTML string, and replace that line with this line - + ```javascript + const formInputHTML = '{{ form_input_html }}'; + ``` +4. Run the `inject-ui` task from this project's Gruntfile by running - + ```bash + grunt htmlmin + grunt inject-ui + ``` + +This will minify the `form-input.html` file and inject the minified HTML in the right place in the `ui.js` file. You can then test these changes and complete your patch. + ## Submitting a Patch Once you have finished working on your patch and verified that your issue has been fixed, push your changes and create a pull request! diff --git a/docs/customizing.md b/docs/customizing/functionality.md similarity index 96% rename from docs/customizing.md rename to docs/customizing/functionality.md index fdd72ef..7374205 100644 --- a/docs/customizing.md +++ b/docs/customizing/functionality.md @@ -37,7 +37,7 @@ mjxgui.successCallback = function () { mjxgui.registerSymbol('\\therefore', '∴', 'Therefore', false); ``` -You can see this example [here](./examples/add-custom-symbol.html). +You can see this example [here](../examples/add-custom-symbol.html). ## Adding A Custom Function Adding a function to the editor widget is a little different from adding a symbol. A function needs to know how its LaTeX should be generated, since it is not simple static LaTeX like a symbol. To add a function that is not already present, you will need - @@ -85,4 +85,4 @@ mjxgui.registerFunction( typeset = false ); ``` -You can see this example [here](./examples/add-custom-function.html). +You can see this example [here](../examples/add-custom-function.html). diff --git a/docs/customizing/index.md b/docs/customizing/index.md new file mode 100644 index 0000000..25652d0 --- /dev/null +++ b/docs/customizing/index.md @@ -0,0 +1,9 @@ +--- +layout: default +title: Customizing +nav_order: 3 +has_children: true +--- + +# Customizing +MJXGUI lets you customize both the functionality of the editor widget as well as the UI. diff --git a/docs/customizing/ui.md b/docs/customizing/ui.md new file mode 100644 index 0000000..e69de29 diff --git a/src/mjxgui.css b/src/mjxgui.css index 6de01a8..1fb7a91 100644 --- a/src/mjxgui.css +++ b/src/mjxgui.css @@ -15,7 +15,7 @@ --error-red: rgb(255, 13, 53); } -#_mjxgui_editor_window { +._mjxgui_editor_window { display: none; position: fixed; top: 50%; @@ -33,30 +33,30 @@ width: min(600px, 80%); } -#_mjxgui_editor_window svg { +._mjxgui_editor_window svg { stroke: var(--default-font-color); } -#_mjxgui_editor_window button { +._mjxgui_editor_window button { background-color: var(--background-color); transition: background-color ease 0.2s; color: var(--default-font-color); } -#_mjxgui_editor_window button:hover { +._mjxgui_editor_window button:hover { background-color: var(--background-dark-1); } -#_mjxgui_tab_container_container { +._mjxgui_tab_container_container { display: flex; flex-flow: row wrap; } -#mjxgui_save_equation svg { +._mjxgui_save_equation svg { stroke: var(--success-green); } -#mjxgui_clear_equation svg { +._mjxgui_clear_equation svg { stroke: var(--error-red); } @@ -80,13 +80,13 @@ background-color: var(--background-dark-1); } -#mjxgui_editor_controls { +.mjxgui_editor_controls { display: flex; flex-flow: row wrap; justify-content: space-between; } -#_mjxgui_editor_display { +._mjxgui_editor_display { padding: 10px; margin: 10px; border: 1px solid var(--default-font-color); @@ -148,4 +148,14 @@ margin: 0 5px; border: 1px solid transparent; border-radius: 3px; +} + +._mjxgui_equation_input { + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +._mjxgui_equation_input_preview { + margin-left: 10px; } \ No newline at end of file diff --git a/src/modules/cursor.js b/src/modules/cursor.js index b6a0a7e..1f4b0dc 100644 --- a/src/modules/cursor.js +++ b/src/modules/cursor.js @@ -7,7 +7,7 @@ for (let char of 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 /** * @class - * + * */ class Cursor { constructor(expression, display) { @@ -24,17 +24,16 @@ class Cursor { // Insert some text into the Expression, either as its own block or into the block // we are in currently. if (this.block === null) { - // Safe to assume we are not in any block and are between two components in the + // Safe to assume we are not in any block and are between two components in the // Expression or at the start or end of the Expression. const _ = new TextComponent(this.block); _.blocks[0].addChild(text); this.expression.add(_, Math.ceil(this.position)); - + this.child = -0.5; this.position++; - } - else { - // We are in some Block in some Component of the Expression. + } else { + // We are in some Block in some Component of the Expression. // The child we are in changes, the component, block, and position remain the same const _ = new TextComponent(this.block); _.blocks[0].addChild(text); @@ -45,34 +44,37 @@ class Cursor { addComponent(component) { // Insert a new Component into the Expression at the position of the cursor. - // If we are in a block, we add a Component to the block as a child, otherwise - // we insert the Component on the top level as a new component in the + // If we are in a block, we add a Component to the block as a child, otherwise + // we insert the Component on the top level as a new component in the // Expression if (this.block === null) { this.expression.add(component, Math.ceil(this.position)); this.position = Math.ceil(this.position); - if (component instanceof MJXGUISymbol || component instanceof TextComponent) { + if ( + component instanceof MJXGUISymbol || + component instanceof TextComponent + ) { this.block = null; this.component = null; this.position += 0.5; - } - else { + } else { this.block = component.blocks[0]; this.component = component; } this.child = -0.5; - } - else { + } else { // Add the component to the block's children and increment this.child in preparation to move // inside the inserted component this.block.addChild(component, Math.ceil(this.child)); // this.child += 0.5; - if (component instanceof MJXGUISymbol || component instanceof TextComponent) { + if ( + component instanceof MJXGUISymbol || + component instanceof TextComponent + ) { // If the component we just inserted is a Symbol or Text, don't move into it and increment // this.child by 0.5 again this.child += 1; - } - else { + } else { // Otherwise, move into the new component this.component = component; this.block = component.blocks[0]; @@ -85,16 +87,19 @@ class Cursor { if (this.block === null) { // If we are not in a block then we check if the component to the left is a TextComponent // If it is, we remove it, else we do nothing. - let prevComponent = this.expression.components[Math.floor(this.position)]; - if (prevComponent instanceof TextComponent || prevComponent instanceof MJXGUISymbol) { + let prevComponent = + this.expression.components[Math.floor(this.position)]; + if ( + prevComponent instanceof TextComponent || + prevComponent instanceof MJXGUISymbol + ) { this.position = Math.floor(this.position); this.component = prevComponent; this.block = prevComponent.blocks[0]; this.child = -0.5; this.removeComponent(); } - } - else if (this.component.parent === null) { + } else if (this.component.parent === null) { // Find the component in the expression and remove it, change the cursor object's // component and block pointers for (let i = 0; i < this.expression.components.length; i++) { @@ -107,11 +112,10 @@ class Cursor { this.component = null; this.block = null; this.child = -0.5; - } - else { + } else { // Capture the block above us to move into after we remove this component let parentBlock = this.component.parent; - for (let i = 0; i < parentBlock.children.length; i++) { + for (let i = 0; i < parentBlock.children.length; i++) { if (parentBlock.children[i] === this.component) { parentBlock.removeChild(i); this.child = i - 0.5; @@ -126,28 +130,21 @@ class Cursor { keyPress(event) { if (characters.has(event.key)) { this.addText(event.key); - } - else if (event.key === 'ArrowLeft') { + } else if (event.key === 'ArrowLeft') { this.seekLeft(); - } - else if (event.key === 'ArrowRight') { + } else if (event.key === 'ArrowRight') { this.seekRight(); - } - else if (event.key === 'Backspace') { + } else if (event.key === 'Backspace') { this.backspace(); - } - else if (event.key === 'Enter') { + } else if (event.key === 'Enter') { document.getElementById('mjxgui_save_equation').click(); - } - else if (event.key === ' ') { + } else if (event.key === ' ') { let _ = new MJXGUISymbol(this.block, '\\:\\:'); this.addComponent(_); - } - else if (event.key === '\\') { + } else if (event.key === '\\') { let _ = new MJXGUISymbol(this.block, '\\backslash'); this.addComponent(_); - } - else if (['$','#','%','&','_','{','}'].includes(event.key)) { + } else if (['$', '#', '%', '&', '_', '{', '}'].includes(event.key)) { let _ = new MJXGUISymbol(this.block, `\\${event.key}`); this.addComponent(_); } @@ -160,15 +157,19 @@ class Cursor { else if (this.block === null) { this.position += 0.5; // If the component at this index is a MJXGUISymbol or a TextComponent, skip it and go to the next - if (this.expression.components[this.position] instanceof TextComponent || this.expression.components[this.position] instanceof MJXGUISymbol) { + if ( + this.expression.components[this.position] instanceof + TextComponent || + this.expression.components[this.position] instanceof + MJXGUISymbol + ) { // If the component to the right of the cursor is a TextComponent, we skip it and // move one more position to the right and into the space between two components this.position += 0.5; this.child = -0.5; this.block = null; this.component = null; - } - else { + } else { // Otherwise we moved into a function // Set the block to be the last block of the function and set the child to be at the left most end this.component = this.expression.components[this.position]; @@ -176,13 +177,12 @@ class Cursor { this.child = -0.5; // this.position remains the same } - } - else { - if (this.child === this.block.children.length-0.5) { + } else { + if (this.child === this.block.children.length - 0.5) { // If we are at the end of the block, we want to move to a different block // and possibly a new component let pos = this.component.blocks.indexOf(this.block); - if (pos === this.component.blocks.length-1) { + if (pos === this.component.blocks.length - 1) { // If we are in the last block of the current component, we want to move out of this component if (this.component.parent === null) { // We are at the top level, our component and blocks become null @@ -190,31 +190,31 @@ class Cursor { this.block = null; this.child = -0.5; this.position += 0.5; - } - else { + } else { // Otherwise, we move one level above and our component becomes the parent component // our block becomes the block that the current component was in this.block = this.component.parent; // Record the position the current component is in. We move the cursor here - this.child = this.block.children.indexOf(this.component) + 0.5; + this.child = + this.block.children.indexOf(this.component) + 0.5; this.component = this.block.parent; } - } - else { + } else { // this.component and this.position remain the same - this.block = this.component.blocks[pos+1]; + this.block = this.component.blocks[pos + 1]; this.child = -0.5; } - } - else { + } else { // We are not at the end of the block // Detect the component to the right let nextComponent = this.block.children[Math.ceil(this.child)]; - if (nextComponent instanceof TextComponent || nextComponent instanceof MJXGUISymbol) { + if ( + nextComponent instanceof TextComponent || + nextComponent instanceof MJXGUISymbol + ) { // If it is a TextComponent or Symbol, skip it and move on this.child++; - } - else { + } else { this.component = nextComponent; this.block = this.component.blocks[0]; this.child = -0.5; @@ -228,24 +228,28 @@ class Cursor { else if (this.block === null) { this.position -= 0.5; // If the component at this index is a MJXGUISymbol or a TextComponent, we skip this component and go one more step backward - if (this.expression.components[this.position] instanceof TextComponent || this.expression.components[this.position] instanceof MJXGUISymbol) { + if ( + this.expression.components[this.position] instanceof + TextComponent || + this.expression.components[this.position] instanceof + MJXGUISymbol + ) { // If the component to the left of the cursor is a TextComponent, we skip it and // move one more position to the left and into the space between two components this.position -= 0.5; this.child = -0.5; this.block = null; this.component = null; - } - else { + } else { // Otherwise we moved into a Function // Set the block to be the last block of the function and set the child to be at the right most end this.component = this.expression.components[this.position]; - this.block = this.component.blocks[this.component.blocks.length-1]; - this.child = this.block.children.length-0.5; + this.block = + this.component.blocks[this.component.blocks.length - 1]; + this.child = this.block.children.length - 0.5; // this.position remains the same } - } - else { + } else { if (this.child === -0.5) { // If we are at the start of the block, we want to move to a different block // and possibly a new component @@ -258,33 +262,34 @@ class Cursor { this.block = null; this.child = -0.5; this.position -= 0.5; - } - else { + } else { // Otherwise, we move one level above and our component becomes the parent component // our block becomes the block that the current component was in this.block = this.component.parent; // Record the position the current component is in. We move the cursor there. - this.child = this.block.children.indexOf(this.component) - 0.5; + this.child = + this.block.children.indexOf(this.component) - 0.5; this.component = this.block.parent; } - } - else { + } else { // this.component and this.position remain the same - this.block = this.component.blocks[pos-1]; - this.child = this.block.children.length-0.5; + this.block = this.component.blocks[pos - 1]; + this.child = this.block.children.length - 0.5; } - } - else { + } else { // We are not at the start of the block // Detect the component to the left let prevComponent = this.block.children[Math.floor(this.child)]; - if (prevComponent instanceof TextComponent || prevComponent instanceof MJXGUISymbol) { + if ( + prevComponent instanceof TextComponent || + prevComponent instanceof MJXGUISymbol + ) { // If it is a TextComponent or Symbol, skip it and move on this.child--; - } - else { + } else { this.component = prevComponent; - this.block = this.component.blocks[this.component.blocks.length-1]; + this.block = + this.component.blocks[this.component.blocks.length - 1]; this.child = this.block.children.length - 0.5; } } @@ -298,29 +303,30 @@ class Cursor { if (this.block === null) { // If we are not in a block, we are in between two components, remove the previous component if it is // a TextComponent - let prevComponent = this.expression.components[Math.floor(this.position)]; - if (prevComponent instanceof TextComponent || prevComponent instanceof MJXGUISymbol) { + let prevComponent = + this.expression.components[Math.floor(this.position)]; + if ( + prevComponent instanceof TextComponent || + prevComponent instanceof MJXGUISymbol + ) { this.removeComponent(); - } - else { + } else { this.component = prevComponent; - this.block = this.component.blocks[this.component.blocks.length-1]; - this.child = this.block.children.length-0.5; + this.block = + this.component.blocks[this.component.blocks.length - 1]; + this.child = this.block.children.length - 0.5; this.position = Math.floor(this.position); } - } - else { + } else { if (this.component.isEmpty()) { this.removeComponent(); - } - else { + } else { if (this.child <= -0.5) { const blockPos = this.component.blocks.indexOf(this.block); if (blockPos === 0) return; - this.block = this.component.blocks[blockPos-1]; - this.child = this.block.children.length-0.5; - } - else { + this.block = this.component.blocks[blockPos - 1]; + this.child = this.block.children.length - 0.5; + } else { this.block.removeChild(Math.floor(this.child)); this.child--; } @@ -336,21 +342,20 @@ class Cursor { } toDisplayLatex() { - // Generate LaTeX to show in the display by adding a caret character to the expression. - // This is not the real LaTeX of the expression but the LaTeX resulting after we add + // Generate LaTeX to show in the display by adding a caret character to the expression. + // This is not the real LaTeX of the expression but the LaTeX resulting after we add // a caret as a | character in the expression let caret = new TextComponent(this.block); caret.blocks[0].addChild('|'); - + let frame = new FrameBox(this.block); if (this.block === null) { - // If we are not in any block, we just add the caret, generate latex + // If we are not in any block, we just add the caret, generate latex // and reset the components this.expression.add(caret, Math.ceil(this.position)); - } - else { - // We add the current component inside the frame, add the caret in the + } else { + // We add the current component inside the frame, add the caret in the // right position, generate latex and reset the components let i = this.component.blocks.indexOf(this.block); this.component.removeBlock(i); @@ -360,12 +365,10 @@ class Cursor { } let latex = this.toLatex(); - if (this.block === null) { this.expression.remove(Math.ceil(this.position)); - } - else { + } else { let i = this.component.blocks.indexOf(frame); this.component.removeBlock(i); this.component.addBlock(this.block, i); @@ -376,11 +379,14 @@ class Cursor { } updateDisplay() { - if (this.display instanceof String || typeof this.display === 'string') { - this.display = document.getElementById(this.display); + if ( + this.display instanceof String || + typeof this.display === 'string' + ) { + this.display = document.querySelector(this.display); } MathJax.typesetClear([this.display]); this.display.innerHTML = '$$' + this.toDisplayLatex() + '$$'; MathJax.typesetPromise([this.display]).then(() => {}); } -} \ No newline at end of file +} diff --git a/src/modules/editor.html b/src/modules/editor.html index bcf6fce..235a881 100644 --- a/src/modules/editor.html +++ b/src/modules/editor.html @@ -1,4 +1,4 @@ -
+
@@ -7,7 +7,7 @@
- + @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@
-
+
α Letters
± Symbols
Σ Functions
@@ -183,4 +183,4 @@
-
\ No newline at end of file +
\ No newline at end of file diff --git a/src/modules/editor.min.html b/src/modules/editor.min.html index 58eda79..4704637 100644 --- a/src/modules/editor.min.html +++ b/src/modules/editor.min.html @@ -1 +1 @@ -
α Letters
± Symbols
Σ Functions
Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
× ÷ · ± < > =
Σ Π limsincostancscseccotarcsinarccosarctan
\ No newline at end of file +
α Letters
± Symbols
Σ Functions
Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
× ÷ · ± < > =
Σ Π limsincostancscseccotarcsinarccosarctan
\ No newline at end of file diff --git a/src/modules/form-input.html b/src/modules/form-input.html new file mode 100644 index 0000000..8736f63 --- /dev/null +++ b/src/modules/form-input.html @@ -0,0 +1,4 @@ +
+ +
Equation Preview
+
diff --git a/src/modules/form-input.min.html b/src/modules/form-input.min.html new file mode 100644 index 0000000..e087a90 --- /dev/null +++ b/src/modules/form-input.min.html @@ -0,0 +1 @@ +
Equation Preview
\ No newline at end of file diff --git a/src/modules/ui.js b/src/modules/ui.js index 05c9042..59ac155 100644 --- a/src/modules/ui.js +++ b/src/modules/ui.js @@ -129,7 +129,6 @@ class MJXGUI { this.successCallback = successCallback; this.eqnHistory = []; this.expression = new Expression(); - this.cursor = new Cursor(this.expression, '_mjxgui_editor_display'); this.isMobileDevice = 'ontouchstart' in document.documentElement; this.pseudoMobileKeyboard = null; this.showUI = () => { @@ -151,6 +150,7 @@ class MJXGUI { } this.constructUI(); + this.cursor = new Cursor(this.expression, this.eqnDisplay); this.elements.forEach(el => { el.addEventListener('click', this.showUI); }); @@ -215,21 +215,20 @@ class MJXGUI { constructUI() { // Injects the UI HTML into the DOM and binds the needed event listeners const editorDiv = document.createElement('div'); - editorDiv.id = '_mjxgui_editor_window'; editorDiv.classList.add('_mjxgui_editor_window'); editorDiv.dataset.visible = 'false'; editorDiv.innerHTML = - '
α Letters
± Symbols
Σ Functions
Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
× ÷ · ± < > =
Σ Π limsincostancscseccotarcsinarccosarctan
'; + '
α Letters
± Symbols
Σ Functions
Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
× ÷ · ± < > =
Σ Π limsincostancscseccotarcsinarccosarctan
'; if (this.options.theme?.toLowerCase().trim() === 'dark') { editorDiv.classList.add('_mjxgui_dark_theme'); } this.editorWindow = editorDiv; - this.eqnDisplay = editorDiv.querySelector('#_mjxgui_editor_display'); + this.eqnDisplay = editorDiv.querySelector('._mjxgui_editor_display'); this.eqnDisplay.innerHTML = `${this.mathDelimiter} | ${this.mathDelimiter}`; this.pseudoMobileKeyboard = editorDiv.querySelector( - '#mjxgui-pseudo-mobile-keyboard', + '.mjxgui-pseudo-mobile-keyboard', ); const mjxguiTabButtons = editorDiv.querySelectorAll( '.mjxgui_tab_container', @@ -274,14 +273,14 @@ class MJXGUI { closeEditor.addEventListener('click', this.hideUI); const clearEquationButton = editorDiv.querySelector( - '#mjxgui_clear_equation', + '._mjxgui_clear_equation', ); clearEquationButton.addEventListener('click', () => { this.clearEquation(); }); const saveEquationButton = editorDiv.querySelector( - '#mjxgui_save_equation', + '._mjxgui_save_equation', ); saveEquationButton.addEventListener('click', () => { this.successCallback(); @@ -312,10 +311,11 @@ class MJXGUI { return this.cursor.toLatex(); } - // Removes all MJXGUI click listeners from the MJXGUI.elements, - // keeps the selector the same, and selects DOM elements again. - // Meant to be called if the DOM changes after DOM content is loaded - // or after the MJXGUI object is created. + /** + * Removes all MJXGUI click listeners for the current selector, + * selects DOM elements again, and rebinds MJXGUI click listeners. Meant + * to be called if the DOM changes after the MJXGUI instance is created. + */ rebindListeners() { this.elements.forEach(el => { el.removeEventListener('click', this.showUI); @@ -380,4 +380,66 @@ class MJXGUI { this.cursor.updateDisplay(); }); } + + /** + * Transforms an element into a button that allows users to enter an equation using MJXGUI. + * Stores the resulting LaTeX of the equation as the value of the input. + * @param selector A CSS selector that identifies the input(s) to be converted + * @param options An object of options for configuring the MJXGUI widget + */ + static createEquationInput(selector, options = {}) { + const inputs = document.querySelectorAll(selector); + const formInputHTML = + '
'; + for (let i = 0; i < inputs.length; i++) { + let inp = inputs[i]; + inp.style.display = 'none'; + inp.value = ''; + + const newEl = document.createElement('div'); + newEl.classList.add('_mjxgui_equation_input_wrapper'); + newEl.innerHTML = formInputHTML; + inp.insertAdjacentElement('afterend', newEl); + + const inputEl = newEl.querySelector('._mjxgui_equation_input'); + const inpButton = newEl.querySelector( + '._mjxgui_insert_equation_button', + ); + const eqnDisplay = newEl.querySelector( + '._mjxgui_equation_input_preview', + ); + inpButton.id = `_mjxgui_insert_equation_button_${i}`; + + const widget = new MJXGUI( + `#_mjxgui_insert_equation_button_${i}`, + function () {}, + options, + ); + widget.successCallback = function () { + const latex = widget.getLatex(); + if (latex.length > 0) { + // Set input value and show equation preview + inp.value = latex; + MathJax.typesetClear([eqnDisplay]); + eqnDisplay.innerHTML = `$ ${latex} $`; + MathJax.typesetPromise([eqnDisplay]).then(() => {}); + + inpButton.textContent = 'Edit'; + } else { + inp.value = ''; + MathJax.typesetClear([eqnDisplay]); + eqnDisplay.innerHTML = ''; + inpButton.textContent = 'Add'; + } + + if (inp.validity.valid) { + inputEl.classList.remove('_mjxgui_equation_input_invalid'); + inputEl.classList.add('_mjxgui_equation_input_valid'); + } else { + inputEl.classList.add('_mjxgui_equation_input_invalid'); + inputEl.classList.remove('_mjxgui_equation_input_valid'); + } + }; + } + } }