aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/EquationEditor.tsx
blob: 23d273523de563cc9d2639c659c7b07388e8331d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import React, { Component, createRef } from 'react';

// Import JQuery, required for the functioning of the equation editor
import $ from 'jquery';
import './EquationEditor.scss';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).jQuery = $;
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('mathquill/build/mathquill');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).MathQuill = (window as any).MathQuill.getInterface(1);

type EquationEditorProps = {
    onChange(latex: string): void;
    value: string;
    spaceBehavesLikeTab?: boolean;
    autoCommands: string;
    autoOperatorNames: string;
    onEnter?(): void;
};

/**
 * @typedef {EquationEditorProps} props
 * @prop {Function} onChange Triggered when content of the equation editor changes
 * @prop {string} value Content of the equation handler
 * @prop {boolean}[false] spaceBehavesLikeTab Whether spacebar should simulate tab behavior
 * @prop {string} autoCommands List of commands for which you only have to type the name of the
 * command with a \ in front of it. Examples: pi theta rho sum
 * @prop {string} autoOperatorNames List of operators for which you only have to type the name of the
 * operator with a \ in front of it. Examples: sin cos tan
 * @prop {Function} onEnter Triggered when enter is pressed in the equation editor
 * @extends {Component<EquationEditorProps>}
 */
class EquationEditor extends Component<EquationEditorProps> {
    element: React.RefObject<HTMLSpanElement>;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mathField: any;
    ignoreEditEvents: number;

    // Element needs to be in the class format and thus requires a constructor. The steps that are run
    // in the constructor is to make sure that React can succesfully communicate with the equation
    // editor.
    constructor(props: EquationEditorProps) {
        super(props);

        this.element = createRef<HTMLSpanElement>();
        this.mathField = null;

        // MathJax apparently fire 2 edit events on startup.
        this.ignoreEditEvents = 2;
    }

    componentDidMount() {
        const { onChange, value, spaceBehavesLikeTab, autoCommands, autoOperatorNames, onEnter } = this.props;

        const config = {
            handlers: {
                edit: () => {
                    if (this.ignoreEditEvents <= 0) onChange(this.mathField.latex());
                    else this.ignoreEditEvents -= 1;
                },
                enter: onEnter,
            },
            spaceBehavesLikeTab,
            autoCommands,
            autoOperatorNames,
        };

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.mathField = (window as any).MathQuill.MathField(this.element.current, config);
        this.mathField.latex(value || '');
    }

    componentDidUpdate(prevProps: Readonly<EquationEditorProps>): void {
        !prevProps.value && this.mathField.latex(this.props.value || '');
    }

    render() {
        return <span ref={this.element} style={{ border: '0px', boxShadow: 'None' }} />;
    }
}

export default EquationEditor;