Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/ //
2
// Moodle is free software: you can redistribute it and/or modify
3
// it under the terms of the GNU General Public License as published by
4
// the Free Software Foundation, either version 3 of the License, or
5
// (at your option) any later version.
6
//
7
// Moodle is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
// GNU General Public License for more details.
11
//
12
// You should have received a copy of the GNU General Public License
13
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
14
 
15
/**
16
 * Mathjax JS Loader.
17
 *
18
 * @module filter_mathjaxloader/loader
19
 * @copyright 2014 Damyon Wiese  <damyon@moodle.com>
20
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21
 */
22
import {
23
    eventTypes,
24
    notifyFilterContentRenderingComplete,
25
} from 'core_filters/events';
26
 
27
/**
1441 ariadna 28
 * URL to MathJax.
29
 * @type {string|null}
1 efrain 30
 */
1441 ariadna 31
let mathJaxUrl = null;
1 efrain 32
 
33
/**
1441 ariadna 34
 * Promise that is resolved when MathJax was loaded.
35
 * @type {Promise|null}
1 efrain 36
 */
1441 ariadna 37
let mathJaxLoaded = null;
1 efrain 38
 
39
/**
40
 * Called by the filter when it is active on any page.
1441 ariadna 41
 * This does not load MathJAX yet - it adds the configuration in case it gets loaded later.
1 efrain 42
 * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax.
43
 *
1441 ariadna 44
 * @param {Object} params List of configuration params containing mathjaxurl, mathjaxconfig (text) and lang
1 efrain 45
 */
46
export const configure = (params) => {
1441 ariadna 47
    let config = {};
48
    try {
49
        if (params.mathjaxconfig !== '') {
50
            config = JSON.parse(params.mathjaxconfig);
51
        }
52
    }
53
    catch (e) {
54
        window.console.error('Invalid JSON in mathjaxconfig.', e);
55
    }
56
    if (typeof config != 'object') {
57
        config = {};
58
    }
59
    if (typeof config.loader !== 'object') {
60
        config.loader = {};
61
    }
62
    if (!Array.isArray(config.loader.load)) {
63
        config.loader.load = [];
64
    }
65
    if (typeof config.startup !== 'object') {
66
        config.startup = {};
67
    }
1 efrain 68
 
1441 ariadna 69
    // Always ensure that ui/safe is in the list. Otherwise, there is a risk of XSS.
70
    // https://docs.mathjax.org/en/v3.2-latest/options/safe.html.
71
    if (!config.loader.load.includes('ui/safe')) {
72
        config.loader.load.push('ui/safe');
73
    }
1 efrain 74
 
1441 ariadna 75
    // This filter controls what elements to typeset.
76
    config.startup.typeset = false;
77
 
78
    // Let's still set the locale even if the localization is not yet ported to version 3.2.2
79
    // https://docs.mathjax.org/en/v3.2-latest/upgrading/v2.html#not-yet-ported-to-version-3.
80
    config.locale = params.lang;
81
 
82
    mathJaxUrl = params.mathjaxurl;
83
    window.MathJax = config;
84
 
1 efrain 85
    // Listen for events triggered when new text is added to a page that needs
86
    // processing by a filter.
87
    document.addEventListener(eventTypes.filterContentUpdated, contentUpdated);
88
};
89
 
90
/**
91
 * Add the node to the typeset queue.
92
 *
93
 * @param {HTMLElement} node The Node to be processed by MathJax
94
 * @private
95
 */
96
const typesetNode = (node) => {
97
    if (!(node instanceof HTMLElement)) {
98
        // We may have been passed a #text node.
99
        // These cannot be formatted.
100
        return;
101
    }
102
 
1441 ariadna 103
    loadMathJax().then(() => {
104
        // Chain the calls to typesetPromise as it is recommended.
105
        // https://docs.mathjax.org/en/v3.2-latest/web/typeset.html#handling-asynchronous-typesetting.
106
        window.MathJax.startup.promise = window.MathJax.startup.promise
107
            .then(() => window.MathJax.typesetPromise([node]))
108
            .then(() => {
109
                notifyFilterContentRenderingComplete([node]);
110
            })
111
            .catch(e => {
112
                window.console.log(e);
113
            });
114
    });
1 efrain 115
};
116
 
117
/**
118
 * Called by the filter when an equation is found while rendering the page.
119
 */
120
export const typeset = () => {
1441 ariadna 121
    const elements = document.getElementsByClassName('filter_mathjaxloader_equation');
122
    for (const element of elements) {
123
        typesetNode(element);
1 efrain 124
    }
125
};
126
 
127
/**
128
 * Handle content updated events - typeset the new content.
129
 *
130
 * @param {CustomEvent} event - Custom event with "nodes" indicating the root of the updated nodes.
131
 */
132
export const contentUpdated = (event) => {
133
    let listOfElementContainMathJax = [];
134
    let hasMathJax = false;
135
    // The list of HTMLElements in an Array.
136
    event.detail.nodes.forEach((node) => {
137
        if (!(node instanceof HTMLElement)) {
138
            // We may have been passed a #text node.
139
            return;
140
        }
141
        const mathjaxElements = node.querySelectorAll('.filter_mathjaxloader_equation');
142
        if (mathjaxElements.length > 0) {
143
            hasMathJax = true;
144
        }
145
        listOfElementContainMathJax.push(mathjaxElements);
146
    });
1441 ariadna 147
 
1 efrain 148
    if (!hasMathJax) {
149
        return;
150
    }
1441 ariadna 151
 
1 efrain 152
    listOfElementContainMathJax.forEach((mathjaxElements) => {
153
        mathjaxElements.forEach((node) => typesetNode(node));
154
    });
155
};
1441 ariadna 156
 
157
/**
158
 * Load the MathJax script.
159
 *
160
 * @return Promise that is resolved when MathJax was loaded.
161
 */
162
export const loadMathJax = () => {
163
    if (!mathJaxLoaded) {
164
        if (!mathJaxUrl) {
165
            return Promise.reject(new Error('URL to MathJax not set.'));
166
        }
167
 
168
        mathJaxLoaded = new Promise((resolve, reject) => {
169
            const script = document.createElement('script');
170
            script.type = 'text/javascript';
171
            script.onload = resolve;
172
            script.onerror = reject;
173
            script.src = mathJaxUrl;
174
            document.getElementsByTagName('head')[0].appendChild(script);
175
        });
176
    }
177
    return mathJaxLoaded;
178
};