MDL-43855 Atto: Add an equation editor
[moodle.git] / lib / editor / atto / plugins / equation / yui / src / button / js / button.js
CommitLineData
8bf5ad67
DW
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/**
17 * Atto text editor equation plugin.
18 *
19 * @package editor-atto
20 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23M.atto_equation = M.atto_equation || {
24 /**
25 * The window used to get the equation details.
26 *
27 * @property dialogue
28 * @type M.core.dialogue
29 * @default null
30 */
31 dialogue : null,
32
33 /**
34 * The selection object returned by the browser.
35 *
36 * @property selection
37 * @type Range
38 * @default null
39 */
40 selection : null,
41
42 /**
43 * A mapping of elementids to contextids.
44 *
45 * @property contextids
46 * @type Object
47 * @default {}
48 */
49 contextids : {},
50
51 /**
52 * A nested object containing a the configured list of tex examples.
53 *
54 * @property library
55 * @type Object
56 * @default {}
57 */
58 library : {},
59
60 /**
61 * The last cursor index in the source.
62 *
63 * @property lastcursor
64 * @type Integer
65 * @default 0
66 */
67 lastcursor : 0,
68
69 /**
70 * Display the chooser dialogue.
71 *
72 * @method display_chooser
73 * @param Event e
74 * @param string elementid
75 */
76 display_chooser : function(e, elementid) {
77 e.preventDefault();
78 if (!M.editor_atto.is_active(elementid)) {
79 M.editor_atto.focus(elementid);
80 }
81 M.atto_equation.selection = M.editor_atto.get_selection();
82 if (M.atto_equation.selection !== false && (!M.atto_equation.selection.collapsed)) {
83 var dialogue;
84 if (!M.atto_equation.dialogue) {
85 dialogue = new M.core.dialogue({
86 visible: false,
87 modal: true,
88 close: true,
89 draggable: true,
90 width: '800px'
91 });
92 } else {
93 dialogue = M.atto_equation.dialogue;
94 }
95
96 dialogue.render();
97 dialogue.set('bodyContent', M.atto_equation.get_form_content(elementid));
98 dialogue.set('headerContent', M.util.get_string('pluginname', 'atto_equation'));
99
100 var tabview = new Y.TabView({
101 srcNode: '#atto_equation_library'
102 });
103
104 tabview.render();
105 dialogue.show();
106 M.atto_equation.resolve_equation();
107 M.atto_equation.update_preview(false, elementid);
108 M.atto_equation.dialogue = dialogue;
109 }
110 },
111
112 /**
113 * Add this button to the form.
114 *
115 * @method init
116 * @param {Object} params
117 */
118 init : function(params) {
119 var iconurl = M.util.image_url('e/math', 'core');
120
121 if (params.texfilteractive) {
122 // Save the elementid/contextid mapping.
123 this.contextids[params.elementid] = params.contextid;
124 // Save the button library.
125 this.library = params.library;
126
127 // Add the button to the toolbar.
128 M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser);
129 }
130 },
131
132 /**
133 * If there is selected text and it is part of an equation,
134 * extract the equation (and set it in the form).
135 *
136 * @method resolve_equation
137 */
138 resolve_equation : function() {
139 // Find the equation in the surrounding text.
140 var selectednode = M.editor_atto.get_selection_parent_node(),
141 text,
142 equation;
143
144 // Note this is a document fragment and YUI doesn't like them.
145 if (!selectednode) {
146 return;
147 }
148
149 text = Y.one(selectednode).get('text');
150 // We use space or not space because . does not match new lines.
151 pattern = /\$\$[\S\s]*\$\$/;
152 equation = pattern.exec(text);
153 if (equation && equation.length) {
154 equation = equation.pop();
155 // Replace the equation.
156 equation = equation.substring(2, equation.length - 2);
157 Y.one('#atto_equation_equation').set('text', equation);
158 }
159 },
160
161 /**
162 * The OK button has been pressed - make the changes to the source.
163 *
164 * @method set_equation
165 * @param {Y.Event} e
166 * @param {String} elementid
167 */
168 set_equation : function(e, elementid) {
169 var input,
170 selectednode,
171 text,
172 pattern,
173 equation,
174 value;
175
176 e.preventDefault();
177 M.atto_equation.dialogue.hide();
178 M.editor_atto.set_selection(M.atto_equation.selection);
179
180 input = e.currentTarget.ancestor('.atto_form').one('textarea');
181
182 value = input.get('value');
183 if (value !== '') {
184 value = '$$ ' + value.trim() + ' $$';
185 selectednode = Y.one(M.editor_atto.get_selection_parent_node()),
186 text = selectednode.get('text');
187 pattern = /\$\$[\S\s]*\$\$/;
188 equation = pattern.exec(text);
189 if (equation && equation.length) {
190 // Replace the equation.
191 equation = equation.pop();
192 text = text.replace(equation, '$$' + value + '$$');
193 selectednode.set('text', text);
194 } else {
195 // Insert the new equation.
196 if (document.selection && document.selection.createRange().pasteHTML) {
197 document.selection.createRange().pasteHTML(value);
198 } else {
199 document.execCommand('insertHTML', false, value);
200 }
201
202 }
203
204 // Clean the YUI ids from the HTML.
205 M.editor_atto.text_updated(elementid);
206 }
207 },
208
209 /**
210 * Update the preview div to match the current equation.
211 *
212 * @param Event e - unused
213 * @param String elementid - The editor elementid.
214 * @method update_preview
215 */
216 update_preview : function(e, elementid) {
217 var textarea = Y.one('#atto_equation_equation');
218 var equation = textarea.get('value'), url, preview;
219 var prefix = '';
220 var cursorlatex = '\\square ' ;
221
222 var currentpos = textarea.get('selectionStart');
223 if (!currentpos) {
224 currentpos = 0;
225 }
226 // Move the cursor so it does not break expressions.
227 //
228 while (equation.charAt(currentpos) === '\\' && currentpos > 0) {
229 currentpos -= 1;
230 }
231 var ischar = /[\w\{\}]/;
232 while (ischar.test(equation.charAt(currentpos)) && currentpos < equation.length) {
233 currentpos += 1;
234 }
235 // Save the cursor position - for insertion from the library.
236 this.lastcursorpos = currentpos;
237 equation = prefix + equation.substring(0, currentpos) + cursorlatex + equation.substring(currentpos);
238 if (e) {
239 e.preventDefault();
240 }
241 url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
242 params = {
243 sesskey: M.cfg.sesskey,
244 contextid: this.contextids[elementid],
245 action : 'filtertext',
246 text : '$$ ' + equation + ' $$'
247 };
248
249
250 preview = Y.io(url, { sync: true,
251 data: params });
252 if (preview.status === 200) {
253 Y.one('#atto_equation_preview').setHTML(preview.responseText);
254 }
255 },
256
257 /**
258 * Return the HTML of the form to show in the dialogue.
259 *
260 * @method get_form_content
261 * @param string elementid
262 * @return string
263 */
264 get_form_content : function(elementid) {
265 var content = Y.Node.create('<form class="atto_form">' +
266 this.get_library_html(elementid) +
267 '<label for="atto_equation_equation">' + M.util.get_string('editequation', 'atto_equation') +
268 '</label>' +
269 '<textarea class="fullwidth" id="atto_equation_equation" rows="8"></textarea><br/>' +
270 '<p>' + M.util.get_string('editequation_desc', 'atto_equation') + '</p>' +
271 '<label for="atto_equation_preview">' + M.util.get_string('preview', 'atto_equation') +
272 '</label>' +
273 '<div class="fullwidth" id="atto_equation_preview"></div>' +
274 '<div class="mdl-align">' +
275 '<br/>' +
276 '<button id="atto_equation_submit">' +
277 M.util.get_string('saveequation', 'atto_equation') +
278 '</button>' +
279 '</div>' +
280 '</form>');
281
282 content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid);
283 content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid);
284 content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid);
285 content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid);
286
287 content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid);
288
289 return content;
290 },
291
292 /**
293 * Reponse to button presses in the tex library panels.
294 *
295 * @method select_library_item
296 * @param Event event
297 * @param string elementid
298 * @return string
299 */
300 select_library_item : function(event, elementid) {
301 var tex = event.currentTarget.getAttribute('data-tex');
302
303 event.preventDefault();
304
305 input = event.currentTarget.ancestor('.atto_form').one('textarea');
306
307 value = input.get('value');
308
309 value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length);
310
311 input.set('value', value);
312 M.atto_equation.update_preview(false, elementid);
313 input.focus();
314 },
315
316 /**
317 * Return the HTML for rendering the library of predefined buttons.
318 *
319 * @method get_library_html
320 * @param string elementid
321 * @return string
322 */
323 get_library_html : function(elementid) {
324 var content = '<div id="atto_equation_library">', i = 0, group = 1;
325 content += '<ul>';
326 for (group = 1; group < 5; group++) {
327 content += '<li><a href="#atto_equation_library' + group + '">' + M.util.get_string('librarygroup' + group, 'atto_equation') + '</a></li>';
328 }
329 content += '</ul>';
330 content += '<div>';
331 for (group = 1; group < 5; group++) {
332 content += '<div id="atto_equation_library' + group + '">';
333 var examples = this.library['group' + group].split("\n");
334 for (i = 0; i < examples.length; i++) {
335 if (examples[i]) {
336 examples[i] = Y.Escape.html(examples[i]);
337 content += '<button data-tex="' + examples[i] + '" title="' + examples[i] + '">$$' + examples[i] + '$$</button>';
338 }
339 }
340 content += '</div>';
341 }
342 content += '</div>';
343 content += '</div>';
344
345 var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
346 var params = {
347 sesskey: M.cfg.sesskey,
348 contextid: this.contextids[elementid],
349 action : 'filtertext',
350 text : content
351 };
352
353 preview = Y.io(url, { sync: true, data: params, method: 'POST'});
354
355 if (preview.status === 200) {
356 content = preview.responseText;
357 }
358 return content;
359 }
360};