MDL-44226 editor_atto: refactored the accessibility checker to make it usable by...
[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();
3ee53a42
DW
106 var equation = M.atto_equation.resolve_equation();
107 if (equation) {
108 Y.one('#atto_equation_equation').set('text', equation);
109 }
8bf5ad67
DW
110 M.atto_equation.update_preview(false, elementid);
111 M.atto_equation.dialogue = dialogue;
112 }
113 },
114
115 /**
116 * Add this button to the form.
117 *
118 * @method init
119 * @param {Object} params
120 */
121 init : function(params) {
122 var iconurl = M.util.image_url('e/math', 'core');
123
124 if (params.texfilteractive) {
125 // Save the elementid/contextid mapping.
126 this.contextids[params.elementid] = params.contextid;
127 // Save the button library.
128 this.library = params.library;
129
130 // Add the button to the toolbar.
131 M.editor_atto.add_toolbar_button(params.elementid, 'equation', iconurl, params.group, this.display_chooser);
3ee53a42
DW
132 // Attach an event listner to watch for "changes" in the contenteditable.
133 // This includes cursor changes, we check if the button should be active or not, based
134 // on the text selection.
135 var editable = M.editor_atto.get_editable_node(params.elementid);
136 editable.on('atto:selectionchanged', function(e) {
137 if (M.atto_equation.resolve_equation() !== false) {
138 M.editor_atto.add_widget_highlight(e.elementid, 'equation');
139 } else {
140 M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
141 }
142 });
8bf5ad67
DW
143 }
144 },
145
146 /**
147 * If there is selected text and it is part of an equation,
148 * extract the equation (and set it in the form).
149 *
150 * @method resolve_equation
3ee53a42 151 * @return {String|Boolean} The equation or false.
8bf5ad67
DW
152 */
153 resolve_equation : function() {
154 // Find the equation in the surrounding text.
155 var selectednode = M.editor_atto.get_selection_parent_node(),
156 text,
157 equation;
158
159 // Note this is a document fragment and YUI doesn't like them.
160 if (!selectednode) {
3ee53a42 161 return false;
8bf5ad67
DW
162 }
163
164 text = Y.one(selectednode).get('text');
165 // We use space or not space because . does not match new lines.
166 pattern = /\$\$[\S\s]*\$\$/;
167 equation = pattern.exec(text);
168 if (equation && equation.length) {
169 equation = equation.pop();
170 // Replace the equation.
171 equation = equation.substring(2, equation.length - 2);
3ee53a42 172 return equation;
8bf5ad67 173 }
3ee53a42 174 return false;
8bf5ad67
DW
175 },
176
177 /**
178 * The OK button has been pressed - make the changes to the source.
179 *
180 * @method set_equation
181 * @param {Y.Event} e
182 * @param {String} elementid
183 */
184 set_equation : function(e, elementid) {
185 var input,
186 selectednode,
187 text,
188 pattern,
189 equation,
190 value;
191
192 e.preventDefault();
193 M.atto_equation.dialogue.hide();
194 M.editor_atto.set_selection(M.atto_equation.selection);
195
196 input = e.currentTarget.ancestor('.atto_form').one('textarea');
197
198 value = input.get('value');
199 if (value !== '') {
200 value = '$$ ' + value.trim() + ' $$';
201 selectednode = Y.one(M.editor_atto.get_selection_parent_node()),
202 text = selectednode.get('text');
203 pattern = /\$\$[\S\s]*\$\$/;
204 equation = pattern.exec(text);
205 if (equation && equation.length) {
206 // Replace the equation.
207 equation = equation.pop();
208 text = text.replace(equation, '$$' + value + '$$');
209 selectednode.set('text', text);
210 } else {
211 // Insert the new equation.
a30a40cb 212 M.editor_atto.insert_html_at_focus_point(value);
8bf5ad67
DW
213 }
214
215 // Clean the YUI ids from the HTML.
216 M.editor_atto.text_updated(elementid);
217 }
218 },
219
220 /**
221 * Update the preview div to match the current equation.
222 *
223 * @param Event e - unused
224 * @param String elementid - The editor elementid.
225 * @method update_preview
226 */
227 update_preview : function(e, elementid) {
228 var textarea = Y.one('#atto_equation_equation');
229 var equation = textarea.get('value'), url, preview;
230 var prefix = '';
231 var cursorlatex = '\\square ' ;
232
233 var currentpos = textarea.get('selectionStart');
234 if (!currentpos) {
235 currentpos = 0;
236 }
237 // Move the cursor so it does not break expressions.
238 //
239 while (equation.charAt(currentpos) === '\\' && currentpos > 0) {
240 currentpos -= 1;
241 }
242 var ischar = /[\w\{\}]/;
243 while (ischar.test(equation.charAt(currentpos)) && currentpos < equation.length) {
244 currentpos += 1;
245 }
246 // Save the cursor position - for insertion from the library.
247 this.lastcursorpos = currentpos;
248 equation = prefix + equation.substring(0, currentpos) + cursorlatex + equation.substring(currentpos);
249 if (e) {
250 e.preventDefault();
251 }
252 url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
253 params = {
254 sesskey: M.cfg.sesskey,
255 contextid: this.contextids[elementid],
256 action : 'filtertext',
257 text : '$$ ' + equation + ' $$'
258 };
259
260
261 preview = Y.io(url, { sync: true,
262 data: params });
263 if (preview.status === 200) {
264 Y.one('#atto_equation_preview').setHTML(preview.responseText);
265 }
266 },
267
268 /**
269 * Return the HTML of the form to show in the dialogue.
270 *
271 * @method get_form_content
272 * @param string elementid
273 * @return string
274 */
275 get_form_content : function(elementid) {
276 var content = Y.Node.create('<form class="atto_form">' +
277 this.get_library_html(elementid) +
278 '<label for="atto_equation_equation">' + M.util.get_string('editequation', 'atto_equation') +
279 '</label>' +
280 '<textarea class="fullwidth" id="atto_equation_equation" rows="8"></textarea><br/>' +
281 '<p>' + M.util.get_string('editequation_desc', 'atto_equation') + '</p>' +
282 '<label for="atto_equation_preview">' + M.util.get_string('preview', 'atto_equation') +
283 '</label>' +
284 '<div class="fullwidth" id="atto_equation_preview"></div>' +
285 '<div class="mdl-align">' +
286 '<br/>' +
287 '<button id="atto_equation_submit">' +
288 M.util.get_string('saveequation', 'atto_equation') +
289 '</button>' +
290 '</div>' +
291 '</form>');
292
293 content.one('#atto_equation_submit').on('click', M.atto_equation.set_equation, this, elementid);
294 content.one('#atto_equation_equation').on('valuechange', M.atto_equation.update_preview, this, elementid);
295 content.one('#atto_equation_equation').on('keyup', M.atto_equation.update_preview, this, elementid);
296 content.one('#atto_equation_equation').on('mouseup', M.atto_equation.update_preview, this, elementid);
297
298 content.delegate('click', M.atto_equation.select_library_item, '#atto_equation_library button', this, elementid);
299
300 return content;
301 },
302
303 /**
304 * Reponse to button presses in the tex library panels.
305 *
306 * @method select_library_item
307 * @param Event event
308 * @param string elementid
309 * @return string
310 */
311 select_library_item : function(event, elementid) {
312 var tex = event.currentTarget.getAttribute('data-tex');
313
314 event.preventDefault();
315
316 input = event.currentTarget.ancestor('.atto_form').one('textarea');
317
318 value = input.get('value');
319
320 value = value.substring(0, this.lastcursorpos) + tex + value.substring(this.lastcursorpos, value.length);
321
322 input.set('value', value);
323 M.atto_equation.update_preview(false, elementid);
324 input.focus();
325 },
326
327 /**
328 * Return the HTML for rendering the library of predefined buttons.
329 *
330 * @method get_library_html
331 * @param string elementid
332 * @return string
333 */
334 get_library_html : function(elementid) {
335 var content = '<div id="atto_equation_library">', i = 0, group = 1;
336 content += '<ul>';
337 for (group = 1; group < 5; group++) {
338 content += '<li><a href="#atto_equation_library' + group + '">' + M.util.get_string('librarygroup' + group, 'atto_equation') + '</a></li>';
339 }
340 content += '</ul>';
341 content += '<div>';
342 for (group = 1; group < 5; group++) {
343 content += '<div id="atto_equation_library' + group + '">';
344 var examples = this.library['group' + group].split("\n");
345 for (i = 0; i < examples.length; i++) {
346 if (examples[i]) {
347 examples[i] = Y.Escape.html(examples[i]);
348 content += '<button data-tex="' + examples[i] + '" title="' + examples[i] + '">$$' + examples[i] + '$$</button>';
349 }
350 }
351 content += '</div>';
352 }
353 content += '</div>';
354 content += '</div>';
355
356 var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
357 var params = {
358 sesskey: M.cfg.sesskey,
359 contextid: this.contextids[elementid],
360 action : 'filtertext',
361 text : content
362 };
363
364 preview = Y.io(url, { sync: true, data: params, method: 'POST'});
365
366 if (preview.status === 200) {
367 content = preview.responseText;
368 }
369 return content;
370 }
371};