MDL-64506 templates: Move BS2 btns' to BS4 btns'
[moodle.git] / lib / editor / atto / plugins / equation / yui / build / moodle-atto_equation-button / moodle-atto_equation-button-debug.js
CommitLineData
8bf5ad67
DW
1YUI.add('moodle-atto_equation-button', function (Y, NAME) {
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
62467795 19 * @package atto_equation
8bf5ad67
DW
20 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
62467795
AN
23
24/**
25 * Atto text editor equation plugin.
26 */
27
28/**
29 * Atto equation editor.
30 *
31 * @namespace M.atto_equation
32 * @class Button
33 * @extends M.editor_atto.EditorPlugin
34 */
62467795 35var COMPONENTNAME = 'atto_equation',
050159dc 36 LOGNAME = 'atto_equation',
62467795
AN
37 CSS = {
38 EQUATION_TEXT: 'atto_equation_equation',
39 EQUATION_PREVIEW: 'atto_equation_preview',
40 SUBMIT: 'atto_equation_submit',
41 LIBRARY: 'atto_equation_library',
050159dc
FM
42 LIBRARY_GROUPS: 'atto_equation_groups',
43 LIBRARY_GROUP_PREFIX: 'atto_equation_group'
62467795
AN
44 },
45 SELECTORS = {
050159dc
FM
46 LIBRARY: '.' + CSS.LIBRARY,
47 LIBRARY_GROUP: '.' + CSS.LIBRARY_GROUPS + ' > div > div',
62467795
AN
48 EQUATION_TEXT: '.' + CSS.EQUATION_TEXT,
49 EQUATION_PREVIEW: '.' + CSS.EQUATION_PREVIEW,
50 SUBMIT: '.' + CSS.SUBMIT,
51 LIBRARY_BUTTON: '.' + CSS.LIBRARY + ' button'
52 },
cc90cedc
DW
53 DELIMITERS = {
54 START: '\\(',
55 END: '\\)'
56 },
62467795
AN
57 TEMPLATES = {
58 FORM: '' +
59 '<form class="atto_form">' +
60 '{{{library}}}' +
36beb828 61 '<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{{get_string "editequation" component texdocsurl}}}</label>' +
557f44d9
AN
62 '<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" ' +
63 'id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
62467795 64 '<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
1b217025 65 '<div describedby="{{elementid}}_cursorinfo" class="well well-small p-1 fullwidth {{CSS.EQUATION_PREVIEW}}" ' +
557f44d9 66 'id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
24183668 67 '<div id="{{elementid}}_cursorinfo">{{get_string "cursorinfo" component}}</div>' +
62467795
AN
68 '<div class="mdl-align">' +
69 '<br/>' +
29551c4b 70 '<button class="btn btn-secondary {{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
62467795
AN
71 '</div>' +
72 '</form>',
73 LIBRARY: '' +
74 '<div class="{{CSS.LIBRARY}}">' +
1b217025 75 '<ul class="root nav nav-tabs m-b-1" role="tablist">' +
62467795 76 '{{#each library}}' +
1b217025
BB
77 '<li class="nav-item">' +
78 '<a class="nav-link" href="#{{../elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}_{{@key}}" ' +
79 ' role="tab" data-toggle="tab">' +
80 '{{get_string groupname ../component}}' +
81 '</a>' +
82 '</li>' +
62467795
AN
83 '{{/each}}' +
84 '</ul>' +
1b217025 85 '<div class="tab-content m-b-1 {{CSS.LIBRARY_GROUPS}}">' +
62467795 86 '{{#each library}}' +
1b217025
BB
87 '<div data-medium-type="{{CSS.LINK}}" class="tab-pane" ' +
88 'id="{{../elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}_{{@key}}">' +
050159dc
FM
89 '<div role="toolbar">' +
90 '{{#split "\n" elements}}' +
29551c4b 91 '<button class="btn btn-secondary" tabindex="-1" data-tex="{{this}}"' +
1b217025 92 'aria-label="{{this}}" title="{{this}}">' +
0610d2ca
DW
93 '{{../../DELIMITERS.START}}{{this}}{{../../DELIMITERS.END}}' +
94 '</button>' +
050159dc
FM
95 '{{/split}}' +
96 '</div>' +
62467795
AN
97 '</div>' +
98 '{{/each}}' +
99 '</div>' +
100 '</div>'
101 };
102
103Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
8bf5ad67
DW
104
105 /**
106 * The selection object returned by the browser.
107 *
62467795 108 * @property _currentSelection
8bf5ad67
DW
109 * @type Range
110 * @default null
62467795 111 * @private
8bf5ad67 112 */
62467795 113 _currentSelection: null,
8bf5ad67
DW
114
115 /**
62467795 116 * The cursor position in the equation textarea.
8bf5ad67 117 *
62467795
AN
118 * @property _lastCursorPos
119 * @type Number
8bf5ad67 120 * @default 0
62467795 121 * @private
8bf5ad67 122 */
62467795 123 _lastCursorPos: 0,
8bf5ad67
DW
124
125 /**
62467795 126 * A reference to the dialogue content.
8bf5ad67 127 *
62467795
AN
128 * @property _content
129 * @type Node
130 * @private
8bf5ad67 131 */
62467795 132 _content: null,
8bf5ad67 133
cc90cedc
DW
134 /**
135 * The source equation we are editing in the text.
136 *
137 * @property _sourceEquation
ea382dea 138 * @type Object
cc90cedc
DW
139 * @private
140 */
ea382dea 141 _sourceEquation: null,
cc90cedc 142
050159dc
FM
143 /**
144 * A reference to the tab focus set on each group.
145 *
146 * The keys are the IDs of the group, the value is the Node on which the focus is set.
147 *
148 * @property _groupFocus
149 * @type Object
150 * @private
151 */
152 _groupFocus: null,
153
ea382dea
AN
154 /**
155 * Regular Expression patterns used to pick out the equations in a String.
156 *
157 * @property _equationPatterns
158 * @type Array
159 * @private
160 */
161 _equationPatterns: [
162 // We use space or not space because . does not match new lines.
163 // $$ blah $$.
164 /\$\$([\S\s]+?)\$\$/,
165 // E.g. "\( blah \)".
166 /\\\(([\S\s]+?)\\\)/,
167 // E.g. "\[ blah \]".
168 /\\\[([\S\s]+?)\\\]/,
169 // E.g. "[tex] blah [/tex]".
170 /\[tex\]([\S\s]+?)\[\/tex\]/
171 ],
172
62467795 173 initializer: function() {
050159dc 174 this._groupFocus = {};
0610d2ca 175
cc90cedc
DW
176 // If there is a tex filter active - enable this button.
177 if (this.get('texfilteractive')) {
62467795
AN
178 // Add the button to the toolbar.
179 this.addButton({
180 icon: 'e/math',
181 callback: this._displayDialogue
8bf5ad67
DW
182 });
183
62467795
AN
184 // We need custom highlight logic for this button.
185 this.get('host').on('atto:selectionchanged', function() {
186 if (this._resolveEquation()) {
187 this.highlightButtons();
188 } else {
189 this.unHighlightButtons();
190 }
191 }, this);
cc90cedc
DW
192
193 // We need to convert these to a non dom node based format.
3a0bc0fd 194 this.editor.all('tex').each(function(texNode) {
557f44d9
AN
195 var replacement = Y.Node.create('<span>' +
196 DELIMITERS.START + ' ' + texNode.get('text') + ' ' + DELIMITERS.END +
197 '</span>');
cc90cedc
DW
198 texNode.replace(replacement);
199 });
8bf5ad67 200 }
cc90cedc 201
8bf5ad67
DW
202 },
203
204 /**
62467795 205 * Display the equation editor.
8bf5ad67 206 *
62467795
AN
207 * @method _displayDialogue
208 * @private
8bf5ad67 209 */
62467795
AN
210 _displayDialogue: function() {
211 this._currentSelection = this.get('host').getSelection();
8bf5ad67 212
62467795
AN
213 if (this._currentSelection === false) {
214 return;
215 }
8bf5ad67 216
aca67017
DW
217 // This needs to be done before the dialogue is opened because the focus will shift to the dialogue.
218 var equation = this._resolveEquation();
219
62467795
AN
220 var dialogue = this.getDialogue({
221 headerContent: M.util.get_string('pluginname', COMPONENTNAME),
1eb5839c 222 focusAfterHide: true,
e5ddec38 223 width: 600,
c1660772 224 focusOnShowSelector: SELECTORS.EQUATION_TEXT
62467795
AN
225 });
226
227 var content = this._getDialogueContent();
228 dialogue.set('bodyContent', content);
229
1b217025 230 content.one('.nav-item:first-child .nav-link').getDOMNode().click();
62467795 231
62467795 232 dialogue.show();
28de7771
DW
233 // Notify the filters about the modified nodes.
234 require(['core/event'], function(event) {
235 event.notifyFilterContentUpdated(dialogue.get('boundingBox').getDOMNode());
236 });
62467795 237
62467795
AN
238 if (equation) {
239 content.one(SELECTORS.EQUATION_TEXT).set('text', equation);
8bf5ad67 240 }
62467795 241 this._updatePreview(false);
8bf5ad67
DW
242 },
243
244 /**
245 * If there is selected text and it is part of an equation,
246 * extract the equation (and set it in the form).
247 *
62467795
AN
248 * @method _resolveEquation
249 * @private
3ee53a42 250 * @return {String|Boolean} The equation or false.
8bf5ad67 251 */
62467795
AN
252 _resolveEquation: function() {
253
8bf5ad67 254 // Find the equation in the surrounding text.
62467795 255 var selectedNode = this.get('host').getSelectionParentNode(),
ea382dea 256 selection = this.get('host').getSelection(),
8bf5ad67 257 text,
ea382dea
AN
258 returnValue = false;
259
aca67017
DW
260 // Prevent resolving equations when we don't have focus.
261 if (!this.get('host').isActive()) {
262 return false;
263 }
8bf5ad67
DW
264
265 // Note this is a document fragment and YUI doesn't like them.
62467795 266 if (!selectedNode) {
3ee53a42 267 return false;
8bf5ad67
DW
268 }
269
ea382dea
AN
270 // We don't yet have a cursor selection somehow so we can't possible be resolving an equation that has selection.
271 if (!selection || selection.length === 0) {
272 return false;
273 }
aca67017
DW
274
275 this.sourceEquation = null;
276
ea382dea 277 selection = selection[0];
cc90cedc 278
ea382dea 279 text = Y.one(selectedNode).get('text');
cc90cedc 280
557f44d9
AN
281 // For each of these patterns we have a RegExp which captures the inner component of the equation but also
282 // includes the delimiters.
ea382dea
AN
283 // We first run the RegExp adding the global flag ("g"). This ignores the capture, instead matching the entire
284 // equation including delimiters and returning one entry per match of the whole equation.
285 // We have to deal with multiple occurences of the same equation in a String so must be able to loop on the
286 // match results.
287 Y.Array.find(this._equationPatterns, function(pattern) {
288 // For each pattern in turn, find all whole matches (including the delimiters).
289 var patternMatches = text.match(new RegExp(pattern.source, "g"));
290
291 if (patternMatches && patternMatches.length) {
292 // This pattern matches at least once. See if this pattern matches our current position.
293 // Note: We return here to break the Y.Array.find loop - any truthy return will stop any subsequent
294 // searches which is the required behaviour of this function.
295 return Y.Array.find(patternMatches, function(match) {
296 // Check each occurrence of this match.
297 var startIndex = 0;
3a0bc0fd 298 while (text.indexOf(match, startIndex) !== -1) {
ea382dea
AN
299 // Determine whether the cursor is in the current occurrence of this string.
300 // Note: We do not support a selection exceeding the bounds of an equation.
301 var startOuter = text.indexOf(match, startIndex),
302 endOuter = startOuter + match.length,
303 startMatch = (selection.startOffset >= startOuter && selection.startOffset < endOuter),
304 endMatch = (selection.endOffset <= endOuter && selection.endOffset > startOuter);
305
306 if (startMatch && endMatch) {
307 // This match is in our current position - fetch the innerMatch data.
308 var innerMatch = match.match(pattern);
309 if (innerMatch && innerMatch.length) {
310 // We need the start and end of the inner match for later.
311 var startInner = text.indexOf(innerMatch[1], startOuter),
312 endInner = startInner + innerMatch[1].length;
313
314 // We'll be returning the inner match for use in the editor itself.
315 returnValue = innerMatch[1];
316
317 // Save all data for later.
318 this.sourceEquation = {
319 // Outer match data.
320 startOuterPosition: startOuter,
321 endOuterPosition: endOuter,
322 outerMatch: match,
323
324 // Inner match data.
325 startInnerPosition: startInner,
326 endInnerPosition: endInner,
327 innerMatch: innerMatch
328 };
329
330 // This breaks out of both Y.Array.find functions.
331 return true;
332 }
333 }
334
335 // Update the startIndex to match the end of the current match so that we can continue hunting
336 // for further matches.
337 startIndex = endOuter;
338 }
339 }, this);
cc90cedc 340 }
ea382dea 341 }, this);
cc90cedc 342
aca67017
DW
343 // We trim the equation when we load it and then add spaces when we save it.
344 if (returnValue !== false) {
345 returnValue = returnValue.trim();
346 }
ea382dea 347 return returnValue;
8bf5ad67
DW
348 },
349
350 /**
62467795 351 * Handle insertion of a new equation, or update of an existing one.
8bf5ad67 352 *
62467795
AN
353 * @method _setEquation
354 * @param {EventFacade} e
355 * @private
8bf5ad67 356 */
62467795 357 _setEquation: function(e) {
8bf5ad67 358 var input,
62467795 359 selectedNode,
8bf5ad67 360 text,
cc90cedc 361 value,
557f44d9
AN
362 host,
363 newText;
8bf5ad67 364
cc90cedc 365 host = this.get('host');
62467795 366
8bf5ad67 367 e.preventDefault();
62467795
AN
368 this.getDialogue({
369 focusAfterHide: null
370 }).hide();
8bf5ad67
DW
371
372 input = e.currentTarget.ancestor('.atto_form').one('textarea');
373
374 value = input.get('value');
375 if (value !== '') {
62467795
AN
376 host.setSelection(this._currentSelection);
377
ea382dea 378 if (this.sourceEquation) {
8bf5ad67 379 // Replace the equation.
cc90cedc
DW
380 selectedNode = Y.one(host.getSelectionParentNode());
381 text = selectedNode.get('text');
aca67017 382 value = ' ' + value + ' ';
3a0bc0fd 383 newText = text.slice(0, this.sourceEquation.startInnerPosition) +
ea382dea
AN
384 value +
385 text.slice(this.sourceEquation.endInnerPosition);
cc90cedc 386
ea382dea 387 selectedNode.set('text', newText);
8bf5ad67
DW
388 } else {
389 // Insert the new equation.
cc90cedc 390 value = DELIMITERS.START + ' ' + value + ' ' + DELIMITERS.END;
62467795 391 host.insertContentAtFocusPoint(value);
8bf5ad67
DW
392 }
393
394 // Clean the YUI ids from the HTML.
62467795 395 this.markUpdated();
8bf5ad67
DW
396 }
397 },
398
cc90cedc
DW
399 /**
400 * Smart throttle, only call a function every delay milli seconds,
441f94b2
DW
401 * and always run the last call. Y.throttle does not work here,
402 * because it calls the function immediately, the first time, and then
403 * ignores repeated calls within X seconds. This does not guarantee
404 * that the last call will be executed (which is required here).
cc90cedc
DW
405 *
406 * @param {function} fn
441f94b2 407 * @param {Number} delay Delay in milliseconds
cc90cedc
DW
408 * @method _throttle
409 * @private
410 */
411 _throttle: function(fn, delay) {
412 var timer = null;
3a0bc0fd 413 return function() {
cc90cedc
DW
414 var context = this, args = arguments;
415 clearTimeout(timer);
3a0bc0fd 416 timer = setTimeout(function() {
cc90cedc
DW
417 fn.apply(context, args);
418 }, delay);
419 };
420 },
421
8bf5ad67
DW
422 /**
423 * Update the preview div to match the current equation.
424 *
62467795
AN
425 * @param {EventFacade} e
426 * @method _updatePreview
427 * @private
8bf5ad67 428 */
62467795
AN
429 _updatePreview: function(e) {
430 var textarea = this._content.one(SELECTORS.EQUATION_TEXT),
431 equation = textarea.get('value'),
432 url,
62467795
AN
433 currentPos = textarea.get('selectionStart'),
434 prefix = '',
d8e2dc99 435 cursorLatex = '\\Downarrow ',
441f94b2
DW
436 isChar,
437 params;
62467795 438
62467795
AN
439 if (e) {
440 e.preventDefault();
441 }
442
d926ce71
AN
443 // Move the cursor so it does not break expressions.
444 // Start at the very beginning.
62467795
AN
445 if (!currentPos) {
446 currentPos = 0;
8bf5ad67 447 }
d926ce71
AN
448
449 // First move back to the beginning of the line.
450 while (equation.charAt(currentPos) === '\\' && currentPos >= 0) {
62467795 451 currentPos -= 1;
8bf5ad67 452 }
46045db2 453 isChar = /[a-zA-Z\{]/;
d926ce71 454 if (currentPos !== 0) {
46045db2
DS
455 if (equation.charAt(currentPos - 1) != '{') {
456 // Now match to the end of the line.
457 while (isChar.test(equation.charAt(currentPos)) &&
458 currentPos < equation.length &&
459 isChar.test(equation.charAt(currentPos - 1))) {
460 currentPos += 1;
461 }
d926ce71 462 }
8bf5ad67
DW
463 }
464 // Save the cursor position - for insertion from the library.
62467795
AN
465 this._lastCursorPos = currentPos;
466 equation = prefix + equation.substring(0, currentPos) + cursorLatex + equation.substring(currentPos);
8939ebac 467
cc90cedc
DW
468 equation = DELIMITERS.START + ' ' + equation + ' ' + DELIMITERS.END;
469 // Make an ajax request to the filter.
470 url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
471 params = {
472 sesskey: M.cfg.sesskey,
473 contextid: this.get('contextid'),
474 action: 'filtertext',
475 text: equation
476 };
477
3086c8ce
DT
478 Y.io(url, {
479 context: this,
480 data: params,
481 timeout: 500,
482 on: {
483 complete: this._loadPreview
484 }
441f94b2 485 });
3086c8ce
DT
486 },
487
488 /**
489 * Load returned preview text into preview
490 *
491 * @param {String} id
492 * @param {EventFacade} e
493 * @method _loadPreview
494 * @private
495 */
496 _loadPreview: function(id, preview) {
497 var previewNode = this._content.one(SELECTORS.EQUATION_PREVIEW);
441f94b2 498
cc90cedc
DW
499 if (preview.status === 200) {
500 previewNode.setHTML(preview.responseText);
3086c8ce 501
28de7771
DW
502 // Notify the filters about the modified nodes.
503 require(['core/event'], function(event) {
504 event.notifyFilterContentUpdated(previewNode.getDOMNode());
505 });
8bf5ad67
DW
506 }
507 },
508
509 /**
62467795
AN
510 * Return the dialogue content for the tool, attaching any required
511 * events.
8bf5ad67 512 *
62467795
AN
513 * @method _getDialogueContent
514 * @return {Node}
515 * @private
8bf5ad67 516 */
62467795
AN
517 _getDialogueContent: function() {
518 var library = this._getLibraryContent(),
3086c8ce 519 throttledUpdate = this._throttle(this._updatePreview, 500),
62467795
AN
520 template = Y.Handlebars.compile(TEMPLATES.FORM);
521
522 this._content = Y.Node.create(template({
523 elementid: this.get('host').get('elementid'),
524 component: COMPONENTNAME,
525 library: library,
36beb828 526 texdocsurl: this.get('texdocsurl'),
62467795
AN
527 CSS: CSS
528 }));
529
050159dc
FM
530 // Sets the default focus.
531 this._content.all(SELECTORS.LIBRARY_GROUP).each(function(group) {
532 // The first button gets the focus.
533 this._setGroupTabFocus(group, group.one('button'));
534 // Sometimes the filter adds an anchor in the button, no tabindex on that.
535 group.all('button a').setAttribute('tabindex', '-1');
536 }, this);
537
538 // Keyboard navigation in groups.
539 this._content.delegate('key', this._groupNavigation, 'down:37,39', SELECTORS.LIBRARY_BUTTON, this);
540
62467795 541 this._content.one(SELECTORS.SUBMIT).on('click', this._setEquation, this);
3086c8ce
DT
542 this._content.one(SELECTORS.EQUATION_TEXT).on('valuechange', throttledUpdate, this);
543 this._content.one(SELECTORS.EQUATION_TEXT).on('mouseup', throttledUpdate, this);
544 this._content.one(SELECTORS.EQUATION_TEXT).on('keyup', throttledUpdate, this);
62467795
AN
545 this._content.delegate('click', this._selectLibraryItem, SELECTORS.LIBRARY_BUTTON, this);
546
547 return this._content;
8bf5ad67
DW
548 },
549
050159dc
FM
550 /**
551 * Callback handling the keyboard navigation in the groups of the library.
552 *
553 * @param {EventFacade} e The event.
554 * @method _groupNavigation
555 * @private
556 */
557 _groupNavigation: function(e) {
558 e.preventDefault();
559
560 var current = e.currentTarget,
561 parent = current.get('parentNode'), // This must be the <div> containing all the buttons of the group.
562 buttons = parent.all('button'),
563 direction = e.keyCode !== 37 ? 1 : -1,
564 index = buttons.indexOf(current),
565 nextButton;
566
567 if (index < 0) {
568 Y.log('Unable to find the current button in the list of buttons', 'debug', LOGNAME);
569 index = 0;
570 }
571
572 index += direction;
573 if (index < 0) {
574 index = buttons.size() - 1;
575 } else if (index >= buttons.size()) {
576 index = 0;
577 }
578 nextButton = buttons.item(index);
579
580 this._setGroupTabFocus(parent, nextButton);
581 nextButton.focus();
582 },
583
584 /**
585 * Sets tab focus for the group.
586 *
587 * @method _setGroupTabFocus
588 * @param {Node} button The node that focus should now be set to.
589 * @private
590 */
591 _setGroupTabFocus: function(parent, button) {
592 var parentId = parent.generateID();
593
594 // Unset the previous entry.
595 if (typeof this._groupFocus[parentId] !== 'undefined') {
596 this._groupFocus[parentId].setAttribute('tabindex', '-1');
597 }
598
599 // Set on the new entry.
600 this._groupFocus[parentId] = button;
601 button.setAttribute('tabindex', 0);
602 parent.setAttribute('aria-activedescendant', button.generateID());
603 },
604
8bf5ad67 605 /**
62467795 606 * Reponse to button presses in the TeX library panels.
8bf5ad67 607 *
62467795
AN
608 * @method _selectLibraryItem
609 * @param {EventFacade} e
610 * @return {string}
611 * @private
8bf5ad67 612 */
62467795 613 _selectLibraryItem: function(e) {
d926ce71
AN
614 var tex = e.currentTarget.getAttribute('data-tex'),
615 oldValue,
616 newValue,
617 input,
618 focusPoint = 0;
8bf5ad67 619
62467795 620 e.preventDefault();
8bf5ad67 621
050159dc
FM
622 // Set the group focus on the button.
623 this._setGroupTabFocus(e.currentTarget.get('parentNode'), e.currentTarget);
624
62467795 625 input = e.currentTarget.ancestor('.atto_form').one('textarea');
8bf5ad67 626
d926ce71
AN
627 oldValue = input.get('value');
628
629 newValue = oldValue.substring(0, this._lastCursorPos);
630 if (newValue.charAt(newValue.length - 1) !== ' ') {
631 newValue += ' ';
632 }
633 newValue += tex;
634 focusPoint = newValue.length;
8bf5ad67 635
d926ce71
AN
636 if (oldValue.charAt(this._lastCursorPos) !== ' ') {
637 newValue += ' ';
638 }
639 newValue += oldValue.substring(this._lastCursorPos, oldValue.length);
8bf5ad67 640
d926ce71 641 input.set('value', newValue);
8bf5ad67 642 input.focus();
9ee8a359 643
d926ce71 644 var realInput = input.getDOMNode();
9ee8a359
AN
645 if (typeof realInput.selectionStart === "number") {
646 // Modern browsers have selectionStart and selectionEnd to control the cursor position.
647 realInput.selectionStart = realInput.selectionEnd = focusPoint;
648 } else if (typeof realInput.createTextRange !== "undefined") {
649 // Legacy browsers (IE<=9) use createTextRange().
650 var range = realInput.createTextRange();
651 range.moveToPoint(focusPoint);
652 range.select();
653 }
654 // Focus must be set before updating the preview for the cursor box to be in the correct location.
62467795 655 this._updatePreview(false);
8bf5ad67
DW
656 },
657
658 /**
659 * Return the HTML for rendering the library of predefined buttons.
660 *
62467795
AN
661 * @method _getLibraryContent
662 * @return {string}
663 * @private
8bf5ad67 664 */
62467795
AN
665 _getLibraryContent: function() {
666 var template = Y.Handlebars.compile(TEMPLATES.LIBRARY),
667 library = this.get('library'),
668 content = '';
669
670 // Helper to iterate over a newline separated string.
671 Y.Handlebars.registerHelper('split', function(delimiter, str, options) {
672 var parts,
673 current,
674 out;
675 if (typeof delimiter === "undefined" || typeof str === "undefined") {
676 Y.log('Handlebars split helper: String and delimiter are required.', 'debug', 'moodle-atto_equation-button');
677 return '';
8bf5ad67 678 }
62467795
AN
679
680 out = '';
681 parts = str.trim().split(delimiter);
682 while (parts.length > 0) {
cc90cedc 683 current = parts.shift().trim();
62467795
AN
684 out += options.fn(current);
685 }
686
687 return out;
688 });
689 content = template({
690 elementid: this.get('host').get('elementid'),
691 component: COMPONENTNAME,
692 library: library,
cc90cedc
DW
693 CSS: CSS,
694 DELIMITERS: DELIMITERS
62467795 695 });
8bf5ad67 696
cc90cedc
DW
697 var url = M.cfg.wwwroot + '/lib/editor/atto/plugins/equation/ajax.php';
698 var params = {
699 sesskey: M.cfg.sesskey,
700 contextid: this.get('contextid'),
701 action: 'filtertext',
702 text: content
703 };
704
557f44d9 705 var preview = Y.io(url, {
cc90cedc
DW
706 sync: true,
707 data: params,
708 method: 'POST'
709 });
8bf5ad67 710
cc90cedc
DW
711 if (preview.status === 200) {
712 content = preview.responseText;
8bf5ad67
DW
713 }
714 return content;
715 }
62467795
AN
716}, {
717 ATTRS: {
718 /**
719 * Whether the TeX filter is currently active.
720 *
721 * @attribute texfilteractive
722 * @type Boolean
723 */
724 texfilteractive: {
725 value: false
726 },
cc90cedc 727
62467795
AN
728 /**
729 * The contextid to use when generating this preview.
730 *
731 * @attribute contextid
732 * @type String
733 */
734 contextid: {
735 value: null
736 },
737
738 /**
739 * The content of the example library.
740 *
741 * @attribute library
742 * @type object
743 */
744 library: {
745 value: {}
36beb828
FM
746 },
747
748 /**
749 * The link to the Moodle Docs page about TeX.
750 *
751 * @attribute texdocsurl
752 * @type string
753 */
754 texdocsurl: {
755 value: null
62467795 756 }
cc90cedc 757
62467795
AN
758 }
759});
8bf5ad67
DW
760
761
ea382dea
AN
762}, '@VERSION@', {
763 "requires": [
764 "moodle-editor_atto-plugin",
765 "moodle-core-event",
766 "io",
767 "event-valuechange",
768 "tabview",
769 "array-extras"
770 ]
771});